lists.arthurdejong.org
RSS feed

python-stdnum branch master updated. 1.20-23-g8519221

[Date Prev][Date Next] [Thread Prev][Thread Next]

python-stdnum branch master updated. 1.20-23-g8519221



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-stdnum".

The branch, master has been updated
       via  8519221a54c2baa3a3d7253969402f84aa2353b5 (commit)
      from  0f94ca6b48ea690d3937d1c9a83d76406d2cf451 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://arthurdejong.org/git/python-stdnum/commit/?id=8519221a54c2baa3a3d7253969402f84aa2353b5

commit 8519221a54c2baa3a3d7253969402f84aa2353b5
Author: Quique Porta <me@quiqueporta.com>
Date:   Thu Jul 4 13:33:23 2024 +0200

    Add Spanish CAE Number
    
    Closes https://github.com/arthurdejong/python-stdnum/pull/446

diff --git a/stdnum/es/cae.py b/stdnum/es/cae.py
new file mode 100644
index 0000000..19b9717
--- /dev/null
+++ b/stdnum/es/cae.py
@@ -0,0 +1,239 @@
+# cae.py - functions for handling Spanish CAE number
+# coding: utf-8
+#
+# Copyright (C) 2024 Quique Porta
+#
+# 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
+
+"""CAE (Código de Actividad y Establecimiento, Spanish activity establishment 
code).
+
+The Código de Actividad y Establecimiento (CAE) is assigned by the Spanish
+Tax Agency companies or establishments that carry out activities related to
+products subject to excise duty. It identifies an activity and the
+establishment in which it is carried out.
+
+The number consists of 13 characters where the sixth and seventh characters
+identify the managing office in which the territorial registration is carried
+out and the eighth and ninth characters identify the activity that takes
+place.
+
+
+More information:
+
+* https://www.boe.es/boe/dias/2006/12/28/pdfs/A46098-46100.pdf
+* 
https://www2.agenciatributaria.gob.es/L/inwinvoc/es.aeat.dit.adu.adce.cae.cw.AccW?fAccion=consulta
+
+>>> validate('ES00008V1488Q')
+'ES00008V1488Q'
+>>> validate('00008V1488')  # invalid check length
+Traceback (most recent call last):
+    ...
+InvalidLength: ...
+>>> is_valid('ES00008V1488Q')
+True
+>>> is_valid('00008V1488')
+False
+>>> compact('ES00008V1488Q')
+'ES00008V1488Q'
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean, isdigits
+
+
+_OFFICES = {
+    '01',  # Álava
+    '02',  # Albacete
+    '03',  # Alicante
+    '04',  # Almería
+    '05',  # Ávila
+    '06',  # Badajoz
+    '07',  # Illes Balears
+    '08',  # Barcelona
+    '09',  # Burgos
+    '10',  # Cáceres
+    '11',  # Cádiz
+    '12',  # Castellón
+    '13',  # Ciudad Real
+    '14',  # Córdoba
+    '15',  # A Coruña
+    '16',  # Cuenca
+    '17',  # Girona
+    '18',  # Granada
+    '19',  # Guadalajara
+    '20',  # Guipúzcoa
+    '21',  # Huelva
+    '22',  # Huesca
+    '23',  # Jaén
+    '24',  # León
+    '25',  # Lleida
+    '26',  # La Rioja
+    '27',  # Lugo
+    '28',  # Madrid
+    '29',  # Málaga
+    '30',  # Murcia
+    '31',  # Navarra
+    '32',  # Ourense
+    '33',  # Oviedo
+    '34',  # Palencia
+    '35',  # Las Palmas
+    '36',  # Pontevedra
+    '37',  # Salamanca
+    '38',  # Santa Cruz de Tenerife
+    '39',  # Santander
+    '40',  # Segovia
+    '41',  # Sevilla
+    '42',  # Soria
+    '43',  # Tarragona
+    '44',  # Teruel
+    '45',  # Toledo
+    '46',  # Valencia
+    '47',  # Valladolid
+    '48',  # Bizcaia
+    '49',  # Zamora
+    '50',  # Zaragoza
+    '51',  # Cartagena
+    '52',  # Gijón
+    '53',  # Jerez de la Frontera
+    '54',  # Vigo
+    '55',  # Ceuta
+    '56',  # Melilla
+}
+
+_ACTIVITY_KEYS = {
+    'A1',  # Fábricas de alcohol
+    'B1',  # Fábricas de bebidas derivadas
+    'B9',  # Elaboradores de productos intermedios distintos de los 
comprendidos en B-0
+    'B0',  # Elaboradores de productos intermedios en régimen especial
+    'BA',  # Fábricas de bebidas alcohólicas
+    'C1',  # Fábricas de cerveza
+    'DA',  # Destiladores artesanales
+    'EC',  # Fábricas de extractos y concentrados alcohólicos
+    'F1',  # Elaboradores de otras bebidas fermentadas
+    'V1',  # Elaboradores de vinos
+    'A7',  # Depósitos fiscales de alcohol
+    'AT',  # Almacenes fiscales de alcohol
+    'B7',  # Depósitos fiscales de bebidas derivadas
+    'BT',  # Almacenes fiscales de bebidas alcohólicas
+    'C7',  # Depósitos fiscales de cerveza
+    'DB',  # Depósitos fiscales de bebidas alcohólicas
+    'E7',  # Depósitos fiscales de extractos y concentrados alcohólicos 
exclusivamente
+    'M7',  # Depósitos fiscales de productos intermedios
+    'OA',  # Operadores registrados de alcohol
+    'OB',  # Operadores registrados de bebidas alcohólicas
+    'OE',  # Operadores registrados de extractos y concentrados alcohólicos
+    'OV',  # Operadores registrados de vinos y de otras bebidas fermentadas
+    'V7',  # Depósitos fiscales de vinos y de otras bebidas fermentadas
+    'B6',  # Plantas embotelladoras de bebidas derivadas
+    'A2',  # Centros de investigación
+    'A6',  # Usuarios de alcohol totalmente desnaturalizado
+    'A9',  # Industrias de especialidades farmacéuticas
+    'A0',  # Centros de atención médica
+    'AC',  # Usuarios con derecho a devolución
+    'AV',  # Usuarios de alcohol parcialmente desnaturalizado con 
desnaturalizante general
+    'AW',  # Usuarios de alcohol parcialmente desnaturalizado con 
desnaturalizante especial
+    'AX',  # Fábricas de vinagre
+    'H1',  # Refinerías de crudo de petróleo
+    'H2',  # Fábricas de biocarburante, consistente en alcohol etílico
+    'H4',  # Fábricas de biocarburante o biocombustible con sistente en 
biodiesel
+    'H6',  # Fábricas de biocarburante o biocombustible con sistente en 
alcohol metílico
+    'H9',  # Industrias extractoras de gas natural y otros productos gaseosos
+    'H0',  # Las demás industrias que obtienen productos gravados
+    'HD',  # Industrias o establecimientos que someten productos a un 
tratamiento definido o,
+           # Previa solicitud, a una transformación química
+    'HH',  # Industrias extractoras de crudo de petróleo
+    'H7',  # Depósitos fiscales de hidrocarburos
+    'H8',  # Depósitos fiscales exclusivamente de biocarburantes
+    'HB',  # Obtención accesoria de productos sujetos alimpuesto
+    'HF',  # Almacenes fiscales para el suministro directo a instalaciones 
fijas
+    'HI',  # Depósitos fiscales exclusivamente para la distribución de 
querosenos y gasolinas de aviación
+    'HJ',  # Depósitos fiscales exclusivamente de productos de la tarifa 
segunda
+    'HK',  # Instalaciones de venta de gas natural con tipo general y tipo 
reducido
+    'HL',  # Almacenes fiscales exclusivamente de productos de la tarifa 
segunda
+    'HM',  # Almacenes fiscales para la gestión de aceites usados destinados a 
su utilización como  combustibles
+    'HN',  # Depósitos fiscales constituidos por una red de oleoductos
+    'HT',  # Almacenes fiscales para el comercio al por mayor de hidrocarburos
+    'HU',  # Almacenes fiscales constituidos por redes de transporte o 
distribución de gas natural
+    'HV',  # Puntos de suministro marítimo de gasóleo
+    'HX',  # Depósitos fiscales constituidos por una red de gasoductos
+    'HZ',  # Detallistas de gasóleo
+    'OH',  # Operadores registrados de hidrocarburos
+    'HA',  # Titulares de aeronaves que utilizan instalaciones privadas
+    'HC',  # Explotaciones industriales y proyectos piloto con derecho a 
devolución
+    'HE',  # Los demás usuarios con derecho a exención
+    'HP',  # Inyección en altos hornos
+    'HQ',  # Construcción, modificación, pruebas y mantenimiento de aeronaves 
y embarcaciones
+    'HR',  # Producción de electricidad en centrales eléctricas o producción 
de electricidad o
+           # cogeneración de electricidad y de calor en centrales combinadas
+    'HS',  # Transporte por ferrocarril
+    'HW',  # Consumidores de combustibles y carburantes a tipo reducido 
(artículos 106.4 y 108
+           # del Reglamento de los Impuestos Especiales)
+    'T1',  # Fábricas de labores del tabaco
+    'OT',  # Operadores registrados de labores del tabaco
+    'T7',  # Depósitos fiscales de labores del tabaco
+    'TT',  # Almacenes fiscales de labores del tabaco
+    'L1',  # Fábricas de electricidad en régimen ordinario
+    'L2',  # Generadores o conjunto de generadores de potencia total superior 
a 100 kilovatios
+    'L0',  # Fábricas de electricidad en régimen especial
+    'L3',  # Los demás sujetos pasivos
+    'L7',  # Depósitos fiscales de electricidad
+    'AF',  # Almacenes fiscales de bebidas alcohólicas y de labores del tabaco
+    'DF',  # Depósitos fiscales de bebidas alcohólicas y de labores del tabaco
+    'DM',  # Depósitos fiscales de bebidas alcohólicas y de labores del tabaco 
situados en
+           # puertos y aeropuertos y que funcionen exclusivamente como 
establecimientos minoristas
+    'DP',  # Depósitos fiscales para el suministro de bebidas alcohólicas y de 
labores del
+           # tabaco para consumo o venta a bordo de buques y/o aeronaves
+    'OR',  # Operadores registrados de bebidas alcohólicas y de labores del 
tabaco
+    'PF',  # Industrias o usuarios en régimen de perfeccionamiento fiscal
+    'RF',  # Representantes fiscales
+    'VD',  # Empresas de ventas a distancia
+}
+
+
+def compact(number):
+    """Convert the number to the minimal representation. This strips the
+    number of any valid separators and removes surrounding whitespace."""
+    return clean(number).upper().strip()
+
+
+def validate(number):
+    """Check if the number provided is a valid CAE number. This checks the
+    length and formatting."""
+    number = compact(number)
+    if len(number) != 13:
+        raise InvalidLength()
+    if number[:2] != 'ES':
+        raise InvalidFormat()
+    if number[2:5] != '000':
+        raise InvalidFormat()
+    if number[5:7] not in _OFFICES:
+        raise InvalidFormat()
+    if number[7:9] not in _ACTIVITY_KEYS:
+        raise InvalidFormat()
+    if not isdigits(number[9:12]):
+        raise InvalidFormat()
+    if not number[12].isalpha():
+        raise InvalidFormat()
+    return number
+
+
+def is_valid(number):
+    """Check if the number provided is a valid CAE number. This checks the
+    length and formatting."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
diff --git a/tests/test_es_cae.doctest b/tests/test_es_cae.doctest
new file mode 100644
index 0000000..1af6230
--- /dev/null
+++ b/tests/test_es_cae.doctest
@@ -0,0 +1,261 @@
+test_es_cae.doctest - more detailed doctests for stdnum.es.cae module
+
+Copyright (C) 2024 Quique Porta
+
+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
+
+
+This file contains more detailed doctests for the stdnum.es.cae and related
+modules. It tries to cover more corner cases and detailed functionality that
+is not really useful as module documentation.
+
+>>> from stdnum.es import cae
+
+
+>>> cae.compact('ES00008V1567A')  # valid CAE
+'ES00008V1567A'
+>>> cae.is_valid('ES10008V1567A')  # invalid CAE characters third, fourth and 
fifth must be zero
+False
+>>> cae.is_valid('ES01008V1567A')  # invalid CAE characters third, fourth and 
fifth must be zero
+False
+>>> cae.is_valid('ES00108V1567A')  # invalid CAE characters third, fourth and 
fifth must be zero
+False
+>>> cae.is_valid('ES00008V1567')  # invalid CAE length
+False
+>>> cae.is_valid('IT00008V1567A')  # invalid CAE not starting with ES
+False
+>>> cae.is_valid('ES00000V1567A')  # invalid CAE office
+False
+>>> cae.is_valid('ES00008XX567A')  # invalid CAE activity key
+False
+>>> cae.is_valid('ES00008V1X67A')  # invalid CAE characters tenth, eleventh 
and twelfth must be digits
+False
+>>> cae.is_valid('ES00008V15X7A')  # invalid CAE characters tenth, eleventh 
and twelfth must be digits
+False
+>>> cae.is_valid('ES00008V156XA')  # invalid CAE characters tenth, eleventh 
and twelfth must be digits
+False
+>>> cae.is_valid('ES00008V15670')  # invalid CAE last character must be a 
letter
+False
+
+
+These should all be valid numbers.
+
+>>> numbers = '''
+...
+... ES00001BA567A
+... ES00001H0567A
+... ES00002OV567A
+... ES00002RF567A
+... ES00002V1567A
+... ES00003A1567A
+... ES00003A2567A
+... ES00003OV567A
+... ES00003VD567A
+... ES00004A0567A
+... ES00004A7567A
+... ES00004A9567A
+... ES00004AX567A
+... ES00004DM567A
+... ES00004F1567A
+... ES00004H9567A
+... ES00004OA567A
+... ES00005HX567A
+... ES00006HC567A
+... ES00006HL567A
+... ES00006HS567A
+... ES00006M7567A
+... ES00006RF567A
+... ES00007A7567A
+... ES00007HN567A
+... ES00007OB567A
+... ES00007OV567A
+... ES00009DM567A
+... ES00010H9567A
+... ES00010HF567A
+... ES00010HL567A
+... ES00010HS567A
+... ES00010HX567A
+... ES00011B6567A
+... ES00011C7567A
+... ES00011DA567A
+... ES00011H9567A
+... ES00011HL567A
+... ES00011HQ567A
+... ES00011L0567A
+... ES00012AX567A
+... ES00012DM567A
+... ES00012TT567A
+... ES00013A6567A
+... ES00013HA567A
+... ES00014A7567A
+... ES00014HC567A
+... ES00014L7567A
+... ES00015AC567A
+... ES00015HH567A
+... ES00016AW567A
+... ES00016DP567A
+... ES00016HJ567A
+... ES00017HF567A
+... ES00017L0567A
+... ES00017L7567A
+... ES00018HX567A
+... ES00019H1567A
+... ES00019HD567A
+... ES00019HX567A
+... ES00019TT567A
+... ES00020B1567A
+... ES00020H2567A
+... ES00020H9567A
+... ES00020L3567A
+... ES00020TT567A
+... ES00021H1567A
+... ES00021H8567A
+... ES00021OH567A
+... ES00021OT567A
+... ES00022C7567A
+... ES00022HR567A
+... ES00022OV567A
+... ES00023AT567A
+... ES00023AW567A
+... ES00023AX567A
+... ES00023HU567A
+... ES00024AT567A
+... ES00024HA567A
+... ES00024HC567A
+... ES00024HJ567A
+... ES00025E7567A
+... ES00025OH567A
+... ES00025OR567A
+... ES00026B6567A
+... ES00026H6567A
+... ES00027E7567A
+... ES00027HD567A
+... ES00027HR567A
+... ES00027M7567A
+... ES00027OT567A
+... ES00027OV567A
+... ES00028A2567A
+... ES00028B6567A
+... ES00028HA567A
+... ES00028HD567A
+... ES00028PF567A
+... ES00029B7567A
+... ES00029EC567A
+... ES00029HD567A
+... ES00029HV567A
+... ES00030E7567A
+... ES00030HM567A
+... ES00030HU567A
+... ES00031L7567A
+... ES00031OB567A
+... ES00032A9567A
+... ES00032BA567A
+... ES00032L2567A
+... ES00032OR567A
+... ES00033H7567A
+... ES00033H8567A
+... ES00033HR567A
+... ES00034AX567A
+... ES00034HI567A
+... ES00034VD567A
+... ES00035A0567A
+... ES00035AW567A
+... ES00036E7567A
+... ES00036H2567A
+... ES00036HL567A
+... ES00036HN567A
+... ES00036HR567A
+... ES00036VD567A
+... ES00037HE567A
+... ES00037HM567A
+... ES00037HS567A
+... ES00037L3567A
+... ES00037L7567A
+... ES00038A2567A
+... ES00038H6567A
+... ES00038OT567A
+... ES00039B6567A
+... ES00039H7567A
+... ES00039HI567A
+... ES00039HP567A
+... ES00040A6567A
+... ES00040AX567A
+... ES00040H0567A
+... ES00040H1567A
+... ES00041A0567A
+... ES00041A6567A
+... ES00041H7567A
+... ES00041HJ567A
+... ES00041HN567A
+... ES00041HU567A
+... ES00042A7567A
+... ES00042HM567A
+... ES00042RF567A
+... ES00043A0567A
+... ES00043AF567A
+... ES00043H2567A
+... ES00043HX567A
+... ES00043RF567A
+... ES00043V1567A
+... ES00044AC567A
+... ES00044DA567A
+... ES00044RF567A
+... ES00044TT567A
+... ES00045HR567A
+... ES00045OB567A
+... ES00046B0567A
+... ES00046H9567A
+... ES00046HQ567A
+... ES00047DM567A
+... ES00047HL567A
+... ES00047HZ567A
+... ES00048HX567A
+... ES00048OR567A
+... ES00048OT567A
+... ES00048RF567A
+... ES00049AT567A
+... ES00049C1567A
+... ES00049E7567A
+... ES00049HA567A
+... ES00050HL567A
+... ES00050HX567A
+... ES00051C1567A
+... ES00051E7567A
+... ES00051F1567A
+... ES00051H6567A
+... ES00051OR567A
+... ES00051OV567A
+... ES00052A0567A
+... ES00052A7567A
+... ES00052AX567A
+... ES00052H1567A
+... ES00052HE567A
+... ES00052HZ567A
+... ES00052OB567A
+... ES00052T7567A
+... ES00053F1567A
+... ES00053L3567A
+... ES00053VD567A
+... ES00054AT567A
+... ES00054F1567A
+... ES00054HR567A
+... ES00054HT567A
+... ES00055DF567A
+... ES00056HA567A
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not cae.is_valid(x)]
+[]

-----------------------------------------------------------------------

Summary of changes:
 stdnum/es/cae.py          | 239 ++++++++++++++++++++++++++++++++++++++++++
 tests/test_es_cae.doctest | 261 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 500 insertions(+)
 create mode 100644 stdnum/es/cae.py
 create mode 100644 tests/test_es_cae.doctest


hooks/post-receive
-- 
python-stdnum