File: excise.py

package info (click to toggle)
python-stdnum 2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,960 kB
  • sloc: python: 10,600; javascript: 6,995; sh: 13; makefile: 10
file content (83 lines) | stat: -rw-r--r-- 2,688 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# excise.py - functions for handling EU Excise numbers
# coding: utf-8
#
# Copyright (C) 2023 Cédric Krier
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA

"""European Excise Number

The excise duty identification number assigned to businesses authorised to
operate with excise goods (e.g. alcohol, tobacco, energy products, etc.).

The number is issued by the national customs or tax authority of the Member
State where the business is established. The number consists of a two-letter
country code followed by up to 13 alphanumeric characters.

More information:

* https://ec.europa.eu/taxation_customs/dds2/seed/

>>> validate('LU 987ABC')
'LU00000987ABC'
"""

from __future__ import annotations

from stdnum.eu.vat import MEMBER_STATES
from stdnum.exceptions import *
from stdnum.util import NumberValidationModule, clean, get_cc_module


_country_modules = dict()


def _get_cc_module(cc: str) -> NumberValidationModule | None:
    """Get the Excise number module based on the country code."""
    cc = cc.lower()
    if cc not in MEMBER_STATES:
        raise InvalidComponent()
    if cc not in _country_modules:
        _country_modules[cc] = get_cc_module(cc, 'excise')
    return _country_modules[cc]


def compact(number: str) -> str:
    """Convert the number to the minimal representation. This strips the number
    of any valid separators and removes surrounding whitespace."""
    number = clean(number, ' ').upper().strip()
    if len(number) < 13:
        number = number[:2] + number[2:].zfill(11)
    return number


def validate(number: str) -> str:
    """Check if the number is a valid Excise number."""
    number = compact(number)
    if len(number) != 13:
        raise InvalidLength()
    module = _get_cc_module(number[:2])
    if module:
        module.validate(number)
    return number


def is_valid(number: str) -> bool:
    """Check if the number is a valid excise number."""
    try:
        return bool(validate(number))
    except ValidationError:
        return False