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
- From: Commits of the python-stdnum project <python-stdnum-commits [at] lists.arthurdejong.org>
- To: python-stdnum-commits [at] lists.arthurdejong.org
- Reply-to: python-stdnum-users [at] lists.arthurdejong.org, python-stdnum-commits [at] lists.arthurdejong.org
- Subject: python-stdnum branch master updated. 1.20-23-g8519221
- Date: Sat, 15 Feb 2025 18:28:33 +0100 (CET)
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
- python-stdnum branch master updated. 1.20-23-g8519221,
Commits of the python-stdnum project