Kaynağa Gözat

inital commit with some initial work

Radu Boncea 6 yıl önce
işleme
ba12553aad

+ 249 - 0
.gitignore

@@ -0,0 +1,249 @@
+
+# Created by https://www.gitignore.io/api/python,sublimetext,pycharm+all,visualstudiocode
+# Edit at https://www.gitignore.io/?templates=python,sublimetext,pycharm+all,visualstudiocode
+
+### PyCharm+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm+all Patch ###
+# Ignores the whole .idea folder and all .iml files
+# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
+
+.idea/
+
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### Python Patch ###
+.venv/
+
+### SublimeText ###
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+
+# End of https://www.gitignore.io/api/python,sublimetext,pycharm+all,visualstudiocode

+ 4 - 0
AUTHORS

@@ -0,0 +1,4 @@
+Developers:
+Radu Boncea <radu.boncea@gmail.com>
+
+Contributors:

+ 22 - 0
LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2018 Agile Geeks SRL-D
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, 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.

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# A library for checking on European VAT formats

BIN
doc/VIES-VAT Validation Routines-v15.0.doc


+ 0 - 0
pyVat/__init__.py


+ 118 - 0
pyVat/api.py

@@ -0,0 +1,118 @@
+from __future__ import unicode_literals, print_function
+
+import re
+import sys
+
+
+VAT_NUMBER_REGEXPS = {
+    'AT': re.compile(r'^U\d{8}$', re.IGNORECASE),
+    'BE': re.compile(r'^\d{9,10}$'),
+    'BG': re.compile(r'^\d{9,10}$'),
+    'CY': re.compile(r'^\d{8}[a-z]$', re.IGNORECASE),
+    'CZ': re.compile(r'^\d{8,10}$'),
+    'DE': re.compile(r'^\d{9}$'),
+    'DK': re.compile(r'^\d{8}$'),
+    'EE': re.compile(r'^\d{9}$'),
+    'ES': re.compile(r'^[\da-z]\d{7}[\da-z]$', re.IGNORECASE),
+    'FI': re.compile(r'^\d{8}$'),
+    'FR': re.compile(r'^[\da-hj-np-z]{2}\d{9}$', re.IGNORECASE),
+    'GB': re.compile(r'^((\d{9})|(\d{12})|(GD\d{3})|(HA\d{3}))$',
+                     re.IGNORECASE),
+    'GR': re.compile(r'^\d{9}$'),
+    'HR': re.compile(r'^\d{11}$'),
+    'HU': re.compile(r'^\d{8}$'),
+    'IE': re.compile(r'^((\d{7}[a-z])|(\d[a-z]\d{5}[a-z])|(\d{6,7}[a-z]{2}))$',
+                     re.IGNORECASE),
+    'IT': re.compile(r'^\d{11}$'),
+    'LT': re.compile(r'^((\d{9})|(\d{12}))$'),
+    'LU': re.compile(r'^\d{8}$'),
+    'LV': re.compile(r'^\d{11}$'),
+    'MT': re.compile(r'^\d{8}$'),
+    'NL': re.compile(r'^\d{9}B\d{2,3}$', re.IGNORECASE),
+    'PL': re.compile(r'^\d{10}$'),
+    'PT': re.compile(r'^\d{9}$'),
+    'RO': re.compile(r'^\d{2,10}$'),
+    'SE': re.compile(r'^\d{12}$'),
+    'SI': re.compile(r'^\d{8}$'),
+    'SK': re.compile(r'^\d{10}$'),
+}
+
+def load_class(module_name, class_name):
+    mod = __import__(module_name, fromlist=[class_name])
+    klass = getattr(mod, class_name)
+    return klass
+
+def load_cc_validator(cc):
+    module_name = 'pyVat.validators.%s' % cc
+    klass_name = 'Validator'
+    klass = load_class(module_name, klass_name)
+    return klass
+
+class VatValidationError(Exception):
+    """
+    Exception thrown by the Validator object when the VAT number is not valid
+    """
+    pass
+
+class Validator(object):
+
+    def __init__(self, vat_number, vat_country_code=None):
+
+        self.error_message = None
+        self.vat_number = vat_number
+        self.vat_country_code = vat_country_code
+
+
+    def clean(self):
+        vat_number = self.vat_number
+        vat_country_code = self.vat_country_code
+
+        try:
+            vat_number = str(vat_number)
+        except:
+            VatValidationError('Invalid VAT number provided')
+        vat_number = vat_number.replace(' ', '')
+
+        if vat_country_code is not None:
+            try:
+                vat_country_code = str(vat_country_code)
+            except:
+                VatValidationError('Invalid VAT country provided')
+            vat_country_code = vat_country_code.replace(' ', '')
+            vat_country_code = vat_country_code.upper()
+        # if no vat_country_code provided we try to extract it from vat_number
+        else:
+            try:
+                vat_country_code = vat_number[:2]
+            except:
+                raise VatValidationError('Invalid VAT number provided')
+
+            vat_country_code = vat_country_code.upper()
+
+
+            vat_number = vat_number[2:]
+
+        if vat_country_code == 'EL':
+            vat_country_code = 'GR'
+
+        if vat_country_code not in VAT_NUMBER_REGEXPS.keys():
+            raise VatValidationError('Invalid VAT country')
+
+        if len(vat_number)>2:
+            if vat_number[:2].upper() == vat_country_code:
+                vat_number = vat_number[2:]
+
+        return vat_number,vat_country_code
+
+    def validate(self):
+        try:
+            self.vat_number, self.country_code = self.clean()
+        except VatValidationError as e:
+            self.error_message = str(e)
+            return False
+
+        validator_klass = load_cc_validator(self.country_code.lower())
+        validator = validator_klass()
+        return validator.validate(str(self.vat_number))
+
+

+ 0 - 0
pyVat/validators/__init__.py


+ 31 - 0
pyVat/validators/at.py

@@ -0,0 +1,31 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^U\d{8}$', re.IGNORECASE)
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+        checknum = int (vat_number[8])
+
+        r = 0
+        for i in range(3,8,2):
+            digit = int (vat_number[i-1])
+            r = r + ( int( digit/5 ) + (digit*2)%10 )
+
+        c = 4
+        for i in range(2,9,2):
+            c = c + int (vat_number[i-1])
+
+        c9 = (10 - (r+c)%10) % 10
+
+        return checknum == c9

+ 21 - 0
pyVat/validators/be.py

@@ -0,0 +1,21 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^0[1-9]{1}\d{8}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+        checknum = int(vat_number[8:10])
+        checkval = 97 - ( int(vat_number[:8]) % 97 )
+
+        return checkval == checknum

+ 73 - 0
pyVat/validators/bg.py

@@ -0,0 +1,73 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{9,10}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+        # if is legal entity
+        if (len(vat_number)==9):
+            checknum = int(vat_number[8])
+            a1 = self.sum_weights(list(range(1,9)), vat_number[:8])
+            r1 = a1 % 11
+
+            if r1 == 10:
+                a2 = self.sum_weights(list(range(3,11)), vat_number[:8])
+                r2 = a2 % 11
+                if r2 == 10:
+                    r = 0
+                else:
+                    r = r2
+            else:
+                r = r1
+
+            return checknum == r
+
+        # physical person
+        checknum = int(vat_number[9])
+        weights = [2, 4, 8, 5, 10, 9, 7, 3, 6]
+        a1 = self.sum_weights(weights, vat_number[:9])
+        r1 = a1 % 11
+        if r1 == 10:
+            r = 0
+        else:
+            r = r1
+        if checknum == r:
+            return True
+
+        # foreigners
+        checknum = int(vat_number[9])
+        weights = [21, 19, 17, 13, 11, 9, 7, 3, 1]
+        a1 = self.sum_weights(weights, vat_number[:9])
+        r = a1 % 10
+        if checknum == r:
+            return True
+
+        # others
+        checknum = int(vat_number[9])
+        weights = [4, 3, 2, 7, 6, 5, 4, 3, 2]
+        a1 = self.sum_weights(weights, vat_number[:9])
+        r1 = 11 - a1 % 11
+        if r1 == 11:
+            r = 0
+        elif r1 == 10:
+            return False
+        else:
+            r = r1
+        if checknum == r:
+            return True
+
+        return False
+
+

+ 76 - 0
pyVat/validators/cy.py

@@ -0,0 +1,76 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{8}[a-z]$', re.IGNORECASE)
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+        if int(vat_number[0]) not in [0, 1, 3, 4, 5, 9]:
+            return False
+        if vat_number[:2] == '12':
+            return False
+
+        odd_digit_mapping = {
+            0: 1,
+            1: 0,
+            2: 5,
+            3: 7,
+            4: 9,
+            5: 13,
+            6: 15,
+            7: 17,
+            8: 19,
+            9: 21
+        }
+        a1 = 0
+        for i in range(8):
+            if i % 2 == 0:
+                a1 = a1 + odd_digit_mapping [ int(vat_number[i]) ]
+            else:
+                a1 = a1 + + int(vat_number[i])
+
+        r = a1 % 26
+
+        last_char_mapping = {
+            0: 'A',
+            1: 'B',
+            2: 'C',
+            3: 'D',
+            4: 'E',
+            5: 'F',
+            6: 'G',
+            7: 'H',
+            8: 'I',
+            9: 'J',
+            10: 'K',
+            11: 'L',
+            12: 'M',
+            13: 'N',
+            14: 'O',
+            15: 'P',
+            16: 'Q',
+            17: 'R',
+            18: 'S',
+            19: 'T',
+            20: 'U',
+            21: 'V',
+            22: 'W',
+            23: 'X',
+            24: 'Y',
+            25: 'Z'
+        }
+
+        last_char = vat_number[8]
+        last_char = last_char.upper()
+
+        return last_char_mapping[r] == last_char

+ 114 - 0
pyVat/validators/cz.py

@@ -0,0 +1,114 @@
+import re
+import math
+import calendar
+import datetime
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{8,10}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+        # legal entities
+        if len(vat_number) == 8:
+            if vat_number[0] == '9':
+                return False
+            checknum = int (vat_number[7])
+            a1 = self.sum_weights(list(range(8,1,-1)), vat_number[:8])
+            if a1 % 11 == 0:
+                a2 = a1 + 11
+            else:
+                a2 = math.ceil(a1/11) * 11
+            c8 = (a2 - a1) % 10
+
+            return checknum == c8
+
+        # individuals
+        if len(vat_number) == 9:
+
+            # special cases
+            if vat_number[0] == '6':
+                checknum = int(vat_number[8])
+                a1 = self.sum_weights(list(range(8, 1, -1)), vat_number[1:8])
+                if a1 % 11 == 0:
+                    a2 = a1 + 11
+                else:
+                    a2 = math.ceil(a1/11) * 11
+                d = a2 - a1
+                last_digit_mapping = {
+                    1: 8,
+                    2: 7,
+                    3: 6,
+                    4: 5,
+                    5: 4,
+                    6: 3,
+                    7: 2,
+                    8: 1,
+                    9: 0,
+                    10: 9,
+                    11: 8
+                }
+                return checknum == last_digit_mapping[d]
+            else:
+                # common individuals
+                if int(vat_number[:2])>53:
+                    return False
+
+                monthval = int(vat_number[2:4])
+                if monthval not in range (1,13) and monthval not in range(51,63):
+                    return False
+
+                if monthval>12:
+                    monthval = monthval - 50
+                num_days_month = calendar.monthrange( int('19'.join(vat_number[:2])), monthval)[1]
+
+                daysval = int(vat_number[4:6])
+                if daysval<1 or daysval>num_days_month:
+                    return False
+
+                return True
+
+        # individuals - born between 1954 and 1999
+        if len(vat_number) == 10:
+            if int(vat_number) % 11 != 0:
+                return False
+
+            current_year = str(datetime.date.today().year)
+            current_year = int(current_year[2:])
+            yearval = int(vat_number[:2])
+            if yearval not in range (current_year+1) and yearval not in range(54,100):
+                return False
+
+            monthval = int(vat_number[2:4])
+            if monthval not in range(1, 13) and monthval not in range(21, 33)\
+                    and monthval not in range(51, 63) and monthval not in range(71, 83):
+                return False
+
+            year_prefix = '19'
+            if yearval<13:
+                year_prefix = '20'
+            num_days_month = calendar.monthrange(int(year_prefix.join(vat_number[:2])), monthval)[1]
+            daysval = int(vat_number[4:6])
+            if daysval < 1 or daysval > num_days_month:
+                return False
+
+            a1 = 0
+            for i in range(0,9,2):
+                a1 = a1 + int(vat_number[i] + vat_number[i+1])
+
+            if a1 % 11 != 0:
+                return False
+
+            return True
+
+        return False

+ 36 - 0
pyVat/validators/de.py

@@ -0,0 +1,36 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{9}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+        checknum = int (vat_number[8])
+
+        if vat_number[0] == '0':
+            return False
+
+        p = 10
+        for i in range(8):
+            s = int(vat_number[i]) + p
+            m = s % 10
+            if m == 0:
+                m = 10
+            p = (2 * m) % 11
+        r = 11 - p
+        if r == 10:
+            c9 = 0
+        else:
+            c9 = r
+
+        return checknum == c9

+ 27 - 0
pyVat/validators/dk.py

@@ -0,0 +1,27 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{8}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+
+        if vat_number[0] == '0':
+            return False
+
+
+        r = self.sum_weights([2, 7, 6, 5, 4, 3, 2, 1], vat_number)
+        if r % 11 == 0:
+            return True
+
+        return False

+ 29 - 0
pyVat/validators/ee.py

@@ -0,0 +1,29 @@
+import re
+import math
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{9}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+
+        if vat_number[:2] != '10':
+            return False
+
+        checknum = int(vat_number[8])
+
+        a1 = self.sum_weights([3, 7, 1, 3, 7, 1, 3, 7], vat_number)
+        a2 = math.ceil(a1 / 10) * 10
+        c9 = a2 - a1
+
+        return checknum == c9

+ 40 - 0
pyVat/validators/es.py

@@ -0,0 +1,40 @@
+import re
+import math
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^[\da-z]\d{7}[\da-z]$', re.IGNORECASE)
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+
+        vat_number = str(vat_number)
+
+        try:
+            int( vat_number[8] )
+        except:
+            c9_is_number = False
+        else:
+            c9_is_number = True
+
+        c1 = vat_number[0].upper()
+        if c9_is_number:
+            if c1 not in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'U', 'V']:
+                return False
+        else:
+            if c1 not in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'K',
+                          'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'W', 'X', 'Y', 'Z' ]:
+                return False
+
+
+        # case A)
+        if c9_is_number is False and c1 in ['A', 'B', 'C', 'D', 'E', 'F', 'G',
+                                            'H', 'N', 'P', 'Q', 'R', 'S', 'W']:
+            s1 = int(vat_number[2]) + int(vat_number[4]) + int(vat_number[6])

+ 21 - 0
pyVat/validators/generic.py

@@ -0,0 +1,21 @@
+import re
+
+class GenericValidator(object):
+    """
+    Generic validator
+    """
+
+    def __init__(self):
+        self.regexp = re.compile(r'^.{1,}$')
+
+    def validate(self, vat_number):
+        vat_number = str(vat_number)
+        if not self.regexp.match(vat_number):
+            return False
+
+
+    def sum_weights(self, weights, number, start_pos=0):
+        checkval = 0;
+        for i in range(start_pos, len(weights)):
+            checkval = checkval + int(number[i]) * weights[i]
+        return checkval

+ 26 - 0
pyVat/validators/gr.py

@@ -0,0 +1,26 @@
+import re
+import math
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{9}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+
+        vat_number = str(vat_number)
+
+        checknum = int(vat_number[8])
+
+        a1 = self.sum_weights([256, 128, 64, 32, 16, 8, 4, 2], vat_number)
+        a2 = a1 % 11
+        c9 = a2 % 10
+
+        return checknum == c9

+ 26 - 0
pyVat/validators/ro.py

@@ -0,0 +1,26 @@
+import re
+from .generic import GenericValidator
+
+
+class Validator(GenericValidator):
+    """
+    For rules see /docs/VIES-VAT Validation Routines-v15.0.doc
+    """
+
+    def __init__(self):
+        self.regexp = re.compile(r'^\d{2,10}$')
+
+    def validate(self, vat_number):
+        if super(Validator, self).validate(vat_number) is False:
+            return False
+
+        vat_number = str(vat_number)
+
+        vat_number = vat_number.rjust(10,'0')
+        checksum = int (vat_number[9])
+        weights = [7, 5, 3, 2, 1, 7, 5, 3, 2]
+        checkval = self.sum_weights(weights, vat_number)
+        checkval = (checkval * 10) % 11
+        if checkval==10:
+            checkval=0
+        return checkval == checksum

+ 42 - 0
setup.py

@@ -0,0 +1,42 @@
+import setuptools
+
+with open("README.md", "r") as fh:
+    long_description = fh.read()
+
+
+setuptools.setup(
+    name="vat-format-checker",
+    version="0.0.1",
+    author="Radu Boncea",
+    author_email="radu.boncea@gmail.com",
+    description="A library for checking on European VAT formats",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url="https://github.com/agilegeeks/pyVat",
+    packages=setuptools.find_packages(exclude=['tests']),
+    entry_points={
+    },
+    install_requires=[
+    ],
+    python_requires='>=2.7',
+    classifiers=[
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Developers",
+        "Intended Audience :: Financial and Insurance Industry",
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3.0",
+        "Programming Language :: Python :: 3.1",
+        "Programming Language :: Python :: 3.2",
+        "Programming Language :: Python :: 3.3",
+        "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Topic :: Database :: Front-Ends",
+        "Topic :: Office/Business :: Financial :: Accounting",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Utilities",
+    ],
+)

+ 166 - 0
tests/test_validator.py

@@ -0,0 +1,166 @@
+#from pyVat.validators.ro import Validator
+import unittest
+
+from pyVat.api import Validator
+
+class TestValidator(unittest.TestCase):
+
+    def test_ro(self):
+        validator = Validator('RO2785503')
+        self.assertTrue( validator.validate() )
+        self.assertEqual( validator.country_code, 'RO' )
+        self.assertEqual( validator.vat_number, '2785503' )
+        validator = Validator('2785503', 'RO')
+        self.assertTrue(validator.validate())
+
+        validator = Validator('ro 278  5503')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'RO')
+
+        validator = Validator('RO2785500')
+        self.assertFalse(validator.validate())
+
+    def test_at(self):
+        validator = Validator('ATU10223006')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'AT')
+
+        validator = Validator('ATU10223005')
+        self.assertFalse(validator.validate())
+
+    def test_be(self):
+        validator = Validator('BE0776091951')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'BE')
+        self.assertEqual(validator.vat_number, '0776091951')
+
+        validator = Validator('BE0776091950')
+        self.assertFalse(validator.validate())
+
+    def test_bg(self):
+
+        # test legal entity
+        validator = Validator('BG101004508')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'BG')
+        self.assertEqual(validator.vat_number, '101004508')
+
+        validator = Validator('BG101004502')
+        self.assertFalse(validator.validate())
+
+        # test physical person
+        validator = Validator('BG0041010002')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'BG')
+        self.assertEqual(validator.vat_number, '0041010002')
+
+        # foreigners
+        validator = Validator('BG0000100159')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'BG')
+        self.assertEqual(validator.vat_number, '0000100159')
+
+        # others
+        validator = Validator('BG0000100153')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'BG')
+        self.assertEqual(validator.vat_number, '0000100153')
+
+    def test_cy(self):
+        validator = Validator('CY00532445O') # the last char is O from oranges
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'CY')
+        self.assertEqual(validator.vat_number, '00532445O')
+
+        validator = Validator('CY12000139V')
+        self.assertFalse(validator.validate())
+
+
+    def test_cz(self):
+
+        # test legal entity
+        validator = Validator('CZ46505334')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'CZ')
+        self.assertEqual(validator.vat_number, '46505334')
+
+        validator = Validator('CZ46505332')
+        self.assertFalse(validator.validate())
+
+        # test individuals - special cases
+        validator = Validator('CZ640903926')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'CZ')
+        self.assertEqual(validator.vat_number, '640903926')
+
+        validator = Validator('CZ46505331')
+        self.assertFalse(validator.validate())
+
+        # test common individuals
+        validator = Validator('CZ395601439')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'CZ')
+        self.assertEqual(validator.vat_number, '395601439')
+        validator = Validator('CZ520229439')
+        self.assertTrue(validator.validate())
+
+        validator = Validator('CZ705601439')
+        self.assertFalse(validator.validate())
+
+        # test common individuals born after 1953
+        validator = Validator('CZ7103192745')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'CZ')
+        self.assertEqual(validator.vat_number, '7103192745')
+
+        validator = Validator('CZ7103192744')
+        self.assertFalse(validator.validate())
+
+    def test_de(self):
+        validator = Validator('DE111111125')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'DE')
+        self.assertEqual(validator.vat_number, '111111125')
+
+        validator = Validator('DE111111122')
+        self.assertFalse(validator.validate())
+
+
+    def test_dk(self):
+        validator = Validator('DK88146328')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'DK')
+        self.assertEqual(validator.vat_number, '88146328')
+
+        validator = Validator('DK88146327')
+        self.assertFalse(validator.validate())
+
+    def test_ee(self):
+        validator = Validator('EE100207415')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'EE')
+        self.assertEqual(validator.vat_number, '100207415')
+
+        validator = Validator('EE100207417')
+        self.assertFalse(validator.validate())
+
+    def test_gr(self):
+        validator = Validator('EL040127797')
+        self.assertTrue(validator.validate())
+        self.assertEqual(validator.country_code, 'GR')
+        self.assertEqual(validator.vat_number, '040127797')
+        validator = Validator('040127797', 'EL')
+        self.assertTrue(validator.validate())
+
+        validator = Validator('EL040127798')
+        self.assertFalse(validator.validate())
+
+if __name__ == '__main__':
+    unittest.main()
+
+#
+#
+#
+# validator = Validator()
+#
+# print (validator.validate(36804251))