Compare commits
1 Commits
NominaHabi
...
feature/py
| Author | SHA1 | Date | |
|---|---|---|---|
| 93a3d18e32 |
20
Dockerfile
20
Dockerfile
@@ -7,34 +7,40 @@ RUN apt install software-properties-common -y \
|
||||
&& add-apt-repository ppa:deadsnakes/ppa
|
||||
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
python3.9 python3.9-distutils python3.9-dev \
|
||||
python3.10 python3.10-distutils python3.10-dev \
|
||||
python3.11 python3.11-distutils python3.11-dev \
|
||||
python3.12 python3-setuptools python3.12-dev \
|
||||
python3.13 python3-setuptools python3.13-dev \
|
||||
wget \
|
||||
ca-certificates
|
||||
|
||||
RUN wget https://bootstrap.pypa.io/get-pip.py \
|
||||
&& python3.9 get-pip.py pip==23.2.1 --break-system-packages \
|
||||
&& python3.10 get-pip.py pip==23.2.1 --break-system-packages \
|
||||
&& python3.11 get-pip.py pip==23.2.1 --break-system-packages \
|
||||
&& python3.12 get-pip.py pip==23.2.1 --break-system-packages \
|
||||
&& python3.13 get-pip.py pip==23.2.1 --break-system-packages \
|
||||
&& rm get-pip.py
|
||||
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
build-essential \
|
||||
gcc \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
xmlsec1 \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
zip
|
||||
|
||||
RUN python3.9 --version
|
||||
RUN python3.10 --version
|
||||
RUN python3.11 --version
|
||||
RUN python3.12 --version
|
||||
RUN python3.13 --version
|
||||
|
||||
|
||||
RUN pip3.9 install setuptools setuptools-rust
|
||||
RUN pip3.10 install setuptools setuptools-rust
|
||||
RUN pip3.11 install setuptools setuptools-rust --break-system-packages
|
||||
RUN pip3.12 install setuptools setuptools-rust --break-system-packages
|
||||
RUN pip3.13 install setuptools setuptools-rust --break-system-packages
|
||||
|
||||
RUN pip3 install tox pytest --break-system-packages
|
||||
|
||||
@@ -15,7 +15,7 @@ dev-shell:
|
||||
docker run --rm -ti -v "$(PWD):/app" -w /app --name facho-cli facho bash
|
||||
|
||||
test:
|
||||
docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.12 setup.py test'
|
||||
docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.13 -m pytest'
|
||||
|
||||
tox:
|
||||
docker run -it -v $(PWD)/:/app -w /app facho tox
|
||||
|
||||
@@ -140,12 +140,12 @@ class LXMLBuilder:
|
||||
attrs['pretty_print'] = attrs.pop('pretty_print', False)
|
||||
attrs['encoding'] = attrs.pop('encoding', 'UTF-8')
|
||||
|
||||
for el in elem.getiterator():
|
||||
for el in elem.iter():
|
||||
keys = filter(lambda key: key.startswith('facho_'), el.keys())
|
||||
self.remove_attributes(el, keys, exclude=['facho_optional'])
|
||||
|
||||
is_optional = el.get('facho_optional', 'False') == 'True'
|
||||
if is_optional and el.getchildren() == [] and el.keys() == [
|
||||
if is_optional and list(el) == [] and el.keys() == [
|
||||
'facho_optional']:
|
||||
el.getparent().remove(el)
|
||||
|
||||
@@ -291,7 +291,7 @@ class FachoXML:
|
||||
# se fuerza la adicion como un nuevo elemento
|
||||
if append:
|
||||
last_slibing = None
|
||||
for child in parent.getchildren():
|
||||
for child in list(parent):
|
||||
if child.tag == node_tag:
|
||||
last_slibing = child
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import xmlsec
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass, asdict, field
|
||||
from typing import List
|
||||
|
||||
import http.client
|
||||
import hashlib
|
||||
import secrets
|
||||
@@ -47,7 +47,7 @@ class GetNumberingRangeResponse:
|
||||
ValidateDateTo: str
|
||||
TechnicalKey: str
|
||||
|
||||
NumberRangeResponse: List[NumberRangeResponse]
|
||||
NumberRangeResponse: list[NumberRangeResponse]
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -91,7 +91,7 @@ class SendBillAsync(SOAPService):
|
||||
@dataclass
|
||||
class SendTestSetAsyncResponse:
|
||||
ZipKey: str
|
||||
ErrorMessageList: List[str]
|
||||
ErrorMessageList: list[str]
|
||||
|
||||
@classmethod
|
||||
def fromdict(cls, data):
|
||||
@@ -134,7 +134,7 @@ class GetStatusResponse:
|
||||
IsValid: bool
|
||||
StatusDescription: str
|
||||
StatusCode: int
|
||||
ErrorMessage: List[str]
|
||||
ErrorMessage: list[str]
|
||||
|
||||
@classmethod
|
||||
def fromdict(cls, data):
|
||||
|
||||
@@ -9,7 +9,7 @@ module.
|
||||
|
||||
"""
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from lxml import etree
|
||||
from lxml.etree import QName
|
||||
|
||||
@@ -222,7 +222,7 @@ def sign_envelope(
|
||||
return _sign_envelope_with_key(envelope, key, signature_method, digest_method)
|
||||
|
||||
def get_timestamp(timestamp = None, delta=None):
|
||||
timestamp = timestamp or datetime.utcnow()
|
||||
timestamp = timestamp or datetime.now(timezone.utc)
|
||||
if delta:
|
||||
timestamp += delta
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ class CodeList:
|
||||
row = {}
|
||||
|
||||
#construir registro...
|
||||
for value in xmlrow.getchildren():
|
||||
row[value.attrib['ColumnRef']] = value.getchildren()[0].text
|
||||
for value in list(xmlrow):
|
||||
row[value.attrib['ColumnRef']] = list(value)[0].text
|
||||
|
||||
return row
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import uuid
|
||||
import xmlsig
|
||||
import xades
|
||||
from datetime import datetime
|
||||
import OpenSSL
|
||||
import zipfile
|
||||
# import warnings
|
||||
import hashlib
|
||||
@@ -18,6 +17,21 @@ from dateutil import tz
|
||||
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
|
||||
# Monkey-patch xades/xmlsig para compatibilidad con pyOpenSSL >= 24
|
||||
# (PKCS12 fue eliminado de OpenSSL.crypto, pero ambas librerías lo referencian)
|
||||
import OpenSSL
|
||||
import xades.xades_context
|
||||
|
||||
if not hasattr(OpenSSL.crypto, 'PKCS12'):
|
||||
def _patched_load_pkcs12(self, key):
|
||||
if isinstance(key, tuple):
|
||||
self.x509 = key[1]
|
||||
self.public_key = key[1].public_key()
|
||||
self.private_key = key[0]
|
||||
else:
|
||||
raise NotImplementedError("unsupported key type")
|
||||
xades.xades_context.XAdESContext.load_pkcs12 = _patched_load_pkcs12
|
||||
|
||||
AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code']
|
||||
AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code']
|
||||
|
||||
@@ -444,9 +458,6 @@ class DianXMLExtensionSigner:
|
||||
self._pkcs12_data,
|
||||
self._passphrase))
|
||||
|
||||
# ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(
|
||||
# self._pkcs12_data,
|
||||
# self._passphrase))
|
||||
if self._localpolicy:
|
||||
with mock_xades_policy():
|
||||
ctx.sign(signature)
|
||||
@@ -590,8 +601,8 @@ class DianXMLExtensionSignerVerifier:
|
||||
if isinstance(self._pkcs12_path_or_bytes, str):
|
||||
pkcs12_data = open(self._pkcs12_path_or_bytes, 'rb').read()
|
||||
ctx = xades.XAdESContext()
|
||||
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(pkcs12_data,
|
||||
self._passphrase))
|
||||
ctx.load_pkcs12(pkcs12.load_key_and_certificates(
|
||||
pkcs12_data, self._passphrase))
|
||||
try:
|
||||
if self._localpolicy:
|
||||
with mock_xades_policy():
|
||||
|
||||
@@ -11,7 +11,6 @@ from datetime import datetime, date
|
||||
# from collections import defaultdict
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
import typing
|
||||
from ..data.dian import codelist
|
||||
|
||||
DECIMAL_PRECISION = 6
|
||||
@@ -60,7 +59,7 @@ class AmountCollection(Collection):
|
||||
|
||||
class Amount:
|
||||
def __init__(
|
||||
self, amount: typing.Union[int, float, str, "Amount"],
|
||||
self, amount: int | float | str | "Amount",
|
||||
currency: Currency = Currency('COP')):
|
||||
|
||||
# DIAN 1.7.-2020: 1.2.3.1
|
||||
@@ -294,7 +293,7 @@ class TaxScheme:
|
||||
class Party:
|
||||
name: str
|
||||
ident: str
|
||||
responsability_code: typing.List[Responsability]
|
||||
responsability_code: list[Responsability]
|
||||
responsability_regime_code: str
|
||||
organization_code: str
|
||||
tax_scheme: TaxScheme = field(default_factory=lambda: TaxScheme('01'))
|
||||
@@ -327,7 +326,7 @@ class TaxScheme:
|
||||
@dataclass
|
||||
class TaxSubTotal:
|
||||
percent: float
|
||||
scheme: typing.Optional[TaxScheme] = None
|
||||
scheme: TaxScheme | None = None
|
||||
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
|
||||
|
||||
def calculate(self, invline):
|
||||
@@ -359,7 +358,7 @@ class TaxTotalOmit(TaxTotal):
|
||||
@dataclass
|
||||
class WithholdingTaxSubTotal:
|
||||
percent: float
|
||||
scheme: typing.Optional[TaxScheme] = None
|
||||
scheme: TaxScheme | None = None
|
||||
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
|
||||
|
||||
def calculate(self, invline):
|
||||
@@ -491,7 +490,7 @@ class AllowanceCharge:
|
||||
reason: AllowanceChargeReason = None
|
||||
|
||||
# Valor Base para calcular el descuento o el cargo
|
||||
base_amount: typing.Optional[Amount] = field(
|
||||
base_amount: Amount | None = field(
|
||||
default_factory=lambda: Amount(0.0))
|
||||
|
||||
# Porcentaje: Porcentaje que aplicar.
|
||||
@@ -536,9 +535,9 @@ class InvoiceLine:
|
||||
# ya que al reportar los totales es sobre
|
||||
# la factura y el percent es unico por type_code
|
||||
# de subtotal
|
||||
tax: typing.Optional[TaxTotal]
|
||||
withholding: typing.Optional[WithholdingTaxTotal]
|
||||
allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(
|
||||
tax: TaxTotal | None
|
||||
withholding: WithholdingTaxTotal | None
|
||||
allowance_charge: list[AllowanceCharge] = dataclasses.field(
|
||||
default_factory=list)
|
||||
|
||||
def add_allowance_charge(self, charge):
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import typing
|
||||
|
||||
|
||||
from .. import fe
|
||||
from .. import form
|
||||
@@ -86,13 +86,13 @@ class NumeroSecuencia:
|
||||
|
||||
@dataclass
|
||||
class Periodo:
|
||||
fecha_ingreso: typing.Union[str, Fecha]
|
||||
fecha_liquidacion_inicio: typing.Union[str, Fecha]
|
||||
fecha_liquidacion_fin: typing.Union[str, Fecha]
|
||||
fecha_generacion: typing.Union[str, Fecha]
|
||||
fecha_ingreso: str | Fecha
|
||||
fecha_liquidacion_inicio: str | Fecha
|
||||
fecha_liquidacion_fin: str | Fecha
|
||||
fecha_generacion: str | Fecha
|
||||
|
||||
tiempo_laborado: int = 1
|
||||
fecha_retiro: typing.Union[str, Fecha] = None
|
||||
fecha_retiro: str | Fecha | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
self.fecha_ingreso = Fecha.cast(self.fecha_ingreso)
|
||||
@@ -259,7 +259,7 @@ class InformacionGeneral:
|
||||
def __str__(self):
|
||||
self.valor
|
||||
|
||||
fecha_generacion: typing.Union[str, Fecha]
|
||||
fecha_generacion: str | Fecha
|
||||
hora_generacion: str
|
||||
periodo_nomina: PeriodoNomina
|
||||
tipo_moneda: TipoMoneda
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
from ..amount import Amount
|
||||
from .devengado import Devengado
|
||||
@@ -30,7 +30,7 @@ class DevengadoHoraExtra:
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasDiarias(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HEDs')
|
||||
@@ -39,7 +39,7 @@ class DevengadoHorasExtrasDiarias(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasNocturnas(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HENs')
|
||||
@@ -49,7 +49,7 @@ class DevengadoHorasExtrasNocturnas(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasRecargoNocturno(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HRNs')
|
||||
@@ -58,7 +58,7 @@ class DevengadoHorasRecargoNocturno(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HEDDFs')
|
||||
@@ -67,7 +67,7 @@ class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HRDDFs')
|
||||
@@ -76,7 +76,7 @@ class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HENDFs')
|
||||
@@ -85,7 +85,7 @@ class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasRecargoNocturnoDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HRNDFs')
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
attrs==22.1.0
|
||||
distlib==0.3.6
|
||||
filelock==3.8.0
|
||||
iniconfig==1.1.1
|
||||
packaging==21.3
|
||||
platformdirs==2.5.2
|
||||
pluggy==1.0.0
|
||||
py==1.11.0
|
||||
pyparsing==3.0.9
|
||||
pytest==7.1.3
|
||||
attrs==24.2.0
|
||||
distlib==0.3.9
|
||||
filelock==3.16.1
|
||||
iniconfig==2.0.0
|
||||
packaging==24.1
|
||||
platformdirs==4.3.6
|
||||
pluggy==1.5.0
|
||||
pyparsing==3.2.0
|
||||
pytest==8.3.4
|
||||
semantic-version==2.10.0
|
||||
setuptools-rust==1.5.2
|
||||
setuptools-rust==1.10.2
|
||||
six==1.16.0
|
||||
tomli==2.0.1
|
||||
tox==3.26.0
|
||||
typing_extensions==4.4.0
|
||||
virtualenv==20.16.5
|
||||
tomli==2.2.1
|
||||
tox==4.23.2
|
||||
virtualenv==20.28.0
|
||||
|
||||
18
setup.py
18
setup.py
@@ -14,14 +14,14 @@ with open('HISTORY.rst') as history_file:
|
||||
history = history_file.read()
|
||||
|
||||
requirements = ['Click>=8.1.7',
|
||||
'zeep==4.2.1',
|
||||
'lxml==5.2.2',
|
||||
'cryptography==3.3.2',
|
||||
'pyOpenSSL==20.0.1',
|
||||
'xmlsig==0.1.7',
|
||||
'xades==1.0.0',
|
||||
'xmlsec==1.3.14',
|
||||
'python-dateutil==2.9.0.post0',
|
||||
'zeep>=4.2.1',
|
||||
'lxml>=5.2.2',
|
||||
'cryptography>=41.0.0',
|
||||
'pyOpenSSL>=23.2.0',
|
||||
'xmlsig>=0.1.9',
|
||||
'xades>=1.0.0',
|
||||
'xmlsec>=1.3.12',
|
||||
'python-dateutil>=2.9.0',
|
||||
# usamos esta dependencia en runtime
|
||||
# para forzar uso de policy_id de archivo local
|
||||
'mock>=5.1.0',
|
||||
@@ -57,10 +57,10 @@ setup(
|
||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
'Natural Language :: English',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Programming Language :: Python :: 3.13',
|
||||
],
|
||||
description="Facturacion Electronica Colombia",
|
||||
entry_points={
|
||||
|
||||
Reference in New Issue
Block a user