1 Commits

Author SHA1 Message Date
cosmos
c919d8e36c Add timestamp 2024-08-22 09:41:37 -05:00
75 changed files with 2935 additions and 5859 deletions

1
.gitignore vendored
View File

@@ -215,4 +215,3 @@ tags
pyvenv.cfg
.venv
pip-selfcheck.json
invoice.xml

View File

@@ -69,7 +69,7 @@ Ready to contribute? Here's how to set up `facho` for local development.
$ python3 -mvenv facho-venv && source facho-venv/bin/activate
$ cd facho/
$ pre-commit install
$ pip install -e .
$ python setup.py develop
4. Create a branch for local development::
@@ -81,7 +81,7 @@ Ready to contribute? Here's how to set up `facho` for local development.
tests, including testing other Python versions with tox::
$ flake8 facho tests
$ py.test
$ python setup.py test or py.test
$ tox
To get flake8 and tox, just pip install them into your virtualenv.
@@ -97,10 +97,10 @@ Ready to contribute? Here's how to set up `facho` for local development.
Using docker
------------
1. make -f Makefile.dev dev-setup
1. make -f Makefile.dev build
2. make -f Makefile.dev dev-shell
3. make -f Makefile.dev test
4. make -f Makefile.dev tox
3. make -f Makefile.dev python3.8 setup.py develop
4. make -f Makefile.dev python3.8 setup.py test
Pull Request Guidelines
-----------------------

View File

@@ -7,45 +7,34 @@ RUN apt install software-properties-common -y \
&& add-apt-repository ppa:deadsnakes/ppa
RUN apt-get install -y --no-install-recommends \
python3.7 python3.7-distutils python3.7-dev \
python3.8 python3.8-distutils python3.8-dev \
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.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 \
&& python3.7 get-pip.py pip==22.2.2 \
&& python3.8 get-pip.py pip==22.2.2 \
&& python3.9 get-pip.py pip==22.2.2 \
&& python3.10 get-pip.py pip==22.2.2 \
&& rm get-pip.py
RUN apt-get install -y --no-install-recommends \
gcc \
build-essential \
pkg-config \
libxml2-dev \
libxmlsec1-dev \
xmlsec1 \
libxml2-dev \
libxslt-dev \
libxml2-dev \
libxmlsec1-dev \
build-essential \
zip
RUN python3.7 --version
RUN python3.8 --version
RUN python3.9 --version
RUN python3.10 --version
RUN python3.11 --version
RUN python3.12 --version
RUN python3.13 --version
RUN pip3.7 install setuptools setuptools-rust
RUN pip3.8 install setuptools setuptools-rust
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
WORKDIR /app
COPY pyproject.toml README.rst ./
COPY facho ./facho
RUN pip3.13 install -e . --break-system-packages
RUN pip3 install tox pytest

View File

@@ -80,8 +80,9 @@ release: dist ## package and upload a release
twine upload dist/*
dist: clean ## builds source and wheel package
python -m build
python setup.py sdist
python setup.py bdist_wheel
ls -l dist
install: clean ## install the package to the active Python's site-packages
pip install .
python setup.py install

View File

@@ -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.13 -m pytest -vv'
docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.7 setup.py test'
tox:
docker run -it -v $(PWD)/:/app -w /app facho tox

View File

@@ -18,11 +18,11 @@
# relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here.
#
import facho
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
import facho
# -- General configuration ---------------------------------------------

View File

@@ -44,7 +44,7 @@ Once you have a copy of the source, you can install it with:
.. code-block:: console
$ pip install .
$ python setup.py install
.. _Github repo: https://github.com/bit4bit/facho

View File

@@ -11,18 +11,13 @@ from facho.fe import form_xml
from facho.fe import fe
# importar otras necesarias
from datetime import datetime
from datetime import datetime, date
# Datos del fomulario del SET de pruebas
# Número suministrado por la Dian en el momento de la creación del SET de
# Pruebas
INVOICE_AUTHORIZATION = '181360000001'
# Id suministrado por la Dian en el momento de la creación del SET de Pruebas
ID_SOFTWARE = '57bcb6d1-c591-5a90-b80a-cb030ec91440'
# Número creado por la empresa para poder crear el SET de pruebas
PIN = '19642'
# Id suministrado por la Dian en el momento de la creación del SET de Pruebas
CLAVE_TECNICA = 'fc9eac422eba16e21ffd8c5f94b3f30a6e38162d'
INVOICE_AUTHORIZATION = '181360000001' #Número suministrado por la Dian en el momento de la creación del SET de Pruebas
ID_SOFTWARE = '57bcb6d1-c591-5a90-b80a-cb030ec91440' #Id suministrado por la Dian en el momento de la creación del SET de Pruebas
PIN = '19642' #Número creado por la empresa para poder crear el SET de pruebas
CLAVE_TECNICA = 'fc9eac422eba16e21ffd8c5f94b3f30a6e38162d' ##Id suministrado por la Dian en el momento de la creación del SET de Pruebas
# callback que retonar las extensiones XML necesarias
@@ -31,25 +26,15 @@ CLAVE_TECNICA = 'fc9eac422eba16e21ffd8c5f94b3f30a6e38162d'
# muchos de los valores usados son obtenidos
# del servicio web de la DIAN.
def extensions(inv):
security_code = fe.DianXMLExtensionSoftwareSecurityCode(
ID_SOFTWARE, PIN, inv.invoice_ident)
security_code = fe.DianXMLExtensionSoftwareSecurityCode(ID_SOFTWARE, PIN, inv.invoice_ident)
authorization_provider = fe.DianXMLExtensionAuthorizationProvider()
cufe = fe.DianXMLExtensionCUFE(inv, CLAVE_TECNICA, fe.AMBIENTE_PRUEBAS)
software_provider = fe.DianXMLExtensionSoftwareProvider(
'nit_empresa', 'dígito_verificación', ID_SOFTWARE)
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization(
INVOICE_AUTHORIZATION,
# Datos tomados de la configuración
datetime(2019, 1, 19),
datetime(2030, 1, 19),
'SETP', 990000000, 995000000) # del SET de pruebas
return [
security_code,
authorization_provider,
cufe,
software_provider,
inv_authorization]
software_provider = fe.DianXMLExtensionSoftwareProvider('nit_empresa', 'dígito_verificación', ID_SOFTWARE)
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization(INVOICE_AUTHORIZATION,
datetime(2019, 1, 19),#Datos toamdos de
datetime(2030, 1, 19),#la configuración
'SETP', 990000000, 995000000)#del SET de pruebas
return [security_code, authorization_provider, cufe, software_provider, inv_authorization]
def invoice():
# factura de venta nacional
@@ -63,72 +48,70 @@ def invoice():
# asignar tipo de operacion ver DIAN:6.1.5
inv.set_operation_type('10')
inv.set_supplier(form.Party(
legal_name='Nombre registrado de la empresa',
name='Nombre comercial o él mismo nombre registrado',
ident=form.PartyIdentification(
'nit_empresa', 'digito_verificación', '31'),
legal_name = 'Nombre registrado de la empresa',
name = 'Nombre comercial o él mismo nombre registrado',
ident = form.PartyIdentification('nit_empresa', 'digito_verificación', '31'),
# obligaciones del contribuyente ver DIAN:FAK26
responsability_code=form.Responsability(['ZZ', 'O-14', 'O-48']),
responsability_code = form.Responsability(['O-07', 'O-14', 'O-48']),
# ver DIAN:FAJ28
responsability_regime_code='48',
responsability_regime_code = '48',
# tipo de organizacion juridica ver DIAN:6.2.3
organization_code='1',
email="correoempresa@correoempresa.correo",
address=form.Address(
organization_code = '1',
email = "correoempresa@correoempresa.correo",
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia')),
))
# Tercero a quien se le factura
#Tercero a quien se le factura
inv.set_customer(form.Party(
legal_name='consumidor final',
name='consumidor final',
ident=form.PartyIdentification('222222222222', '', '13'),
responsability_code=form.Responsability(['R-99-PN']),
responsability_regime_code='49',
organization_code='2',
email="consumidor_final0final.final",
address=form.Address(
legal_name = 'consumidor final',
name = 'consumidor final',
ident = form.PartyIdentification('222222222222', '', '13'),
responsability_code = form.Responsability(['R-99-PN']),
responsability_regime_code = '49',
organization_code = '2',
email = "consumidor_final0final.final",
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia')),
# tax_scheme = form.TaxScheme('01', 'IVA')
#tax_scheme = form.TaxScheme('01', 'IVA')
))
# asignar metodo de pago
inv.set_payment_mean(form.PaymentMean(
# metodo de pago ver DIAN:3.4.1
id='1',
# codigocorrespondientealmediodepagoverDIAN:3.4.2
code='20',
# fechadevencimientodelafactura
due_at=datetime.now(),
# identificadornumerico
payment_id='2'
id = '1',
# codigo correspondiente al medio de pago ver DIAN:3.4.2
code = '20',
# fecha de vencimiento de la factura
due_at = datetime.now(),
# identificador numerico
payment_id = '2'
))
# adicionar una linea al documento
inv.add_invoice_line(
form.InvoiceLine(
quantity=form.Quantity(int(20.5), '94'),
# item general de codigo 999
description='productO3',
sitem=form.StandardItem('test', 9999),
price=form.Price(
# precio base del item (sin iva)
amount=form.Amount(200.00),
# ver DIAN:6.3.5.1
type_code='01',
type='x'
),
tax=form.TaxTotal(
subtotals=[
form.TaxSubTotal(
percent=19.00,
scheme=form.TaxScheme('01')
)]
)
))
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(int(20.5), '94'),
# item general de codigo 999
description = 'productO3',
item = form.StandardItem('test', 9999),
price = form.Price(
# precio base del item (sin iva)
amount = form.Amount(200.00),
# ver DIAN:6.3.5.1
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.00,
scheme=form.TaxScheme('01')
)
]
)
))
return inv
def document_xml():
return form_xml.DIANInvoiceXML

View File

@@ -1,28 +1,26 @@
from facho import fe
def extensions(nomina):
return []
def nomina():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_metadata(fe.nomina.Metadata(
secuencia=fe.nomina.NumeroSecuencia(
numero='N00001',
numero = 'N00001',
consecutivo=232
),
lugar_generacion=fe.nomina.Lugar(
pais=fe.nomina.Pais(
code='CO'
pais = fe.nomina.Pais(
code = 'CO'
),
departamento=fe.nomina.Departamento(
code='05'
departamento = fe.nomina.Departamento(
code = '05'
),
municipio=fe.nomina.Municipio(
code='05001'
municipio = fe.nomina.Municipio(
code = '05001'
),
),
proveedor=fe.nomina.Proveedor(
@@ -34,62 +32,62 @@ def nomina():
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion='2020-01-16',
hora_generacion='1053:10-05:00',
tipo_ambiente=fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin='693',
periodo_nomina=fe.nomina.PeriodoNomina(code='1'),
tipo_moneda=fe.nomina.TipoMoneda(code='COP')
fecha_generacion = '2020-01-16',
hora_generacion = '1053:10-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin = '693',
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_empleador(fe.nomina.Empleador(
nit='700085371',
dv='1',
pais=fe.nomina.Pais(
code='CO'
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento=fe.nomina.Departamento(
code='05'
departamento = fe.nomina.Departamento(
code = '05'
),
municipio=fe.nomina.Municipio(
code='05001'
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion='calle etrivial'
direccion = 'calle etrivial'
))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato=fe.nomina.TipoContrato(
code='1'
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo=False,
tipo_documento=fe.nomina.TipoDocumento(
code='11'
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido='gnu',
segundo_apellido='emacs',
primer_nombre='facho',
lugar_trabajo=fe.nomina.LugarTrabajo(
pais=fe.nomina.Pais(code='CO'),
departamento=fe.nomina.Departamento(code='05'),
municipio=fe.nomina.Municipio(code='05001'),
direccion='calle facho'
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento='800199436',
tipo=fe.nomina.TipoTrabajador(
code='01'
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral=True,
sueldo=fe.nomina.Amount(1_500_000)
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados=60,
sueldo_trabajado=fe.nomina.Amount(3_500_000)
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(3_500_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje=fe.nomina.Amount(19),
deduccion=fe.nomina.Amount(1_000_000)
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
return nomina

View File

@@ -1,127 +1,109 @@
# importar libreria de modelos
from facho import fe, form_xml
import facho.fe.form as form
import datetime
PRIVATE_KEY_PATH = 'ruta a mi llave privada'
PRIVATE_PASSPHRASE = 'clave de la llave privada'
import facho.fe.form_xml
PRIVATE_KEY_PATH='ruta a mi llave privada'
PRIVATE_PASSPHRASE='clave de la llave privada'
# consultar las extensiones necesarias
def extensions(inv):
security_code = fe.DianXMLExtensionSoftwareSecurityCode(
'id software', 'pin', inv.invoice_ident)
security_code = fe.DianXMLExtensionSoftwareSecurityCode('id software', 'pin', inv.invoice_ident)
authorization_provider = fe.DianXMLExtensionAuthorizationProvider()
cufe = fe.DianXMLExtensionCUFE(
inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS,
'clave tecnica')
cufe = fe.DianXMLExtensionCUFE(inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS,
'clave tecnica')
nit = form.PartyIdentification('nit', '5', '31')
software_provider = fe.DianXMLExtensionSoftwareProvider(
nit, nit.dv, 'id software')
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization(
'invoice autorization',
datetime(2019, 1, 19),
datetime(2030, 1, 19),
'SETP', 990000001, 995000000)
return [
security_code,
authorization_provider,
cufe, software_provider,
inv_authorization
]
software_provider = fe.DianXMLExtensionSoftwareProvider(nit, nit.dv, 'id software')
inv_authorization = fe.DianXMLExtensionInvoiceAuthorization('invoice autorization',
datetime(2019, 1, 19),
datetime(2030, 1, 19),
'SETP', 990000001, 995000000)
return [security_code, authorization_provider, cufe, software_provider, inv_authorization]
# generar documento desde modelo a ruta indicada
def generate_document(invoice, filepath):
xml = form_xml.DIANInvoiceXML(invoice)
for extension in extensions(invoice):
xml.add_extension(extension)
form_xml.utils.DIANWriteSigned(
xml, filepath, PRIVATE_KEY_PATH, PRIVATE_PASSPHRASE, True)
form_xml.utils.DIANWriteSigned(xml, filepath, PRIVATE_KEY_PATH, PRIVATE_PASSPHRASE, True)
# Modelars las facturas
# ...
# factura de venta nacional
inv = form.NationalSalesInvoice()
# asignar periodo de facturacion
inv.set_period(datetime.now(), datetime.now())
# asignar fecha de emision de la factura
inv.set_issue(datetime.now())
# asignar prefijo y numero del documento
inv.set_ident('SETP990003033')
# asignar tipo de operacion ver DIAN:6.1.5
inv.set_operation_type('10')
# asignar proveedor
inv.set_supplier(form.Party(
legal_name='FACHOSOS',
name='FACHOSOS',
ident=form.PartyIdentification('900579212', '5', '31'),
legal_name = 'FACHO SOS',
name = 'FACHO SOS',
ident = form.PartyIdentification('900579212', '5', '31'),
# obligaciones del contribuyente ver DIAN:FAK26
responsability_code=form.Responsability(['ZZ', 'O-09', 'O-14', 'O-48']),
responsability_code = form.Responsability(['O-07', 'O-09', 'O-14', 'O-48']),
# ver DIAN:FAJ28
responsability_regime_code='48',
responsability_regime_code = '48',
# tipo de organizacion juridica ver DIAN:6.2.3
organization_code='1',
email="sdds@sd.com",
address=form.Address(
name='',
street='',
city=form.City('05001', 'Medellín'),
country=form.Country('CO', 'Colombia'),
countrysubentity=form.CountrySubentity('05', 'Antioquia'))
organization_code = '1',
email = "sdds@sd.com",
address = form.Address(
name = '',
street = '',
city = form.City('05001', 'Medellín'),
country = form.Country('CO', 'Colombia'),
countrysubentity = form.CountrySubentity('05', 'Antioquia'))
))
inv.set_customer(form.Party(
legal_name='facho-customer',
name='facho-customer',
ident=form.PartyIdentification('999999999', '', '13'),
responsability_code=form.Responsability(['R-99-PN']),
responsability_regime_code='49',
organization_code='2',
email="sdds@sd.com",
address=form.Address(
name='',
street='',
city=form.City('05001', 'Medellín'),
country=form.Country('CO', 'Colombia'),
countrysubentity=form.CountrySubentity('05', 'Antioquia'))
legal_name = 'facho-customer',
name = 'facho-customer',
ident = form.PartyIdentification('999999999', '', '13'),
responsability_code = form.Responsability(['R-99-PN']),
responsability_regime_code = '49',
organization_code = '2',
email = "sdds@sd.com",
address = form.Address(
name = '',
street = '',
city = form.City('05001', 'Medellín'),
country = form.Country('CO', 'Colombia'),
countrysubentity = form.CountrySubentity('05', 'Antioquia'))
))
# asignar metodo de pago
inv.set_payment_mean(form.PaymentMean(
# metodo de pago ver DIAN:3.4.1
id='1',
id = '1',
# codigo correspondiente al medio de pago ver DIAN:3.4.2
code='10',
code = '10',
# fecha de vencimiento de la factura
due_at=datetime.now(),
due_at = datetime.now(),
# identificador numerico
payment_id='1'
payment_id = '1'
))
# adicionar una linea al documento
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto facho',
quantity = form.Quantity(1, '94'),
description = 'producto facho',
# item general de codigo 999
item=form.StandardItem('test', 9999),
price=form.Price(
item = form.StandardItem('test', 9999),
price = form.Price(
# precio base del tiem
amount=form.Amount(100.00),
amount = form.Amount(100.00),
# ver DIAN:6.3.5.1
type_code='01',
type='x'
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.00,
)]
percent = 19.00,
)
]
)
))

View File

@@ -76,7 +76,7 @@
<cac:PartyTaxScheme>
<cbc:RegistrationName>NEUROTEC TECNOLOGIA S.A.S</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyName="CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)" schemeAgencyID="195" schemeID="5" schemeName="31">900579212</cbc:CompanyID>
<cbc:TaxLevelCode listName="48">ZZ;O-09;O-14;O-48</cbc:TaxLevelCode>
<cbc:TaxLevelCode listName="48">O-07;O-09;O-14;O-48</cbc:TaxLevelCode>
<cac:TaxScheme/>
</cac:PartyTaxScheme>
<cac:Contact>

View File

@@ -1,37 +1,39 @@
# -*- coding: utf-8 -*-
import sys
import base64
import warnings
import click
import logging.config
logging.config.dictConfig(
{
"version": 1,
"formatters": {"verbose": {"format": "%(name)s: %(message)s"}},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
},
logging.config.dictConfig({
'version': 1,
'formatters': {
'verbose': {
'format': '%(name)s: %(message)s'
}
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
"loggers": {
"zeep.transports": {
"level": "DEBUG",
"propagate": True,
"handlers": ["console"],
},
},
'loggers': {
'zeep.transports': {
'level': 'DEBUG',
'propagate': True,
'handlers': ['console'],
},
}
)
})
def disable_ssl():
# MACHETE
import ssl
if getattr(ssl, "_create_unverified_context", None):
if getattr(ssl, '_create_unverified_context', None):
ssl._create_default_https_context = ssl._create_unverified_context
warnings.warn("be sure!! ssl disable")
else:
@@ -39,122 +41,99 @@ def disable_ssl():
@click.command()
@click.option("--nit", required=True)
@click.option("--nit-proveedor", required=True)
@click.option("--id-software", required=True)
@click.option("--username", required=True)
@click.option("--password", required=True)
def consultaResolucionesFacturacion(
nit, nit_proveedor, id_software, username, password
):
@click.option('--nit', required=True)
@click.option('--nit-proveedor', required=True)
@click.option('--id-software', required=True)
@click.option('--username', required=True)
@click.option('--password', required=True)
def consultaResolucionesFacturacion(nit, nit_proveedor, id_software, username, password):
from facho.fe.client import dian
client_dian = dian.DianClient(username, password)
resp = client_dian.request(
dian.ConsultaResolucionesFacturacionPeticion(
nit, nit_proveedor, id_software
)
)
client_dian = dian.DianClient(username,
password)
resp = client_dian.request(dian.ConsultaResolucionesFacturacionPeticion(
nit, nit_proveedor, id_software
))
print(str(resp))
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.option("--test-setid", required=True)
@click.argument("filename", required=True)
@click.argument("zipfile", type=click.Path(exists=True))
def soap_send_test_set_async(
private_key,
public_key,
habilitacion,
password,
test_setid,
filename,
zipfile,
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.option('--test-setid', required=True)
@click.argument('filename', required=True)
@click.argument('zipfile', type=click.Path(exists=True))
def soap_send_test_set_async(private_key, public_key, habilitacion, password, test_setid, filename, zipfile):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.SendTestSetAsync
if habilitacion:
req = dian.Habilitacion.SendTestSetAsync
resp = client.request(
req(
filename,
open(zipfile, "rb").read(),
test_setid,
)
)
resp = client.request(req(
filename,
open(zipfile, 'rb').read(),
test_setid,
))
print(resp)
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.argument("filename", required=True)
@click.argument("zipfile", type=click.Path(exists=True))
def soap_send_bill_async(
private_key, public_key, habilitacion, password, filename, zipfile
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.argument('filename', required=True)
@click.argument('zipfile', type=click.Path(exists=True))
def soap_send_bill_async(private_key, public_key, habilitacion, password, filename, zipfile):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.SendBillAsync
if habilitacion:
req = dian.Habilitacion.SendBillAsync
resp = client.request(req(filename, open(zipfile, "rb").read()))
resp = client.request(req(
filename,
open(zipfile, 'rb').read()
))
print(resp)
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.argument("filename", required=True)
@click.argument("zipfile", type=click.Path(exists=True))
def soap_send_bill_sync(
private_key, public_key, habilitacion, password, filename, zipfile
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.argument('filename', required=True)
@click.argument('zipfile', type=click.Path(exists=True))
def soap_send_bill_sync(private_key, public_key, habilitacion, password, filename, zipfile):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.SendBillSync
if habilitacion:
req = dian.Habilitacion.SendBillSync
resp = client.request(req(filename, open(zipfile, "rb").read()))
resp = client.request(req(
filename,
open(zipfile, 'rb').read()
))
print(resp)
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.option("--track-id", required=True)
def soap_get_status_zip(
private_key, public_key, habilitacion, password, track_id
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.option('--track-id', required=True)
def soap_get_status_zip(private_key, public_key, habilitacion, password, track_id):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.GetStatusZip
if habilitacion:
req = dian.Habilitacion.GetStatusZip
resp = client.request(req(trackId=track_id))
resp = client.request(req(
trackId = track_id
))
print("StatusCode:", resp.StatusCode)
print("StatusDescription:", resp.StatusDescription)
@@ -164,78 +143,68 @@ def soap_get_status_zip(
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.option("--track-id", required=True)
def soap_get_status(
private_key, public_key, habilitacion, password, track_id
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.option('--track-id', required=True)
def soap_get_status(private_key, public_key, habilitacion, password, track_id):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.GetStatus
if habilitacion:
req = dian.Habilitacion.GetStatus
resp = client.request(req(trackId=track_id))
resp = client.request(req(
trackId = track_id
))
print(resp)
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.option("--nit", required=True)
@click.option("--nit-proveedor", required=True)
@click.option("--id-software", required=True)
def soap_get_numbering_range(
private_key,
public_key,
habilitacion,
password,
nit,
nit_proveedor,
id_software,
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.option('--nit', required=True)
@click.option('--nit-proveedor', required=True)
@click.option('--id-software', required=True)
def soap_get_numbering_range(private_key,
public_key,
habilitacion,
password,
nit, nit_proveedor, id_software):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.GetNumberingRange
if habilitacion:
req = dian.Habilitacion.GetNumberingRange
resp = client.request(req(nit, nit_proveedor, id_software))
resp = client.request(req(
nit, nit_proveedor, id_software
))
print(resp)
@click.command()
@click.argument("invoice_path")
@click.argument('invoice_path')
def validate_invoice(invoice_path):
warnings.warn("!! NO APROBADO FUNCIONAMIENTO")
from facho.fe.data.dian import XSD
content = open(invoice_path, "r").read()
content = open(invoice_path, 'r').read()
# TODO donde ubicar esta responsabilidad?
# esto es requerido por el XSD de la DIAN
content = content.replace(
'xmlns:fe="http://www.dian.gov.co/contratos/facturaelectronica/v1"',
'xmlns:fe="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"',
'xmlns:fe="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"'
)
XSD.validate(content, XSD.UBLInvoice)
@click.command()
@click.argument("nomina_path")
@click.argument('nomina_path')
def validate_nominaindividual(nomina_path):
from facho.fe.data.dian import XSD
content = open(nomina_path, "r").read()
content = open(nomina_path, 'r').read()
content = content.replace(
'xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1"',
'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"',
@@ -244,55 +213,35 @@ def validate_nominaindividual(nomina_path):
@click.command()
@click.option("--private-key", type=click.Path(exists=True))
@click.option("--passphrase")
@click.option("--ssl/--no-ssl", default=False)
@click.option("--use-cache-policy/--no-use-cache-policy", default=False)
@click.argument("xmlfile", type=click.Path(exists=True), required=True)
@click.argument("output", required=True)
def sign_xml(
private_key,
passphrase,
xmlfile,
ssl=True,
use_cache_policy=False,
output=None,
):
@click.option('--private-key', type=click.Path(exists=True))
@click.option('--passphrase')
@click.option('--ssl/--no-ssl', default=False)
@click.option('--use-cache-policy/--no-use-cache-policy', default=False)
@click.argument('xmlfile', type=click.Path(exists=True), required=True)
@click.argument('output', required=True)
def sign_xml(private_key, passphrase, xmlfile, ssl=True, use_cache_policy=False, output=None):
if not ssl:
disable_ssl()
from facho import fe
if use_cache_policy:
warnings.warn("xades using cache policy")
signer = fe.DianXMLExtensionSigner(
private_key, passphrase=passphrase, localpolicy=use_cache_policy
)
document = open(xmlfile, "r").read().encode("utf-8")
with open(output, "w") as f:
signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase, localpolicy=use_cache_policy)
document = open(xmlfile, 'r').read().encode('utf-8')
with open(output, 'w') as f:
f.write(signer.sign_xml_string(document))
@click.command()
@click.option("--private-key", type=click.Path(exists=True))
@click.option("--generate/--validate", default=False)
@click.option("--passphrase")
@click.option("--ssl/--no-ssl", default=False)
@click.option("--sign/--no-sign", default=False)
@click.option("--use-cache-policy/--no-use-cache-policy", default=False)
@click.argument("scriptname", type=click.Path(exists=True), required=True)
@click.argument("output", required=True)
def generate_invoice(
private_key,
passphrase,
scriptname,
generate=False,
ssl=True,
sign=False,
use_cache_policy=False,
output=None,
):
@click.option('--private-key', type=click.Path(exists=True))
@click.option('--generate/--validate', default=False)
@click.option('--passphrase')
@click.option('--ssl/--no-ssl', default=False)
@click.option('--sign/--no-sign', default=False)
@click.option('--use-cache-policy/--no-use-cache-policy', default=False)
@click.argument('scriptname', type=click.Path(exists=True), required=True)
@click.argument('output', required=True)
def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=True, sign=False, use_cache_policy=False, output=None):
"""
imprime xml en pantalla.
SCRIPTNAME espera
@@ -305,20 +254,18 @@ def generate_invoice(
import importlib.util
spec = importlib.util.spec_from_file_location("invoice", scriptname)
spec = importlib.util.spec_from_file_location('invoice', scriptname)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
from facho.fe.form_xml import (
DIANWriteSigned,
DIANWrite,
DIANSupportDocumentXML,
)
import facho.fe.form as form
from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned, DIANWrite, DIANSupportDocumentXML
from facho import fe
try:
invoice_xml = module.document_xml()
except AttributeError:
# invoice_xml = DIANInvoiceXML
#invoice_xml = DIANInvoiceXML
invoice_xml = DIANSupportDocumentXML
print("Using document xml:", invoice_xml)
invoice = module.invoice()
@@ -332,39 +279,24 @@ def generate_invoice(
xml.add_extension(extension)
if sign:
DIANWriteSigned(
xml, output, private_key, passphrase, use_cache_policy
)
DIANWriteSigned(xml, output, private_key, passphrase, use_cache_policy)
else:
DIANWrite(xml, output)
@click.command()
@click.option("--private-key", type=click.Path(exists=True))
@click.option("--passphrase")
@click.option("--ssl/--no-ssl", default=False)
@click.option("--sign/--no-sign", default=False)
@click.option("--use-cache-policy/--no-use-cache-policy", default=False)
@click.argument("scriptname", type=click.Path(exists=True), required=True)
@click.argument("output", required=True)
def generate_nomina(
private_key,
passphrase,
scriptname,
ssl=True,
sign=False,
use_cache_policy=False,
output=None,
):
@click.option('--private-key', type=click.Path(exists=True))
@click.option('--passphrase')
@click.option('--ssl/--no-ssl', default=False)
@click.option('--sign/--no-sign', default=False)
@click.option('--use-cache-policy/--no-use-cache-policy', default=False)
@click.argument('scriptname', type=click.Path(exists=True), required=True)
@click.argument('output', required=True)
def generate_nomina(private_key, passphrase, scriptname, ssl=True, sign=False, use_cache_policy=False, output=None):
"""
imprime xml en pantalla.
SCRIPTNAME espera
def nomina() -> (
fe.nomina.NominaIndividual
)
def extensions(
fe.nomina.NominaIndividual
): -> List[facho.FachoXMLExtension]
def nomina() -> fe.nomina.NominaIndividual
def extensions(fe.nomina.NominaIndividual): -> List[facho.FachoXMLExtension]
"""
if not ssl:
@@ -372,7 +304,7 @@ def generate_nomina(
import importlib.util
spec = importlib.util.spec_from_file_location("nomina", scriptname)
spec = importlib.util.spec_from_file_location('nomina', scriptname)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
@@ -388,83 +320,59 @@ def generate_nomina(
xml.add_extension(extension)
if sign:
DIANWriteSigned(
xml,
output,
private_key,
passphrase,
use_cache_policy,
dian_signer=facho.fe.nomina.DianXMLExtensionSigner,
)
DIANWriteSigned(xml, output, private_key, passphrase, use_cache_policy, dian_signer=facho.fe.nomina.DianXMLExtensionSigner)
else:
DIANWrite(xml, output)
@click.command()
@click.option("--private-key", required=True)
@click.option("--public-key", required=True)
@click.option("--habilitacion/--produccion", default=False)
@click.option("--password")
@click.argument("filename", required=True)
@click.argument("zipfile", type=click.Path(exists=True))
def soap_send_nomina_sync(
private_key, public_key, habilitacion, password, filename, zipfile
):
@click.option('--private-key', required=True)
@click.option('--public-key', required=True)
@click.option('--habilitacion/--produccion', default=False)
@click.option('--password')
@click.argument('filename', required=True)
@click.argument('zipfile', type=click.Path(exists=True))
def soap_send_nomina_sync(private_key, public_key, habilitacion, password, filename, zipfile):
from facho.fe.client import dian
client = dian.DianSignatureClient(
private_key, public_key, password=password
)
client = dian.DianSignatureClient(private_key, public_key, password=password)
req = dian.SendNominaSync
if habilitacion:
req = dian.Habilitacion.SendNominaSync
resp = client.request(req(open(zipfile, "rb").read()))
resp = client.request(req(
open(zipfile, 'rb').read()
))
print(resp)
@click.command()
@click.option("--private-key", type=click.Path(exists=True))
@click.option("--passphrase")
@click.option("--ssl/--no-ssl", default=False)
@click.option("--use-cache-policy/--no-use-cache-policy", default=False)
@click.argument("xmlfile", type=click.Path(exists=True), required=True)
def sign_verify_xml(
private_key,
passphrase,
xmlfile,
ssl=True,
use_cache_policy=False,
output=None,
):
@click.option('--private-key', type=click.Path(exists=True))
@click.option('--passphrase')
@click.option('--ssl/--no-ssl', default=False)
@click.option('--use-cache-policy/--no-use-cache-policy', default=False)
@click.argument('xmlfile', type=click.Path(exists=True), required=True)
def sign_verify_xml(private_key, passphrase, xmlfile, ssl=True, use_cache_policy=False, output=None):
if not ssl:
disable_ssl()
from facho.fe import fe
if use_cache_policy:
warnings.warn("xades using cache policy")
print("THIS ONLY WORKS FOR DOCUMENTS GENERATE WITH FACHO")
signer = fe.DianXMLExtensionSignerVerifier(
private_key, passphrase=passphrase, localpolicy=use_cache_policy
)
document = open(xmlfile, "r").read().encode("utf-8")
signer = fe.DianXMLExtensionSignerVerifier(private_key, passphrase=passphrase, localpolicy=use_cache_policy)
document = open(xmlfile, 'r').read().encode('utf-8')
if signer.verify_string(document):
print("+OK")
else:
print("-INVALID")
@click.command()
@click.option("--software-id")
@click.option("--software-pin")
@click.option("--nit")
@click.option("--dv")
@click.option("--output-zippath")
def generate_nomina_habilitacion(
software_id, software_pin, nit, dv, output_zippath
):
@click.option('--software-id')
@click.option('--software-pin')
@click.option('--nit')
@click.option('--dv')
@click.option('--output-zippath')
def generate_nomina_habilitacion(software_id, software_pin, nit, dv, output_zippath):
from facho import fe
generador = fe.nomina.habilitacion.Habilitacion(
@@ -472,17 +380,15 @@ def generate_nomina_habilitacion(
software_id=software_id,
software_pin=software_pin,
nit=nit,
dv=dv,
dv=dv
)
)
generador.generar(output_zippath)
@click.group()
def main():
pass
main.add_command(consultaResolucionesFacturacion)
main.add_command(soap_send_test_set_async)
main.add_command(soap_send_bill_async)

View File

@@ -119,7 +119,7 @@ class LXMLBuilder:
def is_attribute(self, elem, key, value):
return elem.get(key, False) == value
def set_attribute(self, elem, key, value):
def set_attribute(self, elem, key, value):
elem.attrib[key] = value
@classmethod
@@ -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.iter():
for el in elem.getiterator():
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 list(el) == [] and el.keys() == [
if is_optional and el.getchildren() == [] and el.keys() == [
'facho_optional']:
el.getparent().remove(el)
@@ -156,7 +156,6 @@ class FachoXML:
"""
Decora XML con funciones de consulta XPATH de un solo elemento
"""
def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',
fragment_root_element=None):
if builder is None:
@@ -292,7 +291,7 @@ class FachoXML:
# se fuerza la adicion como un nuevo elemento
if append:
last_slibing = None
for child in list(parent):
for child in parent.getchildren():
if child.tag == node_tag:
last_slibing = child

View File

@@ -14,22 +14,3 @@ from .fe import AMBIENTE_PRUEBAS
from .fe import AMBIENTE_PRODUCCION
from . import form_xml
from . import nomina
__all__ = [
'FeXML',
'fe_from_string',
'NAMESPACES',
'DianXMLExtensionSigner',
'DianXMLExtensionSoftwareSecurityCode',
'DianXMLExtensionCUFE',
'DianXMLExtensionCUDE',
'DianXMLExtensionCUDS',
'DianXMLExtensionInvoiceAuthorization',
'DianXMLExtensionSoftwareProvider',
'DianXMLExtensionAuthorizationProvider',
'DianZIP',
'AMBIENTE_PRUEBAS',
'AMBIENTE_PRODUCCION',
'form_xml',
'nomina',
]

View File

@@ -1,13 +1,23 @@
from facho import facho
import zeep
from zeep.wsse.username import UsernameToken
from .wsse.signature import BinarySignature
from .wsse.signature import Signature, BinarySignature
from zeep.wsa import WsAddressingPlugin
import xmlsec
from dataclasses import dataclass, asdict
import urllib.request
from datetime import datetime
from dataclasses import dataclass, asdict, field
from typing import List
import http.client
import hashlib
import secrets
import base64
__all__ = ['DianClient']
__all__ = ['DianClient',
'ConsultaResolucionesFacturacionPeticion',
'ConsultaResolucionesFacturacionRespuesta']
class SOAPService:
@@ -23,7 +33,6 @@ class SOAPService:
def todict(self):
return asdict(self)
@dataclass
class GetNumberingRangeResponse:
@@ -38,7 +47,8 @@ class GetNumberingRangeResponse:
ValidateDateTo: str
TechnicalKey: str
NumberRangeResponse: list[NumberRangeResponse]
NumberRangeResponse: List[NumberRangeResponse]
@classmethod
def fromdict(cls, data):
@@ -81,7 +91,7 @@ class SendBillAsync(SOAPService):
@dataclass
class SendTestSetAsyncResponse:
ZipKey: str
ErrorMessageList: list[str]
ErrorMessageList: List[str]
@classmethod
def fromdict(cls, data):
@@ -90,7 +100,6 @@ class SendTestSetAsyncResponse:
data['ErrorMessageList'] or []
)
@dataclass
class SendTestSetAsync(SOAPService):
fileName: str
@@ -106,7 +115,6 @@ class SendTestSetAsync(SOAPService):
def build_response(self, as_dict):
return SendTestSetAsyncResponse.fromdict(as_dict)
@dataclass
class SendBillSync(SOAPService):
fileName: str
@@ -121,13 +129,12 @@ class SendBillSync(SOAPService):
def build_response(self, as_dict):
return as_dict
@dataclass
class GetStatusResponse:
IsValid: bool
StatusDescription: str
StatusCode: int
ErrorMessage: list[str]
ErrorMessage: List[str]
@classmethod
def fromdict(cls, data):
@@ -155,7 +162,6 @@ class GetStatus(SOAPService):
def build_response(self, as_dict):
return GetStatusResponse.fromdict(as_dict)
@dataclass
class GetStatusZip(SOAPService):
trackId: bytes
@@ -169,7 +175,6 @@ class GetStatusZip(SOAPService):
def build_response(self, as_dict):
return GetStatusResponse.fromdict(as_dict[0])
@dataclass
class SendNominaSync(SOAPService):
contentFile: bytes
@@ -215,7 +220,6 @@ class Habilitacion:
def get_wsdl(self):
return Habilitacion.WSDL
class DianGateway:
def _open(self, service):
@@ -246,11 +250,7 @@ class DianClient(DianGateway):
self._password = password
def _open(self, service):
return zeep.Client(
service.get_wsdl(),
wsse=UsernameToken(
self._username,
self._password))
return zeep.Client(service.get_wsdl(), wsse=UsernameToken(self._username, self._password))
class DianSignatureClient(DianGateway):
@@ -262,10 +262,13 @@ class DianSignatureClient(DianGateway):
def _open(self, service):
# RESOLUCCION 0004: pagina 756
from zeep.wsse import utils
client = zeep.Client(service.get_wsdl(), wsse=BinarySignature(
self.private_key_path, self.public_key_path, self.password,
signature_method=xmlsec.Transform.RSA_SHA256,
digest_method=xmlsec.Transform.SHA256),
client = zeep.Client(service.get_wsdl(), wsse=
BinarySignature(
self.private_key_path, self.public_key_path, self.password,
signature_method=xmlsec.Transform.RSA_SHA256,
digest_method=xmlsec.Transform.SHA256)
,
)
return client

View File

@@ -9,7 +9,7 @@ module.
"""
import pytz
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from lxml import etree
from lxml.etree import QName
@@ -70,11 +70,8 @@ class MemorySignature(object):
def apply(self, envelope, headers):
key = _make_sign_key(self.key_data, self.cert_data, self.password)
_sign_envelope_with_key(
envelope,
key,
self.signature_method,
self.digest_method,
expires_dt=self.expires_dt)
envelope, key, self.signature_method, self.digest_method, expires_dt=self.expires_dt
)
return envelope, headers
def verify(self, envelope):
@@ -84,7 +81,7 @@ class MemorySignature(object):
class Signature(MemorySignature):
"""Sign given SOAP envelope with WSSE sig using given key and cert file."""
"""Sign given SOAP envelope with WSSE sig using given key file and cert file."""
def __init__(
self,
@@ -104,18 +101,15 @@ class Signature(MemorySignature):
class BinarySignature(Signature):
"""Sign given SOAP envelope with WSSE sig using given key and cert file.
"""Sign given SOAP envelope with WSSE sig using given key file and cert file.
Place the key information into BinarySecurityElement."""
def apply(self, envelope, headers):
key = _make_sign_key(self.key_data, self.cert_data, self.password)
_sign_envelope_with_key_binary(
envelope,
key,
self.signature_method,
self.digest_method,
expires_dt=self.expires_dt)
envelope, key, self.signature_method, self.digest_method, expires_dt = self.expires_dt
)
return envelope, headers
@@ -225,12 +219,10 @@ def sign_envelope(
"""
# Load the signing key and certificate.
key = _make_sign_key(_read_file(keyfile), _read_file(certfile), password)
return _sign_envelope_with_key(
envelope, key, signature_method, digest_method)
return _sign_envelope_with_key(envelope, key, signature_method, digest_method)
def get_timestamp(timestamp=None, delta=None):
timestamp = timestamp or datetime.now(timezone.utc)
def get_timestamp(timestamp = None, delta=None):
timestamp = timestamp or datetime.utcnow()
if delta:
timestamp += delta
@@ -238,33 +230,25 @@ def get_timestamp(timestamp=None, delta=None):
timestamp = timestamp.replace(tzinfo=pytz.utc, microsecond=0)
return timestamp.strftime(format_)
def _append_timestamp(security, expires_dt=None):
if expires_dt is None:
expires_dt = timedelta(seconds=6000)
etimestamp = utils.WSU.Timestamp({
'{http://docs.oasis-open.org/wss/2004/01/'
'oasis-200401-wss-wssecurity-utility-1.0.xsd}Id':
utils.get_unique_id()})
etimestamp.append(utils.WSU.Created(get_timestamp()))
etimestamp.append(utils.WSU.Expires(get_timestamp(delta=expires_dt)))
timestamp = datetime.now()
etimestamp = utils.WSU.Timestamp({'{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id': utils.get_unique_id()})
etimestamp.append(utils.WSU.Created(get_timestamp(timestamp=timestamp)))
etimestamp.append(utils.WSU.Expires(get_timestamp(timestamp=timestamp, delta=expires_dt)))
security.insert(0, etimestamp)
if etree.LXML_VERSION[:2] >= (3, 5):
etree.cleanup_namespaces(security,
keep_ns_prefixes=security.nsmap,
keep_ns_prefixes = security.nsmap,
top_nsmap=utils.NSMAP)
else:
etree.cleanup_namespaces(security)
etree.cleanup_namespaces(header)
def _signature_prepare(
envelope,
key,
signature_method,
digest_method,
expires_dt=None):
def _signature_prepare(envelope, key, signature_method, digest_method, expires_dt=None):
"""Prepare envelope and sign."""
soap_env = detect_soap_env(envelope)
# Create the Signature node.
signature = xmlsec.template.create(
@@ -295,7 +279,7 @@ def _signature_prepare(
_append_timestamp(security, expires_dt=expires_dt)
timestamp = security.find(QName(ns.WSU, "Timestamp"))
if timestamp is not None:
if timestamp != None:
_sign_node(ctx, signature, timestamp, digest_method)
ctx.sign(signature)
@@ -303,30 +287,18 @@ def _signature_prepare(
# KeyInfo. The recipient expects this structure, but we can't rearrange
# like this until after signing, because otherwise xmlsec won't populate
# the X509 data (because it doesn't understand WSSE).
sec_token_ref = etree.SubElement(
key_info, QName(
ns.WSSE, "SecurityTokenReference"))
sec_token_ref = etree.SubElement(key_info, QName(ns.WSSE, "SecurityTokenReference"))
return security, sec_token_ref, x509_data
def _sign_envelope_with_key(
envelope,
key,
signature_method,
digest_method,
expires_dt=None):
def _sign_envelope_with_key(envelope, key, signature_method, digest_method, expires_dt=None):
_, sec_token_ref, x509_data = _signature_prepare(
envelope, key, signature_method, digest_method, expires_dt=expires_dt
)
sec_token_ref.append(x509_data)
def _sign_envelope_with_key_binary(
envelope,
key,
signature_method,
digest_method,
expires_dt=None):
def _sign_envelope_with_key_binary(envelope, key, signature_method, digest_method, expires_dt=None):
security, sec_token_ref, x509_data = _signature_prepare(
envelope, key, signature_method, digest_method, expires_dt=expires_dt
)
@@ -382,10 +354,7 @@ def _verify_envelope_with_key(envelope, key):
ctx = xmlsec.SignatureContext()
# Find each signed element and register its ID with the signing context.
refs = signature.xpath(
"ds:SignedInfo/ds:Reference",
namespaces={
"ds": ns.DS})
refs = signature.xpath("ds:SignedInfo/ds:Reference", namespaces={"ds": ns.DS})
for ref in refs:
# Get the reference URI and cut off the initial '#'
referenced_id = ref.get("URI")[1:]

View File

@@ -7,17 +7,10 @@ def path_for_xsd(dirname, xsdname):
data_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(data_dir, dirname, xsdname)
UBLInvoice= xmlschema.XMLSchema(path_for_xsd('maindoc', 'UBL-Invoice-2.1.xsd'))
UBLInvoice = xmlschema.XMLSchema(
path_for_xsd(
'maindoc',
'UBL-Invoice-2.1.xsd'))
NominaIndividual = xmlschema.XMLSchema(path_for_xsd(
'nomina', 'NominaIndividualElectronicaXSDV1.0.6.xsd'))
NominaIndividualDeAjuste = xmlschema.XMLSchema(path_for_xsd(
'nomina', 'NominaIndividualDeAjusteElectronicaXSDV1.0.6.xsd'))
NominaIndividual = xmlschema.XMLSchema(path_for_xsd('nomina', 'NominaIndividualElectronicaXSDV1.0.6.xsd'))
NominaIndividualDeAjuste = xmlschema.XMLSchema(path_for_xsd('nomina', 'NominaIndividualDeAjusteElectronicaXSDV1.0.6.xsd'))
def validate(xml, schema):
schema.validate(xml)

View File

@@ -96,5 +96,27 @@
<SimpleValue>Exclusivo en referencias a documentos (elementos DocumentReference)</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>05</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Tipo de documento</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>95</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>Nota de ajuste al documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente</SimpleValue>
</Value>
<Value ColumnRef="description">
<SimpleValue>Exclusivo en referencias a documentos (elementos DocumentReference)</SimpleValue>
</Value>
</Row>
</SimpleCodeList>
</gc:CodeList>

View File

@@ -62,12 +62,28 @@
<SimpleValue>Régimen simple de tributación</SimpleValue>
</Value>
</Row>
<Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>ZZ</SimpleValue>
<SimpleValue>O-48</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No aplica</SimpleValue>
<SimpleValue>Impuesto sobre las ventas IVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>O-49</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No responsable de IVA</SimpleValue>
</Value>
</Row>
<Row>
<Value ColumnRef="code">
<SimpleValue>R-99-PN</SimpleValue>
</Value>
<Value ColumnRef="name">
<SimpleValue>No responsable</SimpleValue>
</Value>
</Row>
</SimpleCodeList>

View File

@@ -20,12 +20,12 @@ class CodeList:
def _load(self, filename, primary_column):
tree = etree.parse(filename)
# obtener identificadores...
#obtener identificadores...
self.short_name = tree.find('./Identification/ShortName').text
self.long_name = tree.find('./Identification/LongName').text
self.version = tree.find('./Identification/Version').text
# obtener registros...
#obtener registros...
for row in tree.findall('./SimpleCodeList/Row'):
new_row = self.xmlrow_to_dict(row)
primary_key = new_row[primary_column]
@@ -34,9 +34,9 @@ class CodeList:
def xmlrow_to_dict(self, xmlrow):
row = {}
# construir registro...
for value in list(xmlrow):
row[value.attrib['ColumnRef']] = list(value)[0].text
#construir registro...
for value in xmlrow.getchildren():
row[value.attrib['ColumnRef']] = value.getchildren()[0].text
return row
@@ -60,7 +60,6 @@ class CodeList:
# nombres de variables igual a ./Identification/ShortName
# TODO: garantizar unica carga en python
__all__ = ['TipoOrganizacion',
'TipoResponsabilidad',
'TipoAmbiente',
@@ -73,88 +72,38 @@ __all__ = ['TipoOrganizacion',
'Municipio',
'Departamento']
def path_for_codelist(name):
return os.path.join(DATA_DIR, name)
TipoOrganizacion = CodeList(path_for_codelist(
'TipoOrganizacion-2.1.gc'), 'code', 'name')
TipoResponsabilidad = CodeList(
path_for_codelist('TipoResponsabilidad-2.1.gc'),
'code',
'name') .update(
CodeList(
path_for_codelist('TipoResponsabilidad-2.1.custom.gc'),
'code',
'name'))
TipoAmbiente = CodeList(path_for_codelist(
'TipoAmbiente-2.1.gc'), 'code', 'name')
TipoDocumento = CodeList(path_for_codelist(
'TipoDocumento-2.1.gc'), 'code', 'name')
TipoImpuesto = CodeList(
path_for_codelist('TipoImpuesto-2.1.gc'),
'code',
'name') .update(
CodeList(
path_for_codelist('TipoImpuesto-2.1.custom.gc'),
'code',
'name'))
TarifaImpuesto = CodeList(
path_for_codelist('TarifaImpuestoINC-2.1.gc'),
'code',
'name') .update(
CodeList(
path_for_codelist('TarifaImpuestoIVA-2.1.gc'),
'code',
'name')) .update(
CodeList(
path_for_codelist('TarifaImpuestoReteIVA-2.1.gc'),
'code',
'name')) .update(
CodeList(
path_for_codelist(
'TarifaImpuestoReteRenta-2.1.gc'),
'code',
'name'))
CodigoPrecioReferencia = CodeList(path_for_codelist(
'CodigoPrecioReferencia-2.1.gc'), 'code', 'name')
TipoOrganizacion = CodeList(path_for_codelist('TipoOrganizacion-2.1.gc'), 'code', 'name')
TipoResponsabilidad = CodeList(path_for_codelist('TipoResponsabilidad-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TipoResponsabilidad-2.1.custom.gc'), 'code', 'name'))
TipoAmbiente = CodeList(path_for_codelist('TipoAmbiente-2.1.gc'), 'code', 'name')
TipoDocumento = CodeList(path_for_codelist('TipoDocumento-2.1.gc'), 'code', 'name')
TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TipoImpuesto-2.1.custom.gc'), 'code', 'name'))
TarifaImpuesto = CodeList(path_for_codelist('TarifaImpuestoINC-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TarifaImpuestoIVA-2.1.gc'), 'code', 'name'))\
.update(CodeList(path_for_codelist('TarifaImpuestoReteIVA-2.1.gc'), 'code', 'name'))\
.update(CodeList(path_for_codelist('TarifaImpuestoReteRenta-2.1.gc'), 'code', 'name'))
CodigoPrecioReferencia = CodeList(path_for_codelist('CodigoPrecioReferencia-2.1.gc'), 'code', 'name')
MediosPago = CodeList(path_for_codelist('MediosPago-2.1.gc'), 'code', 'name')
FormasPago = CodeList(path_for_codelist('FormasPago-2.1.gc'), 'code', 'name')
RegimenFiscal = CodeList(path_for_codelist(
'RegimenFiscal-2.1.custom.gc'), 'code', 'name')
TipoOperacionNC = CodeList(path_for_codelist(
'TipoOperacionNC-2.1.gc'), 'code', 'name')
TipoOperacionNCDS = CodeList(path_for_codelist(
'TipoOperacionNCDS-2.1.gc'), 'code', 'name')
TipoOperacionND = CodeList(path_for_codelist(
'TipoOperacionND-2.1 - copia.gc'), 'code', 'name')
TipoOperacionF = CodeList(
path_for_codelist('TipoOperacionF-2.1.gc'),
'code',
'name') .update(
CodeList(
path_for_codelist('TipoOperacionF-2.1.custom.gc'),
'code',
'name'))
RegimenFiscal = CodeList(path_for_codelist('RegimenFiscal-2.1.custom.gc'), 'code', 'name')
TipoOperacionNC = CodeList(path_for_codelist('TipoOperacionNC-2.1.gc'), 'code', 'name')
TipoOperacionNCDS = CodeList(path_for_codelist('TipoOperacionNCDS-2.1.gc'), 'code', 'name')
TipoOperacionND = CodeList(path_for_codelist('TipoOperacionND-2.1 - copia.gc'), 'code', 'name')
TipoOperacionF = CodeList(path_for_codelist('TipoOperacionF-2.1.gc'), 'code', 'name')\
.update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name'))
Municipio = CodeList(path_for_codelist('Municipio-2.1.gc'), 'code', 'name')
Departamento = CodeList(path_for_codelist(
'Departamentos-2.1.gc'), 'code', 'name')
Departamento = CodeList(path_for_codelist('Departamentos-2.1.gc'), 'code', 'name')
Paises = CodeList(path_for_codelist('Paises-2.1.gc'), 'code', 'name')
TipoIdFiscal = CodeList(path_for_codelist(
'TipoIdFiscal-2.1.gc'), 'code', 'name')
CodigoDescuento = CodeList(path_for_codelist(
'CodigoDescuento-2.1.gc'), 'code', 'name')
UnidadesMedida = CodeList(path_for_codelist(
'UnidadesMedida-2.1.gc'), 'code', 'name')
TipoTrabajador = CodeList(path_for_codelist(
'TipoTrabajador-2.1.gc'), 'code', 'name')
SubTipoTrabajador = CodeList(path_for_codelist(
'SubTipoTrabajador-2.1.gc'), 'code', 'name')
TipoContrato = CodeList(path_for_codelist(
'TipoContrato-2.1.gc'), 'code', 'name')
PeriodoNomina = CodeList(path_for_codelist(
'PeriodoNomina-2.1.gc'), 'code', 'name')
TipoIdFiscal = CodeList(path_for_codelist('TipoIdFiscal-2.1.gc'), 'code', 'name')
CodigoDescuento = CodeList(path_for_codelist('CodigoDescuento-2.1.gc'), 'code', 'name')
UnidadesMedida = CodeList(path_for_codelist('UnidadesMedida-2.1.gc'), 'code', 'name')
TipoTrabajador = CodeList(path_for_codelist('TipoTrabajador-2.1.gc'), 'code', 'name')
SubTipoTrabajador = CodeList(path_for_codelist('SubTipoTrabajador-2.1.gc'), 'code', 'name')
TipoContrato = CodeList(path_for_codelist('TipoContrato-2.1.gc'), 'code', 'name')
PeriodoNomina = CodeList(path_for_codelist('PeriodoNomina-2.1.gc'), 'code', 'name')
TipoMoneda = CodeList(path_for_codelist('TipoMoneda-2.1.gc'), 'code', 'name')
IdiomaISO6391 = CodeList(path_for_codelist(
'Idioma-2.1.gc'), 'iso-639-1', 'name')
IdiomaISO6391 = CodeList(path_for_codelist('Idioma-2.1.gc'), 'iso-639-1', 'name')

View File

@@ -1,81 +1,51 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder
import uuid
import xmlsig
import xades
from datetime import datetime
import OpenSSL
import zipfile
# import warnings
import warnings
import hashlib
from contextlib import contextmanager
from .data.dian import codelist
from . import form
from collections import defaultdict
# from pathlib import Path
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
from pathlib import Path
AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code']
AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code']
SCHEME_AGENCY_ATTRS = {
'schemeAgencyName': 'CO, DIAN (Dirección de Impuestos y Aduanas'
' Nacionales)',
'schemeAgencyID': '195'}
'schemeAgencyName': 'CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)',
'schemeAgencyID': '195'
}
# RESOLUCION 0001: pagina 516
POLICY_ID = (
'https://facturaelectronica.dian.gov.co/politicadefirma/v2'
'/politicadefirmav2.pdf'
)
POLICY_NAME = (
u'Política de firma para facturas electrónicas de la República de '
u'Colombia.'
)
POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf'
POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.'
Bogota = tz.gettz('America/Bogota')
# NAMESPACES = {
# 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
# 'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual',
# 'nominaajuste': 'dian:gov:co:facturaelectronica:'
# 'NominaIndividualDeAjuste',
# 'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste',
# 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
# 'xs': 'http://www.w3.org/2001/XMLSchema-instance',
# 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:'
# 'CommonAggregateComponents-2',
# 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:'
# 'CommonBasicComponents-2',
# 'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:'
# 'schema:xsd:DocumentInformationAggregateComponents-1',
# 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
# 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
# 'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1',
# 'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001',
# 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:'
# 'IANAMIMEMediaType:2003',
# 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:'
# 'CommonExtensionComponents-2',
# 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:'
# 'QualifiedDatatypes-2',
# 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003',
# 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2',
# 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2',
# 'sts': 'dian:gov:co:facturaelectronica:Structures-2-1',
# 'udt': 'urn:un:unece:uncefact:data:specification:'
# 'UnqualifiedDataTypesSchemaModule:2',
# 'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
# 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
# 'xades': 'http://uri.etsi.org/01903/v1.3.2#',
# 'xades141': 'http://uri.etsi.org/01903/v1.4.1#',
@@ -86,30 +56,23 @@ Bogota = tz.gettz('America/Bogota')
NAMESPACES = {
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
'cac': (
'urn:oasis:names:specification:ubl:schema:xsd'
':CommonAggregateComponents-2'),
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd'
':CommonBasicComponents-2',
'ext': (
'urn:oasis:names:specification:ubl:schema:xsd'
':CommonExtensionComponents-2'),
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2',
'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2',
'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2',
'sts': 'dian:gov:co:facturaelectronica:Structures-2-1',
'udt': (
'urn:un:unece:uncefact:data:specification'
':UnqualifiedDataTypesSchemaModule:2'),
'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'ds': 'http://www.w3.org/2000/09/xmldsig#',
'xades': 'http://uri.etsi.org/01903/v1.3.2#',
}
def fe_from_string(document: str) -> FachoXML:
return FeXML.from_string(document)
# from contextlib import contextmanager
from contextlib import contextmanager
@contextmanager
def mock_xades_policy():
from mock import patch
@@ -140,25 +103,23 @@ class FeXML(FachoXML):
return super().from_string(document, namespaces=NAMESPACES)
def tostring(self, **kw):
# MACHETE(bit4bit) la DIAN espera que la etiqueta raiz no este en un
# namespace
# MACHETE(bit4bit) la DIAN espera que la etiqueta raiz no este en un namespace
root_namespace = self.root_namespace()
root_localname = self.root_localname()
xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace]
if root_localname == 'Invoice':
urn_oasis = (
'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2')
urn_oasis = 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'
if root_localname == 'CreditNote':
urn_oasis = (
'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2')
urn_oasis = 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'
return super().tostring(**kw)\
.replace(xmlns_name + ':', '')\
.replace('xmlns:' + xmlns_name, 'xmlns')\
.replace(root_namespace, urn_oasis)
.replace(xmlns_name + ':', '')\
.replace('xmlns:'+xmlns_name, 'xmlns')\
.replace(root_namespace, urn_oasis)
class DianXMLExtensionCUDFE(FachoXMLExtension):
def __init__(self, invoice, tipo_ambiente=AMBIENTE_PRUEBAS):
def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.invoice = invoice
@@ -173,12 +134,9 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
def _get_qrcode(self, cufe):
url_for = {
AMBIENTE_PRUEBAS: (
'https://catalogo-vpfe-hab.dian.gov.co/document/'
'searchqr?documentkey='),
AMBIENTE_PRODUCCION: (
'https://catalogo-vpfe.dian.gov.co/document/'
'searchqr?documentkey=')}
AMBIENTE_PRUEBAS: 'https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=',
AMBIENTE_PRODUCCION: 'https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey='
}
return url_for[self.tipo_ambiente] + cufe
def build(self, fachoxml):
@@ -189,30 +147,20 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
if self.schemeName() == "CUDS-SHA384":
if fachoxml.tag_document() == 'Invoice':
fachoxml.set_element(
'./cbc:ProfileID',
'DIAN 2.1: documento soporte en adquisiciones '
'efectuadas a no obligados a facturar.')
fachoxml.set_element('./cbc:ProfileID',
'DIAN 2.1: documento soporte en adquisiciones efectuadas a no obligados a facturar.')
else:
fachoxml.set_element(
'./cbc:ProfileID',
'DIAN 2.1: Nota de ajuste al documento soporte en '
'adquisiciones efectuadas a sujetos no obligados a '
'expedir factura o documento equivalente')
fachoxml.set_element('./cbc:ProfileID',
'DIAN 2.1: Nota de ajuste al documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente')
else:
fachoxml.set_element(
'./cbc:ProfileID',
'DIAN 2.1: Factura Electrónica de Venta')
fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta')
# fachoxml.set_element('./cbc:ProfileID',
# 'DIAN 2.1: Factura Electrónica de Venta')
fachoxml.set_element(
'./cbc:ProfileExecutionID', self._tipo_ambiente_int())
# DIAN 1.7.-2020: FAB36
fachoxml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:QRCode',
self._get_qrcode(cufe))
# #DIAN 1.8.-2021: FAD03
# fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta')
fachoxml.set_element('./cbc:ProfileExecutionID', self._tipo_ambiente_int())
#DIAN 1.7.-2020: FAB36
fachoxml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode',
self._get_qrcode(cufe))
def issue_time(self, datetime_):
return datetime_.strftime('%H:%M:%S-05:00')
@@ -227,10 +175,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
build_vars['FecFac'] = self.issue_date(invoice.invoice_issue)
build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue)
# PAG 601
build_vars['ValorBruto'] = (
invoice.invoice_legal_monetary_total.line_extension_amount)
build_vars['ValorTotalPagar'
] = invoice.invoice_legal_monetary_total.payable_amount
build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount
build_vars['ValorTotalPagar'] = invoice.invoice_legal_monetary_total.payable_amount
ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0))
build_vars['CodImpuesto1'] = '01'
build_vars['CodImpuesto2'] = '04'
@@ -240,8 +186,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
if subtotal.scheme is not None:
# TODO cual es la naturaleza de tax_scheme_ident?
codigo_impuesto = subtotal.scheme.code
ValorImpuestoPara.setdefault(
codigo_impuesto, form.Amount(0.0))
ValorImpuestoPara.setdefault(codigo_impuesto, form.Amount(0.0))
ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount
build_vars['ValorImpuestoPara'] = ValorImpuestoPara
@@ -261,8 +206,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
class DianXMLExtensionCUFE(DianXMLExtensionCUDFE):
def __init__(
self, invoice, clave_tecnica='', tipo_ambiente=AMBIENTE_PRUEBAS):
def __init__(self, invoice, clave_tecnica = '', tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.clave_tecnica = clave_tecnica
self.invoice = invoice
@@ -281,40 +225,25 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE):
CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3']
return [
'%s' %
build_vars['NumFac'],
'%s' %
build_vars['FecFac'],
'%s' %
build_vars['HoraFac'],
form.Amount(
build_vars['ValorBruto']).truncate_as_string(2),
'%s' % build_vars['NumFac'],
'%s' % build_vars['FecFac'],
'%s' % build_vars['HoraFac'],
form.Amount(build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1,
build_vars['ValorImpuestoPara'].get(
CodImpuesto1,
form.Amount(0.0)).truncate_as_string(2),
build_vars['ValorImpuestoPara'].get(CodImpuesto1, form.Amount(0.0)).truncate_as_string(2),
CodImpuesto2,
build_vars['ValorImpuestoPara'].get(
CodImpuesto2,
form.Amount(0.0)).truncate_as_string(2),
build_vars['ValorImpuestoPara'].get(CodImpuesto2, form.Amount(0.0)).truncate_as_string(2),
CodImpuesto3,
build_vars['ValorImpuestoPara'].get(
CodImpuesto3,
form.Amount(0.0)).truncate_as_string(2),
build_vars['ValorImpuestoPara'].get(CodImpuesto3, form.Amount(0.0)).truncate_as_string(2),
build_vars['ValorTotalPagar'].truncate_as_string(2),
'%s' %
build_vars['NitOFE'],
'%s' %
build_vars['NumAdq'],
'%s' %
build_vars['ClTec'],
'%d' %
build_vars['TipoAmb'],
'%s' % build_vars['NitOFE'],
'%s' % build_vars['NumAdq'],
'%s' % build_vars['ClTec'],
'%d' % build_vars['TipoAmb'],
]
class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
def __init__(self, invoice, software_pin, tipo_ambiente=AMBIENTE_PRUEBAS):
def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.software_pin = software_pin
self.invoice = invoice
@@ -333,44 +262,25 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3']
return [
'%s' %
build_vars['NumFac'],
'%s' %
build_vars['FecFac'],
'%s' %
build_vars['HoraFac'],
form.Amount(
build_vars['ValorBruto']).truncate_as_string(2),
'%s' % build_vars['NumFac'],
'%s' % build_vars['FecFac'],
'%s' % build_vars['HoraFac'],
form.Amount(build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1,
form.Amount(
build_vars['ValorImpuestoPara'].get(
CodImpuesto1,
0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2),
CodImpuesto2,
form.Amount(
build_vars['ValorImpuestoPara'].get(
CodImpuesto2,
0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto2, 0.0)).truncate_as_string(2),
CodImpuesto3,
form.Amount(
build_vars['ValorImpuestoPara'].get(
CodImpuesto3,
0.0)).truncate_as_string(2),
form.Amount(
build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' %
build_vars['NitOFE'],
'%s' %
build_vars['NumAdq'],
'%s' %
build_vars['Software-PIN'],
'%d' %
build_vars['TipoAmb'],
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' % build_vars['NitOFE'],
'%s' % build_vars['NumAdq'],
'%s' % build_vars['Software-PIN'],
'%d' % build_vars['TipoAmb'],
]
class DianXMLExtensionCUDS(DianXMLExtensionCUDFE):
def __init__(self, invoice, software_pin, tipo_ambiente=AMBIENTE_PRUEBAS):
def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS):
self.tipo_ambiente = tipo_ambiente
self.software_pin = software_pin
self.invoice = invoice
@@ -386,33 +296,22 @@ class DianXMLExtensionCUDS(DianXMLExtensionCUDFE):
def formatVars(self):
build_vars = self.buildVars()
CodImpuesto1 = build_vars['CodImpuesto1']
CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3']
return [
'%s' %
build_vars['NumFac'],
'%s' %
build_vars['FecFac'],
'%s' %
build_vars['HoraFac'],
form.Amount(
build_vars['ValorBruto']).truncate_as_string(2),
'%s' % build_vars['NumFac'],
'%s' % build_vars['FecFac'],
'%s' % build_vars['HoraFac'],
form.Amount(build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1,
form.Amount(
build_vars['ValorImpuestoPara'].get(
CodImpuesto1,
0.0)).truncate_as_string(2),
form.Amount(
build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' %
build_vars['NitOFE'],
'%s' %
build_vars['NumAdq'],
'%s' %
build_vars['Software-PIN'],
'%d' %
build_vars['TipoAmb'],
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2),
form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' % build_vars['NitOFE'],
'%s' % build_vars['NumAdq'],
'%s' % build_vars['Software-PIN'],
'%d' % build_vars['TipoAmb'],
]
class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
# RESOLUCION 0004: pagina 108
@@ -422,21 +321,15 @@ class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
self.id_software = id_software
def build(self, fexml):
software_provider = fexml.fragment(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareProvider')
software_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider')
provider_id_attrs = SCHEME_AGENCY_ATTRS.copy()
provider_id_attrs.update({'schemeID': self.dv})
# DIAN 1.7.-2020: FAB23
#DIAN 1.7.-2020: FAB23
provider_id_attrs.update({'schemeName': '31'})
software_provider.set_element(
'/sts:SoftwareProvider/sts:ProviderID',
self.nit,
**provider_id_attrs)
software_provider.set_element(
'/sts:SoftwareProvider/sts:SoftwareID',
self.id_software,
**SCHEME_AGENCY_ATTRS)
software_provider.set_element('/sts:SoftwareProvider/sts:ProviderID', self.nit,
**provider_id_attrs)
software_provider.set_element('/sts:SoftwareProvider/sts:SoftwareID', self.id_software,
**SCHEME_AGENCY_ATTRS)
class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension):
@@ -448,10 +341,7 @@ class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension):
self.invoice_ident = invoice_ident
def build(self, fexml):
dian_path = (
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareSecurityCode'
)
dian_path = './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode'
code = str(self.id_software) + str(self.pin) + str(self.invoice_ident)
m = hashlib.sha384()
m.update(code.encode('utf-8'))
@@ -461,6 +351,7 @@ class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension):
class DianXMLExtensionSigner:
def __init__(self, pkcs12_path, passphrase=None, localpolicy=True):
self._pkcs12_data = open(pkcs12_path, 'rb').read()
self._passphrase = None
@@ -471,6 +362,7 @@ class DianXMLExtensionSigner:
@classmethod
def from_bytes(cls, data, passphrase=None, localpolicy=True):
self = cls.__new__(cls)
self._pkcs12_data = data
self._passphrase = None
self._localpolicy = localpolicy
@@ -480,16 +372,14 @@ class DianXMLExtensionSigner:
return self
def _element_extension_content(self, fachoxml):
return fachoxml.builder.xpath(
fachoxml.root,
'./ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent')
return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent')
def sign_xml_string(self, document):
xml = LXMLBuilder.from_string(document)
signature = self.sign_xml_element(xml)
fachoxml = FachoXML(xml, nsmap=NAMESPACES)
# DIAN 1.7.-2020: FAB01
fachoxml = FachoXML(xml,nsmap=NAMESPACES)
#DIAN 1.7.-2020: FAB01
extcontent = self._element_extension_content(fachoxml)
fachoxml.append_element(extcontent, signature)
@@ -504,38 +394,33 @@ class DianXMLExtensionSigner:
)
xml.append(signature)
ref = xmlsig.template.add_reference(
signature,
xmlsig.constants.TransformSha256,
uri="",
name="xmldsig-%s-ref0" %
(id_uuid))
signature, xmlsig.constants.TransformSha256, uri="", name="xmldsig-%s-ref0" % (id_uuid)
)
xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid)
xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="#%s" % (
id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid),
signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid),
)
ki = xmlsig.template.ensure_key_info(signature, name=id_keyinfo)
data = xmlsig.template.add_x509_data(ki)
xmlsig.template.x509_data_add_certificate(data)
xmlsig.template.add_key_value(ki)
qualifying = xades.template.create_qualifying_properties(
signature, 'XadesObjects', 'xades')
qualifying = xades.template.create_qualifying_properties(signature, 'XadesObjects', 'xades')
xades.utils.ensure_id(qualifying)
id_props = "xmldsig-%s-signedprops" % (id_uuid)
props_ref = xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="#%s" %
(id_props), uri_type="http://uri.etsi.org/01903#SignedProperties")
xmlsig.template.add_transform(
props_ref, xmlsig.constants.TransformInclC14N)
signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props),
uri_type="http://uri.etsi.org/01903#SignedProperties"
)
xmlsig.template.add_transform(props_ref, xmlsig.constants.TransformInclC14N)
# TODO assert with http://www.sic.gov.co/hora-legal-colombiana
props = xades.template.create_signed_properties(
qualifying, name=id_props, datetime=datetime.now(tz=Bogota))
props = xades.template.create_signed_properties(qualifying, name=id_props, datetime=datetime.now())
xades.template.add_claimed_role(props, "supplier")
policy = xades.policy.GenericPolicyId(
@@ -543,9 +428,8 @@ class DianXMLExtensionSigner:
POLICY_NAME,
xmlsig.constants.TransformSha256)
ctx = xades.XAdESContext(policy)
ctx.load_pkcs12(pkcs12.load_key_and_certificates(
self._pkcs12_data,
self._passphrase))
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(self._pkcs12_data,
self._passphrase))
if self._localpolicy:
with mock_xades_policy():
@@ -554,7 +438,7 @@ class DianXMLExtensionSigner:
else:
ctx.sign(signature)
ctx.verify(signature)
# xmlsig take parent root
#xmlsig take parent root
xml.remove(signature)
return signature
@@ -570,28 +454,22 @@ class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
def build(self, fexml):
attrs = {'schemeID': '4', 'schemeName': '31'}
attrs.update(SCHEME_AGENCY_ATTRS)
authorization_provider = fexml.fragment(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:AuthorizationProvider')
authorization_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider')
authorization_provider.set_element('./sts:AuthorizationProviderID',
'800197268',
**attrs)
class DianXMLExtensionInvoiceSource(FachoXMLExtension):
# CAB13
def build(self, fexml):
dian_path = (
'/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionCo'
'ntent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode'
)
fexml.set_element(
dian_path, 'CO',
listAgencyID="6",
listAgencyName="United Nations Economic Commission for Europe",
listSchemeURI=(
"urn:oasis:names:specification:ubl:codelist:gc:"
"CountryIdentificationCode-2.1"))
dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode'
fexml.set_element(dian_path, 'CO',
listAgencyID="6",
listAgencyName="United Nations Economic Commission for Europe",
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1")
class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
@@ -608,38 +486,29 @@ class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
self.to = to
def build(self, fexml):
invoice_control = fexml.fragment(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceControl')
invoice_control.set_element(
'/sts:InvoiceControl/sts:InvoiceAuthorization',
self.authorization)
invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:StartDate',
self.period_startdate.strftime('%Y-%m-%d'))
invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:EndDate',
self.period_enddate.strftime('%Y-%m-%d'))
invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizedInvoices/sts:Prefix',
self.prefix)
invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizedInvoices/sts:From', self.from_)
invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To', self.to)
invoice_control = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl')
invoice_control.set_element('/sts:InvoiceControl/sts:InvoiceAuthorization', self.authorization)
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:StartDate',
self.period_startdate.strftime('%Y-%m-%d'))
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:EndDate',
self.period_enddate.strftime('%Y-%m-%d'))
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:Prefix',
self.prefix)
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:From',
self.from_)
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To',
self.to)
fexml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode',
'CO',
#DIAN 1.7.-2020: FAB15
listAgencyID="6",
#DIAN 1.7.-2020: FAB16
listAgencyName="United Nations Economic Commission for Europe",
#DIAN 1.7.-2020: FAB17
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1"
)
fexml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceSource/cbc:IdentificationCode',
'CO',
# DIAN 1.7.-2020: FAB15
listAgencyID="6",
# DIAN 1.7.-2020: FAB16
listAgencyName="United Nations Economic Commission for Europe",
# DIAN 1.7.-2020: FAB17
listSchemeURI=(
"urn:oasis:names:specification:ubl:codelist:gc:"
"CountryIdentificationCode-2.1"))
class DianZIP:
@@ -648,8 +517,7 @@ class DianZIP:
MAX_FILES = 50
def __init__(self, file_like):
self.zipfile = zipfile.ZipFile(
file_like, mode='w', compression=zipfile.ZIP_DEFLATED)
self.zipfile = zipfile.ZipFile(file_like, mode='w', compression=zipfile.ZIP_DEFLATED)
self.num_files = 0
def add_xml(self, name, xml_data):
@@ -670,6 +538,7 @@ class DianZIP:
def __enter__(self):
"""
Facilita el uso de esta manera:
f = open('xxx', 'rb')
with DianZIP(f) as zip:
zip.add_invoice_xml('name', 'data xml')
@@ -682,11 +551,7 @@ class DianZIP:
class DianXMLExtensionSignerVerifier:
def __init__(
self,
pkcs12_path_or_bytes,
passphrase=None,
localpolicy=True):
def __init__(self, pkcs12_path_or_bytes, passphrase=None, localpolicy=True):
self._pkcs12_path_or_bytes = pkcs12_path_or_bytes
self._passphrase = None
self._localpolicy = localpolicy
@@ -696,7 +561,7 @@ class DianXMLExtensionSignerVerifier:
def verify_string(self, document):
# Obtener FachoXML
xml = LXMLBuilder.from_string(document)
fachoxml = FachoXML(xml, nsmap=NAMESPACES)
fachoxml = FachoXML(xml,nsmap=NAMESPACES)
# Obtener Signature
signature = fachoxml.builder.xpath(fachoxml.root, '//ds:Signature')
@@ -711,8 +576,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(pkcs12.load_key_and_certificates(
pkcs12_data, self._passphrase))
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(pkcs12_data,
self._passphrase))
try:
if self._localpolicy:
with mock_xades_policy():
@@ -720,5 +585,5 @@ class DianXMLExtensionSignerVerifier:
else:
ctx.verify(signature)
return True
except BaseException:
except:
return False

View File

@@ -1,27 +1,22 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
# import hashlib
# from functools import reduce
# import copy
from __future__ import annotations
import hashlib
from functools import reduce
import copy
import dataclasses
from dataclasses import dataclass, field
from datetime import datetime
# from collections import defaultdict
from dataclasses import dataclass
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
class AmountCurrencyError(TypeError):
pass
@dataclass
class Currency:
code: str
@@ -32,7 +27,6 @@ class Currency:
def __str__(self):
return self.code
class Collection:
def __init__(self, array):
@@ -49,7 +43,6 @@ class Collection:
def sum(self):
return sum(self.array)
class AmountCollection(Collection):
def sum(self):
@@ -58,13 +51,10 @@ class AmountCollection(Collection):
total += v
return total
class Amount:
def __init__(
self, amount: int | float | str | "Amount",
currency: Currency = Currency('COP')):
def __init__(self, amount: int or float or str or Amount, currency: Currency = Currency('COP')):
# DIAN 1.7.-2020: 1.2.3.1
#DIAN 1.7.-2020: 1.2.3.1
if isinstance(amount, Amount):
if amount < Amount(0.0):
raise ValueError('amount must be positive >= 0')
@@ -75,11 +65,9 @@ class Amount:
if float(amount) < 0:
raise ValueError('amount must be positive >= 0')
self.amount = Decimal(
amount, decimal.Context(
prec=DECIMAL_PRECISION,
# DIAN 1.7.-2020: 1.2.1.1
rounding=decimal.ROUND_HALF_EVEN))
self.amount = Decimal(amount, decimal.Context(prec=DECIMAL_PRECISION,
#DIAN 1.7.-2020: 1.2.1.1
rounding=decimal.ROUND_HALF_EVEN ))
self.currency = currency
def fromNumber(self, val):
@@ -102,8 +90,7 @@ class Amount:
def __eq__(self, other):
if not self.is_same_currency(other):
raise AmountCurrencyError()
return round(self.amount, DECIMAL_PRECISION) == round(
other.amount, DECIMAL_PRECISION)
return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION)
def _cast(self, val):
if type(val) in [int, float]:
@@ -135,7 +122,7 @@ class Amount:
def truncate_as_string(self, prec):
parts = str(self.float()).split('.', 1)
return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec, '0'))
return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec,'0'))
def float(self):
return float(round(self.amount, DECIMAL_PRECISION))
@@ -164,7 +151,6 @@ class Quantity:
def __repr__(self):
return str(self)
@dataclass
class Item:
scheme_name: str
@@ -202,7 +188,6 @@ class Country:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Paises[self.code]['name']
@dataclass
class CountrySubentity:
code: str
@@ -213,7 +198,6 @@ class CountrySubentity:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Departamento[self.code]['name']
@dataclass
class City:
code: str
@@ -224,22 +208,18 @@ class City:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.Municipio[self.code]['name']
@dataclass
class PostalZone:
code: str = ''
@dataclass
class Address:
name: str
street: str = ''
city: City = field(default_factory=lambda: City('05001'))
country: Country = field(default_factory=lambda: Country('CO'))
countrysubentity: CountrySubentity = field(
default_factory=lambda: CountrySubentity('05'))
postalzone: PostalZone = field(default_factory=lambda: PostalZone(''))
city: City = City('05001')
country: Country = Country('CO')
countrysubentity: CountrySubentity = CountrySubentity('05')
postalzone: PostalZone = PostalZone('')
@dataclass
class PartyIdentification:
@@ -260,7 +240,6 @@ class PartyIdentification:
if self.type_fiscal not in codelist.TipoIdFiscal:
raise ValueError("type_fiscal [%s] not found" % (self.type_fiscal))
@dataclass
class Responsability:
codes: list
@@ -280,17 +259,27 @@ class Responsability:
raise ValueError("code %s not found" % (code))
@dataclass
class TaxScheme:
code: str
name: str = ''
def __post_init__(self):
if self.code not in codelist.TipoImpuesto:
raise ValueError("code not found")
self.name = codelist.TipoImpuesto[self.code]['name']
@dataclass
class Party:
name: str
ident: str
responsability_code: list[Responsability]
responsability_code: typing.List[Responsability]
responsability_regime_code: str
organization_code: str
tax_scheme: TaxScheme = field(default_factory=lambda: TaxScheme('01'))
tax_scheme: TaxScheme = TaxScheme('01')
phone: str = ''
address: Address = field(default_factory=lambda: Address(''))
address: Address = Address('')
email: str = ''
legal_name: str = ''
legal_company_ident: str = ''
@@ -317,8 +306,8 @@ class TaxScheme:
@dataclass
class TaxSubTotal:
percent: float
scheme: TaxScheme | None = None
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
scheme: typing.Optional[TaxScheme] = None
tax_amount: Amount = Amount(0.0)
def calculate(self, invline):
if self.percent is not None:
@@ -328,8 +317,8 @@ class TaxSubTotal:
@dataclass
class TaxTotal:
subtotals: list
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
taxable_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_amount: Amount = Amount(0.0)
taxable_amount: Amount = Amount(0.0)
def calculate(self, invline):
self.taxable_amount = invline.total_amount
@@ -345,23 +334,21 @@ class TaxTotalOmit(TaxTotal):
def calculate(self, invline):
pass
@dataclass
class WithholdingTaxSubTotal:
percent: float
scheme: TaxScheme | None = None
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
scheme: typing.Optional[TaxScheme] = None
tax_amount: Amount = Amount(0.0)
def calculate(self, invline):
if self.percent is not None:
self.tax_amount = invline.total_amount * Amount(self.percent / 100)
@dataclass
class WithholdingTaxTotal:
subtotals: list
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
taxable_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_amount: Amount = Amount(0.0)
taxable_amount: Amount = Amount(0.0)
def calculate(self, invline):
self.taxable_amount = invline.total_amount
@@ -370,7 +357,6 @@ class WithholdingTaxTotal:
subtax.calculate(invline)
self.tax_amount += subtax.tax_amount
class WithholdingTaxTotalOmit(WithholdingTaxTotal):
def __init__(self):
super().__init__([])
@@ -378,7 +364,6 @@ class WithholdingTaxTotalOmit(WithholdingTaxTotal):
def calculate(self, invline):
pass
@dataclass
class Price:
amount: Amount
@@ -394,7 +379,6 @@ class Price:
self.amount *= self.quantity
@dataclass
class PaymentMean:
DEBIT = '01'
@@ -412,9 +396,8 @@ class PaymentMean:
@dataclass
class PrePaidPayment:
# DIAN 1.7.-2020: FBD03
paid_amount: Amount = field(default_factory=lambda: Amount(0.0))
#DIAN 1.7.-2020: FBD03
paid_amount: Amount = Amount(0.0)
@dataclass
class BillingResponse:
@@ -422,7 +405,6 @@ class BillingResponse:
code: str
description: str
class SupportDocumentCreditNoteResponse(BillingResponse):
"""
ReferenceID: Identifica la sección del Documento
@@ -432,13 +414,13 @@ class SupportDocumentCreditNoteResponse(BillingResponse):
"""
@dataclass
class BillingReference:
ident: str
uuid: str
date: date
class CreditNoteDocumentReference(BillingReference):
"""
ident: Prefijo + Numero de la factura relacionada
@@ -446,7 +428,6 @@ class CreditNoteDocumentReference(BillingReference):
date: fecha de emision de la factura relacionada
"""
class DebitNoteDocumentReference(BillingReference):
"""
ident: Prefijo + Numero de la factura relacionada
@@ -454,7 +435,6 @@ class DebitNoteDocumentReference(BillingReference):
date: fecha de emision de la factura relacionada
"""
class InvoiceDocumentReference(BillingReference):
"""
ident: Prefijo + Numero de la nota credito relacionada
@@ -462,7 +442,6 @@ class InvoiceDocumentReference(BillingReference):
date: fecha de emision de la nota credito relacionada
"""
@dataclass
class AllowanceChargeReason:
code: str
@@ -475,26 +454,22 @@ class AllowanceChargeReason:
@dataclass
class AllowanceCharge:
# DIAN 1.7.-2020: FAQ03
#DIAN 1.7.-2020: FAQ03
charge_indicator: bool = True
amount: Amount = field(default_factory=lambda: Amount(0.0))
amount: Amount = Amount(0.0)
reason: AllowanceChargeReason = None
# Valor Base para calcular el descuento o el cargo
base_amount: Amount | None = field(
default_factory=lambda: Amount(0.0))
#Valor Base para calcular el descuento o el cargo
base_amount: typing.Optional[Amount] = Amount(0.0)
# Porcentaje: Porcentaje que aplicar.
multiplier_factor_numeric: Amount = field(
default_factory=lambda: Amount(1.0))
multiplier_factor_numeric: Amount = Amount(1.0)
def isCharge(self):
charge_indicator = self.charge_indicator is True
return charge_indicator
return self.charge_indicator == True
def isDiscount(self):
charge_indicator = self.charge_indicator is False
return charge_indicator
return self.charge_indicator == False
def asCharge(self):
self.charge_indicator = True
@@ -508,13 +483,11 @@ class AllowanceCharge:
def set_base_amount(self, amount):
self.base_amount = amount
class AllowanceChargeAsDiscount(AllowanceCharge):
def __init__(self, amount: Amount = Amount(0.0)):
self.charge_indicator = False
self.amount = amount
@dataclass
class InvoiceLine:
# RESOLUCION 0004: pagina 155
@@ -526,10 +499,9 @@ class InvoiceLine:
# ya que al reportar los totales es sobre
# la factura y el percent es unico por type_code
# de subtotal
tax: TaxTotal | None
withholding: WithholdingTaxTotal | None
allowance_charge: list[AllowanceCharge] = dataclasses.field(
default_factory=list)
tax: typing.Optional[TaxTotal]
withholding: typing.Optional[WithholdingTaxTotal]
allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list)
def add_allowance_charge(self, charge):
if not isinstance(charge, AllowanceCharge):
@@ -594,19 +566,18 @@ class InvoiceLine:
if self.withholding is None:
self.withholding = WithholdingTaxTotalOmit()
@dataclass
class LegalMonetaryTotal:
line_extension_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_exclusive_amount: Amount = field(default_factory=lambda: Amount(0.0))
tax_inclusive_amount: Amount = field(default_factory=lambda: Amount(0.0))
charge_total_amount: Amount = field(default_factory=lambda: Amount(0.0))
allowance_total_amount: Amount = field(default_factory=lambda: Amount(0.0))
payable_amount: Amount = field(default_factory=lambda: Amount(0.0))
prepaid_amount: Amount = field(default_factory=lambda: Amount(0.0))
line_extension_amount: Amount = Amount(0.0)
tax_exclusive_amount: Amount = Amount(0.0)
tax_inclusive_amount: Amount = Amount(0.0)
charge_total_amount: Amount = Amount(0.0)
allowance_total_amount: Amount = Amount(0.0)
payable_amount: Amount = Amount(0.0)
prepaid_amount: Amount = Amount(0.0)
def calculate(self):
# DIAN 1.7.-2020: FAU14
#DIAN 1.7.-2020: FAU14
self.payable_amount = \
self.tax_inclusive_amount \
+ self.allowance_total_amount \
@@ -614,29 +585,26 @@ class LegalMonetaryTotal:
- self.prepaid_amount
class NationalSalesInvoiceDocumentType(str):
def __str__(self):
# 6.1.3
return '01'
class CreditNoteDocumentType(str):
def __str__(self):
# 6.1.3
return '91'
class DebitNoteDocumentType(str):
def __str__(self):
# 6.1.3
return '92'
class CreditNoteSupportDocumentType(str):
def __str__(self):
return '95'
class Invoice:
def __init__(self, type_code: str):
if str(type_code) not in codelist.TipoDocumento:
@@ -682,8 +650,7 @@ class Invoice:
if len(prefix) <= 4:
self.invoice_ident_prefix = prefix
else:
raise ValueError(
'ident prefix failed to get, expected 0 to 4 chars')
raise ValueError('ident prefix failed to get, expected 0 to 4 chars')
def set_ident(self, ident: str):
"""
@@ -736,6 +703,7 @@ class Invoice:
def set_discrepancy_response(self, billing_response: BillingResponse):
self.invoice_discrepancy_response = billing_response
def accept(self, visitor):
visitor.visit_payment_mean(self.invoice_payment_mean)
visitor.visit_customer(self.invoice_customer)
@@ -747,34 +715,29 @@ class Invoice:
def _calculate_legal_monetary_total(self):
for invline in self.invoice_lines:
self.invoice_legal_monetary_total.line_extension_amount +=\
invline.total_amount
self.invoice_legal_monetary_total.tax_exclusive_amount +=\
invline.total_tax_exclusive_amount
# DIAN 1.7.-2020: FAU6
self.invoice_legal_monetary_total.tax_inclusive_amount +=\
invline.total_tax_inclusive_amount
self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount
self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_tax_exclusive_amount
#DIAN 1.7.-2020: FAU6
self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount
# DIAN 1.7.-2020: FAU08
self.invoice_legal_monetary_total.allowance_total_amount =\
AmountCollection(self.invoice_allowance_charge)\
#DIAN 1.7.-2020: FAU08
self.invoice_legal_monetary_total.allowance_total_amount = AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isDiscount())\
.map(lambda charge: charge.amount)\
.sum()
# DIAN 1.7.-2020: FAU10
self.invoice_legal_monetary_total.charge_total_amount =\
AmountCollection(self.invoice_allowance_charge)\
#DIAN 1.7.-2020: FAU10
self.invoice_legal_monetary_total.charge_total_amount = AmountCollection(self.invoice_allowance_charge)\
.filter(lambda charge: charge.isCharge())\
.map(lambda charge: charge.amount)\
.sum()
# DIAN 1.7.-2020: FAU12
self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(
self.invoice_prepaid_payment).map(
lambda paid: paid.paid_amount).sum()
#DIAN 1.7.-2020: FAU12
self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(self.invoice_prepaid_payment)\
.map(lambda paid: paid.paid_amount)\
.sum()
# DIAN 1.7.-2020: FAU14
#DIAN 1.7.-2020: FAU14
self.invoice_legal_monetary_total.calculate()
def _refresh_charges_base_amount(self):
@@ -782,13 +745,11 @@ class Invoice:
for invline in self.invoice_lines:
if invline.allowance_charge:
# TODO actualmente solo uno de los cargos es permitido
raise ValueError(
'allowance charge in invoice exclude invoice line')
raise ValueError('allowance charge in invoice exclude invoice line')
# cargos a nivel de factura
for charge in self.invoice_allowance_charge:
charge.set_base_amount(
self.invoice_legal_monetary_total.line_extension_amount)
charge.set_base_amount(self.invoice_legal_monetary_total.line_extension_amount)
def calculate(self):
for invline in self.invoice_lines:
@@ -796,7 +757,6 @@ class Invoice:
self._calculate_legal_monetary_total()
self._refresh_charges_base_amount()
class NationalSalesInvoice(Invoice):
def __init__(self):
super().__init__(NationalSalesInvoiceDocumentType())
@@ -841,15 +801,12 @@ class DebitNote(Invoice):
if not self.invoice_ident_prefix:
self.invoice_ident_prefix = self.invoice_ident[0:6]
class SupportDocument(Invoice):
pass
class SupportDocumentCreditNote(SupportDocument):
def __init__(
self, invoice_document_reference: BillingReference,
invoice_discrepancy_response: BillingResponse):
def __init__(self, invoice_document_reference: BillingReference,
invoice_discrepancy_response: BillingResponse):
super().__init__(CreditNoteSupportDocumentType())
if not isinstance(invoice_document_reference, BillingReference):

View File

@@ -6,10 +6,7 @@ from .. import form
from ..fe import fe_from_string
from datetime import datetime
def billing_reference(
xmldocument: str,
klass: form.BillingReference) -> form.BillingReference:
def billing_reference(xmldocument: str, klass: form.BillingReference) -> form.BillingReference:
"""
construye BillingReference desde XMLDOCUMENT
usando KLASS como clase.

View File

@@ -1,18 +1,7 @@
from .invoice import DIANInvoiceXML
from .credit_note import DIANCreditNoteXML
from .debit_note import DIANDebitNoteXML
from .utils import DIANWrite, DIANWriteSigned
from .attached_document import AttachedDocument
from .support_document import DIANSupportDocumentXML
from .support_document_credit_note import DIANSupportDocumentCreditNoteXML
__all__ = [
'DIANInvoiceXML',
'DIANCreditNoteXML',
'DIANDebitNoteXML',
'DIANWrite',
'DIANWriteSigned',
'AttachedDocument',
'DIANSupportDocumentXML',
'DIANSupportDocumentCreditNoteXML',
]
from .invoice import *
from .credit_note import *
from .debit_note import *
from .utils import *
from .attached_document import *
from .support_document import *
from .support_document_credit_note import *

View File

@@ -2,14 +2,13 @@ from .. import fe
__all__ = ['AttachedDocument']
class AttachedDocument():
def __init__(self, id):
schema =\
'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2'
schema = 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2'
self.fexml = fe.FeXML('AttachedDocument', schema)
self.fexml.set_element('./cbc:ID', id)
def toFachoXML(self):
return self.fexml

View File

@@ -1,10 +1,9 @@
# from .. import fe
# from ..form import *
from .. import fe
from ..form import *
from .invoice import DIANInvoiceXML
__all__ = ['DIANCreditNoteXML']
class DIANCreditNoteXML(DIANInvoiceXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun

View File

@@ -1,10 +1,9 @@
# from .. import fe
# from ..form import *
from .. import fe
from ..form import *
from .invoice import DIANInvoiceXML
__all__ = ['DIANDebitNoteXML']
class DIANDebitNoteXML(DIANInvoiceXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
@@ -20,24 +19,19 @@ class DIANDebitNoteXML(DIANInvoiceXML):
def tag_document_concilied(fexml):
return 'Debited'
# DIAN 1.7.-2020: DAU03
#DIAN 1.7.-2020: DAU03
def set_legal_monetary(fexml, invoice):
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount(
'./cac:RequestedMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)
fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)

File diff suppressed because it is too large Load Diff

View File

@@ -1,294 +1,219 @@
from .. import fe
from ..form import (
Amount, DebitNoteDocumentReference, CreditNoteDocumentReference,
InvoiceDocumentReference, TaxTotalOmit, WithholdingTaxTotalOmit
)
from collections import defaultdict
from datetime import datetime
# from .attached_document import *
from ..form import *
from datetime import datetime, date
from .attached_document import *
__all__ = ['DIANSupportDocumentXML']
class DIANSupportDocumentXML(fe.FeXML):
"""
DianSupportDocumentXML mapea objeto form.Invoice a XML segun
lo indicado para él Documento soporte en adquisiciones efectuadas con
sujetos no obligados a expedir factura de venta o documento equivalente.
lo indicado para él Documento soporte en adquisiciones efectuadas con sujetos no obligados a expedir factura de venta o documento equivalente.
"""
def __init__(self, invoice, tag_document='Invoice'):
super().__init__(
tag_document,
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
def __init__(self, invoice, tag_document = 'Invoice'):
super().__init__(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
# DIAN 1.1.-2021: DSAB03
# DIAN 1.1.-2021: NSAB03
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceControl')
#DIAN 1.1.-2021: DSAB03
#DIAN 1.1.-2021: NSAB03
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl')
# DIAN 1.1.-2021: DSAB13
# DIAN 1.1.-2021: NSAB13
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceSource')
#DIAN 1.1.-2021: DSAB13
#DIAN 1.1.-2021: NSAB13
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource')
# DIAN 1.1.-2021: DSAB18
# DIAN 1.1.-2021: NSAB18
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareProvider')
#DIAN 1.1.-2021: DSAB18
#DIAN 1.1.-2021: NSAB18
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider')
# DIAN 1.1.-2021: DSAB27
# DIAN 1.1.-2021: NSAB27
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareSecurityCode')
#DIAN 1.1.-2021: DSAB27
#DIAN 1.1.-2021: NSAB27
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode')
# DIAN 1.1.-2021: DSAB30 DSAB31
# DIAN 1.1.-2021: NSAB30 NSAB31
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/'
'sts:DianExtensions/sts:AuthorizationProvider/'
'sts:AuthorizationProviderID')
#DIAN 1.1.-2021: DSAB30 DSAB31
#DIAN 1.1.-2021: NSAB30 NSAB31
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID')
# ZE02 se requiere existencia para firmar
# DIAN 1.1.-2021: DSAA02 DSAB01
# DIAN 1.1.-2021: NSAA02 NSAB01
ublextension = self.fragment(
'./ext:UBLExtensions/ext:UBLExtension', append=True)
# DIAN 1.1.-2021: DSAB02
# DIAN 1.1.-2021: NSAB02
ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
#ZE02 se requiere existencia para firmar
#DIAN 1.1.-2021: DSAA02 DSAB01
#DIAN 1.1.-2021: NSAA02 NSAB01
ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True)
#DIAN 1.1.-2021: DSAB02
#DIAN 1.1.-2021: NSAB02
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice)
def set_supplier(fexml, invoice):
# DIAN 1.1.-2021: DSAJ01
# DIAN 1.1.-2021: NSAB01
#DIAN 1.1.-2021: DSAJ01
#DIAN 1.1.-2021: NSAB01
fexml.placeholder_for('./cac:AccountingSupplierParty')
# DIAN 1.1.-2021: DSAJ02
# DIAN 1.1.-2021: NSAJ02
fexml.set_element(
'./cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code)
#DIAN 1.1.-2021: DSAJ02
#DIAN 1.1.-2021: NSAJ02
fexml.set_element('./cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code)
# DIAN 1.1.-2021: DSAJ07 DSAJ08
# DIAN 1.1.-2021: NSAJ07 NSAJ08
fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address')
#DIAN 1.1.-2021: DSAJ07 DSAJ08
#DIAN 1.1.-2021: NSAJ07 NSAJ08
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address')
# DIAN 1.1.-2021: DSAJ09
# DIAN 1.1.-2021: NSAJ09
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:ID',
invoice.invoice_supplier.address.city.code)
#DIAN 1.1.-2021: DSAJ09
#DIAN 1.1.-2021: NSAJ09
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID',
invoice.invoice_supplier.address.city.code)
# DIAN 1.1.-2021: DSAJ10
# DIAN 1.1.-2021: NSAJ10
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CityName',
invoice.invoice_supplier.address.city.name)
#DIAN 1.1.-2021: DSAJ10
#DIAN 1.1.-2021: NSAJ10
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName',
invoice.invoice_supplier.address.city.name)
# DIAN 1.1.-2021: DSAJ73
# DIAN 1.1.-2021: NSAJ73
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:PostalZone',
invoice.invoice_supplier.address.postalzone.code)
#DIAN 1.1.-2021: DSAJ73
#DIAN 1.1.-2021: NSAJ73
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:PostalZone',
invoice.invoice_supplier.address.postalzone.code)
# DIAN 1.1.-2021: DSAJ11
# DIAN 1.1.-2021: NSAJ11
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
#DIAN 1.1.-2021: DSAJ11
#DIAN 1.1.-2021: NSAJ11
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.1.-2021: DSAJ12
# DIAN 1.1.-2021: NSAJ12
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.1.-2021: NSAJ13 NSAJ14
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
#DIAN 1.1.-2021: DSAJ12
#DIAN 1.1.-2021: NSAJ12
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code)
#DIAN 1.1.-2021: NSAJ13 NSAJ14
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street)
# DIAN 1.1.-2021: DSAJ15 DSAJ16
# DIAN 1.1.-2021: NSAJ15 NSAJ16
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
#DIAN 1.1.-2021: DSAJ15 DSAJ16
#DIAN 1.1.-2021: NSAJ15 NSAJ16
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code)
#DIAN 1.1.-2021: DSAJ17
#DIAN 1.1.-2021: NSAJ17
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
#DIAN 1.1.-2021: DSAJ18
#DIAN 1.1.-2021: NSAJ18
languageID = 'es')
# DIAN 1.1.-2021: DSAJ17
# DIAN 1.1.-2021: NSAJ17
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name,
# DIAN 1.1.-2021: DSAJ18
# # DIAN 1.1.-2021: NSAJ18
languageID='es')
supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
supplier_company_id_attrs.update(
{
'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal})
supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal})
# DIAN 1.1.-2021: DSAJ19
# DIAN 1.1.-2021: NSAJ19
fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
#DIAN 1.1.-2021: DSAJ19
#DIAN 1.1.-2021: NSAJ19
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.1.-2021: DSAJ20
# DIAN 1.1.-2021: NSAJ20
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':RegistrationName',
invoice.invoice_supplier.legal_name)
#DIAN 1.1.-2021: DSAJ20
#DIAN 1.1.-2021: NSAJ20
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
# DIAN 1.1.-2021: DSAJ21
# DIAN 1.1.-2021: NSAJ21
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':CompanyID',
invoice.invoice_supplier.ident,
# DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25
# DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25
**supplier_company_id_attrs)
#DIAN 1.1.-2021: DSAJ21
#DIAN 1.1.-2021: NSAJ21
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_supplier.ident,
#DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25
#DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25
**supplier_company_id_attrs)
# DIAN 1.1.-2021: DSAJ26
# DIAN 1.1.-2021: NSAJ26
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':TaxLevelCode',
invoice.invoice_supplier.responsability_code,
listName=invoice.invoice_supplier.responsability_regime_code)
#DIAN 1.1.-2021: DSAJ26
#DIAN 1.1.-2021: NSAJ26
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
invoice.invoice_supplier.responsability_code,
listName=invoice.invoice_supplier.responsability_regime_code)
# DIAN 1.1.-2021: DSAJ39
# DIAN 1.1.-2021: NSAJ39
fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme')
#DIAN 1.1.-2021: DSAJ39
#DIAN 1.1.-2021: NSAJ39
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.1.-2021: DSAJ40
# DIAN 1.1.-2021: NSAJ40
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
#DIAN 1.1.-2021: DSAJ40
#DIAN 1.1.-2021: NSAJ40
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
#DIAN 1.1.-2021: DSAJ41
#DIAN 1.1.-2021: NSAJ41
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
# DIAN 1.1.-2021: DSAJ41
# DIAN 1.1.-2021: NSAJ41
fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
def set_customer(fexml, invoice):
# DIAN 1.1.-2021: DSAK01
# DIAN 1.1.-2021: NSAK01
#DIAN 1.1.-2021: DSAK01
#DIAN 1.1.-2021: NSAK01
fexml.placeholder_for('./cac:AccountingCustomerParty')
# DIAN 1.1.-2021: DSAK02
# DIAN 1.1.-2021: NSAK02
fexml.set_element(
'./cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code)
#DIAN 1.1.-2021: DSAK02
#DIAN 1.1.-2021: NSAK02
fexml.set_element('./cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code)
# DIAN 1.1.-2021: DSAK03
# DIAN 1.1.-2021: NSAK03
#DIAN 1.1.-2021: DSAK03
#DIAN 1.1.-2021: NSAK03
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party')
# DIAN 1.1.-2021: DSAK19
# DIAN 1.1.-2021: NSAK19
fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
#DIAN 1.1.-2021: DSAK19
#DIAN 1.1.-2021: NSAK19
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.1.-2021: DSAK20
# DIAN 1.1.-2021: NSAK20
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':RegistrationName',
invoice.invoice_customer.legal_name)
#DIAN 1.1.-2021: DSAK20
#DIAN 1.1.-2021: NSAK20
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
customer_company_id_attrs.update(
{
'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal})
customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal})
# DIAN 1.1.-2021: DSAK21
# DIAN 1.1.-2021: NSAK21
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':CompanyID',
invoice.invoice_customer.ident,
# DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25
# DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25
**customer_company_id_attrs)
#DIAN 1.1.-2021: DSAK21
#DIAN 1.1.-2021: NSAK21
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_customer.ident,
#DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25
#DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25
**customer_company_id_attrs)
# DIAN 1.1.-2021: DSAK26
# DIAN 1.1.-2021: NSAK26
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':TaxLevelCode',
invoice.invoice_customer.responsability_code)
#DIAN 1.1.-2021: DSAK26
#DIAN 1.1.-2021: NSAK26
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode',
invoice.invoice_customer.responsability_code)
# DIAN 1.1.-2021: DSAK39
# DIAN 1.1.-2021: NSAK39
fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme')
#DIAN 1.1.-2021: DSAK39
#DIAN 1.1.-2021: NSAK39
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme')
# DIAN 1.1.-2021: DSAK40
# DIAN 1.1.-2021: NSAK40
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
#DIAN 1.1.-2021: DSAK40
#DIAN 1.1.-2021: NSAK40
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code)
#DIAN 1.1.-2021: DSAK41
#DIAN 1.1.-2021: NSAK41
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
# DIAN 1.1.-2021: DSAK41
# DIAN 1.1.-2021: NSAK41
fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name)
def set_payment_mean(fexml, invoice):
payment_mean = invoice.invoice_payment_mean
# DIAN 1.1.-2021: DSAN01 DSAN02
# DIAN 1.1.-2021: NSAN02 NSAN02
#DIAN 1.1.-2021: DSAN01 DSAN02
#DIAN 1.1.-2021: NSAN02 NSAN02
fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id)
# DIAN 1.1.-2021: DSAN03
# DIAN 1.1.-2021: NSAN03
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentMeansCode',
payment_mean.code)
#DIAN 1.1.-2021: DSAN03
#DIAN 1.1.-2021: NSAN03
fexml.set_element('./cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code)
# DIAN 1.1.-2021: DSAN04
# DIAN 1.1.-2021: NSAN04
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentDueDate',
payment_mean.due_at.strftime('%Y-%m-%d'))
#DIAN 1.1.-2021: DSAN04
#DIAN 1.1.-2021: NSAN04
fexml.set_element('./cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d'))
# DIAN 1.1.-2021: DSAN05
# DIAN 1.1.-2021: NSAN05
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentID',
payment_mean.payment_id)
#DIAN 1.1.-2021: DSAN05
#DIAN 1.1.-2021: NSAN05
fexml.set_element('./cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id)
def set_element_amount_for(fexml, xml, xpath, amount):
if not isinstance(amount, Amount):
@@ -303,47 +228,40 @@ class DIANSupportDocumentXML(fe.FeXML):
fexml.set_element(xpath, amount, currencyID=amount.currency.code)
def set_legal_monetary(fexml, invoice):
# DIAN 1.1.-2021: DSAU01 DSAU02 DSAU03
# DIAN 1.1.-2021: NSAU01 NSAU02 NSAU03
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
#DIAN 1.1.-2021: DSAU01 DSAU02 DSAU03
#DIAN 1.1.-2021: NSAU01 NSAU02 NSAU03
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount)
# DIAN 1.1.-2021: DSAU04 DSAU05
# DIAN 1.1.-2021: NSAU04 NSAU05
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
#DIAN 1.1.-2021: DSAU04 DSAU05
#DIAN 1.1.-2021: NSAU04 NSAU05
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount)
# DIAN 1.1.-2021: DSAU06 DSAU07
# DIAN 1.1.-2021: NSAU06 DSAU07
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
#DIAN 1.1.-2021: DSAU06 DSAU07
#DIAN 1.1.-2021: NSAU06 DSAU07
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount)
# DIAN 1.1.-2021: DSAU10 DSAU11
# DIAN 1.1.-2021: NSAU10 DSAU11
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
#DIAN 1.1.-2021: DSAU10 DSAU11
#DIAN 1.1.-2021: NSAU10 DSAU11
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount)
#DIAN 1.1.-2021: DSAU14 DSAU15
#DIAN 1.1.-2021: NSAU14 DSAU15
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)
# DIAN 1.1.-2021: DSAU14 DSAU15
# DIAN 1.1.-2021: NSAU14 DSAU15
fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount)
def _set_invoice_document_reference(fexml, reference):
fexml._do_set_billing_reference(
reference, 'cac:InvoiceDocumentReference')
fexml._do_set_billing_reference(reference, 'cac:InvoiceDocumentReference')
def _set_credit_note_document_reference(fexml, reference):
fexml._do_set_billing_reference(
reference, 'cac:CreditNoteDocumentReference')
fexml._do_set_billing_reference(reference, 'cac:CreditNoteDocumentReference')
def _set_debit_note_document_reference(fexml, reference):
fexml._do_set_billing_reference(
reference, 'cac:DebitNoteDocumentReference')
fexml._do_set_billing_reference(reference, 'cac:DebitNoteDocumentReference')
def _do_set_billing_reference(fexml, reference, tag_document):
@@ -352,16 +270,13 @@ class DIANSupportDocumentXML(fe.FeXML):
else:
schemeName = 'CUDS-SHA384'
fexml.set_element('./cac:BillingReference/%s/cbc:ID' % (tag_document),
fexml.set_element('./cac:BillingReference/%s/cbc:ID' %(tag_document),
reference.ident)
fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID',
reference.uuid,
schemeName=schemeName)
fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/'
'cbc:IssueDate',
reference.date.strftime("%Y-%m-%d"))
fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID',
reference.uuid,
schemeName=schemeName)
fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate',
reference.date.strftime("%Y-%m-%d"))
def set_billing_reference(fexml, invoice):
reference = invoice.invoice_billing_reference
@@ -389,11 +304,11 @@ class DIANSupportDocumentXML(fe.FeXML):
return fexml._set_invoice_document_reference(reference)
fexml.set_element('./cac:DiscrepancyResponse/cbc:ReferenceID',
reference.id)
reference.id)
fexml.set_element('./cac:DiscrepancyResponse/cbc:ResponseCode',
reference.code)
reference.code)
fexml.set_element('./cac:DiscrepancyResponse/cbc:Description',
reference.description)
reference.description)
def set_invoice_totals(fexml, invoice):
tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0)))
@@ -404,67 +319,60 @@ class DIANSupportDocumentXML(fe.FeXML):
for invoice_line in invoice.invoice_lines:
for subtotal in invoice_line.tax.subtotals:
if subtotal.scheme is not None:
tax_amount_for[
subtotal.scheme.code][
'tax_amount'] += subtotal.tax_amount
tax_amount_for[subtotal.scheme.code][
'taxable_amount'] += invoice_line.taxable_amount
tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount
tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount
# MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal.scheme.code] = subtotal.percent
total_tax_amount += subtotal.tax_amount
if total_tax_amount != Amount(0.0):
fexml.placeholder_for('./cac:TaxTotal')
fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount',
total_tax_amount)
total_tax_amount)
for index, item in enumerate(tax_amount_for.items()):
cod_impuesto, amount_of = item
next_append = index > 0
# DIAN 1.7.-2020: FAS01
#DIAN 1.7.-2020: FAS01
line = fexml.fragment('./cac:TaxTotal', append=next_append)
# DIAN 1.7.-2020: FAU06
#DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cbc:TaxAmount',
tax_amount)
# DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(
line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount'])
#DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount'])
# DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(
line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount'])
#DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount'])
# DIAN 1.7.-2020: FAS07
#DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto])
if percent_for[cod_impuesto]:
line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/'
'cbc:Percent',
percent_for[cod_impuesto])
line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme'
'/cbc:ID',
cod_impuesto)
line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme'
'/cbc:Name', 'IVA')
if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent',
percent_for[cod_impuesto])
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID',
cod_impuesto)
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name',
'IVA')
# abstract method
def tag_document(fexml):
return 'Invoice'
@@ -476,89 +384,50 @@ class DIANSupportDocumentXML(fe.FeXML):
fexml.set_element_amount_for(line,
'./cac:WithholdingTaxTotal/cbc:TaxAmount',
invoice_line.withholding_amount)
# DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(
line,
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.withholding_taxable_amount)
#DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(line,
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.withholding_taxable_amount)
for subtotal in invoice_line.withholding.subtotals:
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
subtotal.tax_amount,
currencyID='COP')
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP')
if subtotal.percent is not None:
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac'
':TaxCategory/cbc:Percent',
'%0.2f' %
round(
subtotal.percent,
2))
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2))
if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac'
':TaxCategory/cac:TaxScheme/cbc:ID',
subtotal.scheme.code)
line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac'
':TaxCategory/cac:TaxScheme/cbc:Name',
subtotal.scheme.name)
#DIAN 1.7.-2020: FAX15
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code)
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name)
def set_invoice_line_tax(fexml, line, invoice_line):
fexml.set_element_amount_for(line,
'./cac:TaxTotal/cbc:TaxAmount',
invoice_line.tax_amount)
# DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(
line,
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.taxable_amount)
#DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(line,
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.taxable_amount)
for subtotal in invoice_line.tax.subtotals:
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
subtotal.tax_amount,
currencyID='COP')
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP')
if subtotal.percent is not None:
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc'
':Percent',
'%0.2f' %
round(
subtotal.percent,
2))
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2))
if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac'
':TaxScheme/cbc:ID',
subtotal.scheme.code)
line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac'
':TaxScheme/cbc:Name',
subtotal.scheme.name)
#DIAN 1.7.-2020: FAX15
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code)
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name)
def set_invoice_lines(fexml, invoice):
next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines):
line = fexml.fragment(
'./cac:%sLine' %
(fexml.tag_document()),
append=next_append)
line = fexml.fragment('./cac:%sLine' % (fexml.tag_document()), append=next_append)
next_append = True
line.set_element('./cbc:ID', index + 1)
line.set_element(
'./cbc:%sQuantity' %
(fexml.tag_document_concilied()),
invoice_line.quantity,
unitCode='NAR')
line.set_element('./cbc:%sQuantity' % (fexml.tag_document_concilied()), invoice_line.quantity, unitCode = 'NAR')
fexml.set_element_amount_for(line,
'./cbc:LineExtensionAmount',
invoice_line.total_amount)
@@ -566,69 +435,53 @@ class DIANSupportDocumentXML(fe.FeXML):
period = line.fragment('./cac:InvoicePeriod')
period.set_element('./cbc:StartDate',
datetime.now().strftime('%Y-%m-%d'))
period.set_element(
'./cbc:DescriptionCode', '1')
period.set_element('./cbc:DescriptionCode',
'1')
period.set_element('./cbc:Description',
'Por operación')
if not isinstance(invoice_line.tax, TaxTotalOmit):
fexml.set_invoice_line_tax(line, invoice_line)
if not isinstance(
invoice_line.withholding,
WithholdingTaxTotalOmit):
if not isinstance(invoice_line.withholding, WithholdingTaxTotalOmit):
fexml.set_invoice_line_withholding(line, invoice_line)
line.set_element(
'./cac:Item/cbc:Description',
invoice_line.item.description)
line.set_element(
'./cac:Item/cac:StandardItemIdentification/cbc:ID',
invoice_line.item.id,
schemeID=invoice_line.item.scheme_id,
schemeName=invoice_line.item.scheme_name,
schemeAgencyID=invoice_line.item.scheme_agency_id)
line.set_element('./cac:Item/cbc:Description', invoice_line.item.description)
line.set_element(
'./cac:Price/cbc:PriceAmount',
invoice_line.price.amount,
currencyID=invoice_line.price.amount.currency.code)
# DIAN 1.7.-2020: FBB04
line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID',
invoice_line.item.id,
schemeID=invoice_line.item.scheme_id,
schemeName=invoice_line.item.scheme_name,
schemeAgencyID=invoice_line.item.scheme_agency_id)
line.set_element('./cac:Price/cbc:PriceAmount', invoice_line.price.amount, currencyID=invoice_line.price.amount.currency.code)
#DIAN 1.7.-2020: FBB04
line.set_element('./cac:Price/cbc:BaseQuantity',
invoice_line.quantity,
unitCode=invoice_line.quantity.code)
for idx, charge in enumerate(invoice_line.allowance_charge):
next_append_charge = idx > 0
fexml.append_allowance_charge(
line, index + 1, charge, append=next_append_charge)
fexml.append_allowance_charge(line, index + 1, charge, append=next_append_charge)
def set_allowance_charge(fexml, invoice):
for idx, charge in enumerate(invoice.invoice_allowance_charge):
next_append = idx > 0
fexml.append_allowance_charge(
fexml, idx + 1, charge, append=next_append)
fexml.append_allowance_charge(fexml, idx + 1, charge, append=next_append)
def append_allowance_charge(fexml, parent, idx, charge, append=False):
line = parent.fragment('./cac:AllowanceCharge', append=append)
# DIAN 1.7.-2020: FAQ02
line.set_element('./cbc:ID', idx)
# DIAN 1.7.-2020: FAQ03
line.set_element('./cbc:ChargeIndicator',
str(charge.charge_indicator).lower())
if charge.reason:
line.set_element(
'./cbc:AllowanceChargeReasonCode',
charge.reason.code)
line.set_element(
'./cbc:allowanceChargeReason',
charge.reason.reason)
line.set_element('./cbc:MultiplierFactorNumeric',
str(round(charge.multiplier_factor_numeric, 2)))
fexml.set_element_amount_for(line, './cbc:Amount', charge.amount)
fexml.set_element_amount_for(
line, './cbc:BaseAmount', charge.base_amount)
line = parent.fragment('./cac:AllowanceCharge', append=append)
#DIAN 1.7.-2020: FAQ02
line.set_element('./cbc:ID', idx)
#DIAN 1.7.-2020: FAQ03
line.set_element('./cbc:ChargeIndicator', str(charge.charge_indicator).lower())
if charge.reason:
line.set_element('./cbc:AllowanceChargeReasonCode', charge.reason.code)
line.set_element('./cbc:allowanceChargeReason', charge.reason.reason)
line.set_element('./cbc:MultiplierFactorNumeric', str(round(charge.multiplier_factor_numeric, 2)))
fexml.set_element_amount_for(line, './cbc:Amount', charge.amount)
fexml.set_element_amount_for(line, './cbc:BaseAmount', charge.base_amount)
def attach_invoice(fexml, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
@@ -636,40 +489,26 @@ class DIANSupportDocumentXML(fe.FeXML):
fexml.placeholder_for('./ext:UBLExtensions')
fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1')
fexml.set_element(
'./cbc:CustomizationID',
invoice.invoice_operation_type)
fexml.set_element('./cbc:CustomizationID', invoice.invoice_operation_type)
fexml.placeholder_for('./cbc:ProfileID')
fexml.placeholder_for('./cbc:ProfileExecutionID')
fexml.set_element('./cbc:ID', invoice.invoice_ident)
fexml.placeholder_for('./cbc:UUID')
fexml.set_element('./cbc:DocumentCurrencyCode', 'COP')
fexml.set_element(
'./cbc:IssueDate',
invoice.invoice_issue.strftime('%Y-%m-%d'))
# DIAN 1.7.-2020: FAD10
fexml.set_element(
'./cbc:IssueTime',
invoice.invoice_issue.strftime('%H:%M:%S-05:00'))
fexml.set_element(
'./cbc:%sTypeCode' %
(fexml.tag_document()),
invoice.invoice_type_code,
listAgencyID='195',
listAgencyName=(
'No matching global declaration available for the '
'validation root'),
listURI='http://www.dian.gov.co')
fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d'))
#DIAN 1.7.-2020: FAD10
fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00'))
fexml.set_element('./cbc:%sTypeCode' % (fexml.tag_document()),
invoice.invoice_type_code,
listAgencyID='195',
listAgencyName='No matching global declaration available for the validation root',
listURI='http://www.dian.gov.co')
fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines))
fexml.set_element(
'./cac:%sPeriod/cbc:StartDate' %
(fexml.tag_document()),
invoice.invoice_period_start.strftime('%Y-%m-%d'))
fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (fexml.tag_document()),
invoice.invoice_period_start.strftime('%Y-%m-%d'))
fexml.set_element(
'./cac:%sPeriod/cbc:EndDate' %
(fexml.tag_document()),
invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (fexml.tag_document()),
invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.customize(invoice)

View File

@@ -1,10 +1,9 @@
# from .. import fe
# from ..form import *
from .. import fe
from ..form import *
from .support_document import DIANSupportDocumentXML
__all__ = ['DIANSupportDocumentCreditNoteXML']
class DIANSupportDocumentCreditNoteXML(DIANSupportDocumentXML):
"""
DianInvoiceXML mapea objeto form.Invoice a XML segun
@@ -12,14 +11,11 @@ class DIANSupportDocumentCreditNoteXML(DIANSupportDocumentXML):
"""
def __init__(self, invoice):
super(
DIANSupportDocumentCreditNoteXML,
self).__init__(
invoice,
'CreditNote')
super(DIANSupportDocumentCreditNoteXML, self).__init__(invoice, 'CreditNote')
def tag_document(fexml):
return 'CreditNote'
def tag_document_concilied(fexml):
return 'Credited'

View File

@@ -2,30 +2,18 @@ from .. import fe
__all__ = ['DIANWrite', 'DIANWriteSigned']
def DIANWrite(xml, filename):
document = xml.tostring(xml_declaration=True, encoding='UTF-8')
with open(filename, 'w') as f:
f.write(document)
def DIANWriteSigned(
xml,
filename,
private_key,
passphrase,
use_cache_policy=False,
dian_signer=None):
document = xml.tostring(
xml_declaration=True,
encoding='UTF-8').encode('utf-8')
def DIANWriteSigned(xml, filename, private_key, passphrase, use_cache_policy=False, dian_signer=None):
document = xml.tostring(xml_declaration=True, encoding='UTF-8').encode('utf-8')
if dian_signer is None:
dian_signer = fe.DianXMLExtensionSigner
signer = dian_signer(
private_key,
passphrase=passphrase,
localpolicy=use_cache_policy)
signer = dian_signer(private_key, passphrase=passphrase, localpolicy=use_cache_policy)
with open(filename, 'w') as f:
f.write(signer.sign_xml_string(document))

View File

@@ -8,91 +8,21 @@
from dataclasses import dataclass
from datetime import datetime
import hashlib
import typing
from .. import fe
from .. import form
from ..data.dian import codelist
from .amount import Amount
from .deduccion import (
Deduccion,
DeduccionFondoPension,
DeduccionSalud,
)
from .devengado import (
Devengado,
DevengadoBasico,
DevengadoHoraExtra,
DevengadoHorasExtrasDiarias,
DevengadoHorasExtrasDiariasDominicalesYFestivos,
DevengadoHorasExtrasNocturnas,
DevengadoHorasExtrasNocturnasDominicalesYFestivos,
DevengadoHorasRecargoDiariasDominicalesYFestivos,
DevengadoHorasRecargoNocturno,
DevengadoHorasRecargoNocturnoDominicalesYFestivos,
DevengadoTransporte,
)
from .empleador import Empleador
from .exception import DIANNominaIndividualError
from .devengado import *
from .deduccion import *
from .trabajador import *
from .empleador import *
from .pago import *
from .lugar import Lugar
from .pago import (
FormaPago,
MetodoPago,
Pago,
)
from .trabajador import (
LugarTrabajo,
SubTipoTrabajador,
TipoContrato,
TipoDocumento,
TipoTrabajador,
Trabajador,
)
__all__ = [
'Amount',
'Deduccion',
'DeduccionFondoPension',
'DeduccionSalud',
'Devengado',
'DevengadoBasico',
'DevengadoHoraExtra',
'DevengadoHorasExtrasDiarias',
'DevengadoHorasExtrasDiariasDominicalesYFestivos',
'DevengadoHorasExtrasNocturnas',
'DevengadoHorasExtrasNocturnasDominicalesYFestivos',
'DevengadoHorasRecargoDiariasDominicalesYFestivos',
'DevengadoHorasRecargoNocturno',
'DevengadoHorasRecargoNocturnoDominicalesYFestivos',
'DevengadoTransporte',
'DIANNominaIndividual',
'DIANNominaIndividualDeAjuste',
'DIANNominaXML',
'DIANNominaIndividualError',
'DianXMLExtensionSigner',
'Empleador',
'Fecha',
'FechaPago',
'FormaPago',
'InformacionGeneral',
'Lugar',
'LugarTrabajo',
'Metadata',
'MetodoPago',
'Novedad',
'NumeroSecuencia',
'Pago',
'Periodo',
'PeriodoNomina',
'Proveedor',
'SubTipoTrabajador',
'TipoContrato',
'TipoDocumento',
'TipoMoneda',
'TipoTrabajador',
'Trabajador',
]
from .amount import Amount
from .exception import *
class Fecha:
def __init__(self, fecha):
@@ -116,7 +46,6 @@ class Fecha:
def __str__(self):
return self.value
class FechaPago(Fecha):
def apply(self, fragment):
fragment.set_element('./FechaPago', self.value)
@@ -153,43 +82,39 @@ class NumeroSecuencia:
# NIE011
Consecutivo=self.consecutivo,
# NIE012
Numero=numero)
Numero = numero)
@dataclass
class Periodo:
fecha_ingreso: str | Fecha
fecha_liquidacion_inicio: str | Fecha
fecha_liquidacion_fin: str | Fecha
fecha_generacion: str | Fecha
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]
tiempo_laborado: int = 1
fecha_retiro: str | Fecha | None = None
fecha_retiro: typing.Union[str, Fecha] = None
def __post_init__(self):
self.fecha_ingreso = Fecha.cast(self.fecha_ingreso)
self.fecha_liquidacion_inicio = Fecha.cast(
self.fecha_liquidacion_inicio)
self.fecha_liquidacion_inicio = Fecha.cast(self.fecha_liquidacion_inicio)
self.fecha_liquidacion_fin = Fecha.cast(self.fecha_liquidacion_fin)
self.fecha_retiro = Fecha.cast(self.fecha_retiro, optional=True)
def apply(self, fragment):
fragment.set_attributes('./Periodo',
# NIE002
#NIE002
FechaIngreso=self.fecha_ingreso,
# NIE003
#NIE003
FechaRetiro=self.fecha_retiro,
# NIE004
FechaLiquidacionInicio=(
self.fecha_liquidacion_inicio),
# NIE005
#NIE004
FechaLiquidacionInicio=self.fecha_liquidacion_inicio,
#NIE005
FechaLiquidacionFin=self.fecha_liquidacion_fin,
# NIE006
#NIE006
TiempoLaborado=self.tiempo_laborado,
# NIE008
#NIE008
FechaGen=self.fecha_generacion)
@dataclass
class Proveedor:
razon_social: str
@@ -216,16 +141,11 @@ class Proveedor:
cune_xpath = scopexml.xpath_from_root('/InformacionGeneral')
cune = fexml.get_element_attribute(cune_xpath, 'CUNE')
ambiente = fexml.get_element_attribute(
scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente')
codigo_qr = (
f"https://catalogo-vpfe.dian.gov.co/document/"
f"searchqr?documentkey={cune}")
ambiente = fexml.get_element_attribute(scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente')
codigo_qr = f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={cune}"
if InformacionGeneral.AMBIENTE_PRUEBAS == ambiente:
codigo_qr = (
f"https://catalogo-vpfe-hab.dian.gov.co/document/"
f"searchqr?documentkey={cune}")
codigo_qr = f"https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey={cune}"
elif ambiente is None:
raise RuntimeError('fail to get InformacionGeneral/@Ambiente')
@@ -233,15 +153,13 @@ class Proveedor:
# NIE020
software_code = self._software_security_code(fexml, scopexml)
fexml.set_attributes(
scopexml.xpath_from_root('/ProveedorXML'),
SoftwareSC=software_code)
fexml.set_attributes(scopexml.xpath_from_root('/ProveedorXML'), SoftwareSC=software_code)
def _software_security_code(self, fexml, scopexml):
# 8.2
numero = fexml.get_element_attribute(
scopexml.xpath_from_root('/NumeroSecuenciaXML'), 'Numero')
numero = fexml.get_element_attribute(scopexml.xpath_from_root('/NumeroSecuenciaXML'), 'Numero')
if numero is None:
raise RuntimeError('fallo obtener NumeroSequenciaXML/@Numero')
@@ -255,7 +173,6 @@ class Proveedor:
h.update(code.encode('utf-8'))
return h.hexdigest()
@dataclass
class Metadata:
novedad: Novedad
@@ -264,33 +181,18 @@ class Metadata:
lugar_generacion: Lugar
proveedor: Proveedor
def apply(
self,
novedad,
numero_secuencia_xml,
lugar_generacion_xml,
proveedor_xml):
def apply(self, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
if novedad:
self.novedad.apply(novedad)
self.secuencia.apply(numero_secuencia_xml)
self.lugar_generacion.apply(
lugar_generacion_xml,
'./LugarGeneracionXML')
self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML')
self.proveedor.apply(proveedor_xml)
def post_apply(
self,
fexml,
scopexml,
novedad,
numero_secuencia_xml,
lugar_generacion_xml,
proveedor_xml):
def post_apply(self, fexml, scopexml, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.proveedor.post_apply(fexml, scopexml, proveedor_xml)
if novedad:
self.novedad.post_apply(fexml, scopexml, proveedor_xml)
@dataclass
class PeriodoNomina:
code: str
@@ -301,7 +203,6 @@ class PeriodoNomina:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.PeriodoNomina[self.code]['name']
@dataclass
class TipoMoneda:
code: str
@@ -312,7 +213,6 @@ class TipoMoneda:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoMoneda[self.code]['name']
@dataclass
class InformacionGeneral:
@dataclass
@@ -359,7 +259,7 @@ class InformacionGeneral:
def __str__(self):
self.valor
fecha_generacion: str | Fecha
fecha_generacion: typing.Union[str, Fecha]
hora_generacion: str
periodo_nomina: PeriodoNomina
tipo_moneda: TipoMoneda
@@ -373,26 +273,26 @@ class InformacionGeneral:
def apply(self, fragment, version):
fragment.set_attributes('./InformacionGeneral',
# NIE022
Version=version,
Version = version,
# NIE023
Ambiente=self.tipo_ambiente.valor,
Ambiente = self.tipo_ambiente.valor,
# NIE202
# TABLA 5.5.2
# TODO(bit4bit) solo NominaIndividual
TipoXML=self.tipo_xml.valor,
TipoXML = self.tipo_xml.valor,
# NIE024
CUNE=None,
CUNE = None,
# NIE025
EncripCUNE='CUNE-SHA384',
EncripCUNE = 'CUNE-SHA384',
# NIE026
FechaGen=self.fecha_generacion,
FechaGen = self.fecha_generacion,
# NIE027
HoraGen=self.hora_generacion,
HoraGen = self.hora_generacion,
# NIE029
PeriodoNomina=self.periodo_nomina.code,
PeriodoNomina = self.periodo_nomina.code,
# NIE030
TipoMoneda=self.tipo_moneda.code,
TRM=0
TipoMoneda = self.tipo_moneda.code,
TRM = 0
# TODO(bit4bit) resto...
# .....
)
@@ -425,31 +325,20 @@ class InformacionGeneral:
fragment.set_attributes(
'./InformacionGeneral',
# NIE024
CUNE=cune_hash
CUNE = cune_hash
)
class DianXMLExtensionSigner(fe.DianXMLExtensionSigner):
def __init__(self, pkcs12_path, passphrase=None, localpolicy=True):
super().__init__(
pkcs12_path,
passphrase=passphrase,
localpolicy=localpolicy)
super().__init__(pkcs12_path, passphrase=passphrase, localpolicy=localpolicy)
def _element_extension_content(self, fachoxml):
return fachoxml.builder.xpath(
fachoxml.root,
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
class DIANNominaXML:
def __init__(
self,
tag_document,
xpath_ajuste=None,
schemaLocation=None,
namespace_ajuste=None):
def __init__(self, tag_document, xpath_ajuste=None, schemaLocation=None, namespace_ajuste=None):
self.informacion_general_version = None
self.tag_document = tag_document
@@ -457,26 +346,21 @@ class DIANNominaXML:
if namespace_ajuste:
self.fexml = fe.FeXML(tag_document, namespace_ajuste)
else:
self.fexml = fe.FeXML(
tag_document,
'dian:gov:co:facturaelectronica:NominaIndividual')
self.fexml = fe.FeXML(tag_document, 'dian:gov:co:facturaelectronica:NominaIndividual')
self.fexml.root.set("SchemaLocation", "")
self.fexml.root.set("schemaLocation", schemaLocation)
# layout, la dian requiere que los elementos
# esten ordenados segun el anexo tecnico
self.fexml.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
self.fexml.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
self.fexml.placeholder_for('./TipoNota', optional=True)
self.root_fragment = self.fexml
if xpath_ajuste is not None:
self.root_fragment = self.fexml.fragment(xpath_ajuste)
self.root_fragment.placeholder_for(
'./ReemplazandoPredecesor', optional=True)
self.root_fragment.placeholder_for(
'./EliminandoPredecesor', optional=True)
self.root_fragment.placeholder_for('./ReemplazandoPredecesor', optional=True)
self.root_fragment.placeholder_for('./EliminandoPredecesor', optional=True)
if not namespace_ajuste:
self.root_fragment.placeholder_for('./Novedad', optional=False)
self.root_fragment.placeholder_for('./Periodo')
@@ -490,20 +374,16 @@ class DIANNominaXML:
self.root_fragment.placeholder_for('./Pago')
self.root_fragment.placeholder_for('./FechasPagos')
self.root_fragment.placeholder_for('./Devengados/Basico')
self.root_fragment.placeholder_for(
'./Devengados/Transporte', optional=True)
self.root_fragment.placeholder_for('./Devengados/Transporte', optional=True)
if not namespace_ajuste:
self.novedad = self.root_fragment.fragment('./Novedad')
else:
self.novedad = None
self.informacion_general_xml = self.root_fragment.fragment(
'./InformacionGeneral')
self.informacion_general_xml = self.root_fragment.fragment('./InformacionGeneral')
self.periodo_xml = self.root_fragment.fragment('./Periodo')
self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos')
self.numero_secuencia_xml = self.root_fragment.fragment(
'./NumeroSecuenciaXML')
self.lugar_generacion_xml = self.root_fragment.fragment(
'./LugarGeneracionXML')
self.numero_secuencia_xml = self.root_fragment.fragment('./NumeroSecuenciaXML')
self.lugar_generacion_xml = self.root_fragment.fragment('./LugarGeneracionXML')
self.proveedor_xml = self.root_fragment.fragment('./ProveedorXML')
self.empleador = self.root_fragment.fragment('./Empleador')
self.trabajador = self.root_fragment.fragment('./Trabajador')
@@ -519,19 +399,13 @@ class DIANNominaXML:
raise ValueError('se espera tipo Metadata')
self.metadata = metadata
self.metadata.apply(
self.novedad,
self.numero_secuencia_xml,
self.lugar_generacion_xml,
self.proveedor_xml)
self.metadata.apply(self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
def asignar_informacion_general(self, general):
if not isinstance(general, InformacionGeneral):
raise ValueError('se espera tipo InformacionGeneral')
self.informacion_general = general
self.informacion_general.apply(
self.informacion_general_xml,
self.informacion_general_version)
self.informacion_general.apply(self.informacion_general_xml, self.informacion_general_version)
def asignar_periodo(self, periodo):
if not isinstance(periodo, Periodo):
@@ -624,13 +498,10 @@ class DIANNominaXML:
def informacion_general(self):
xpath = self.root_fragment.xpath_from_root('/InformacionGeneral')
return {
'cune': self.fexml.get_element_attribute(
xpath, 'CUNE'),
'fecha_generacion': self.fexml.get_element_attribute(
xpath, 'FechaGen'),
'numero': self.fexml.get_element_attribute(
self.root_fragment(
'/NumeroSecuenciaXML', 'Numero'))}
'cune': self.fexml.get_element_attribute(cune_xpath, 'CUNE'),
'fecha_generacion': self.fexml.get_element_attribute(xpath, 'FechaGen'),
'numero': self.fexml.get_element_attribute(self.root_fragment('/NumeroSecuenciaXML', 'Numero'))
}
def toFachoXML(self):
self._devengados_total()
@@ -638,41 +509,29 @@ class DIANNominaXML:
self._comprobante_total()
if self.informacion_general is not None:
# TODO(bit4bit) acoplamiento temporal
#TODO(bit4bit) acoplamiento temporal
# es importante el orden de ejecucion
self.informacion_general.post_apply(
self.fexml, self.root_fragment, self.informacion_general_xml)
self.informacion_general.post_apply(self.fexml, self.root_fragment, self.informacion_general_xml)
if self.metadata is not None:
self.metadata.post_apply(
self.fexml,
self.root_fragment,
self.novedad,
self.numero_secuencia_xml,
self.lugar_generacion_xml,
self.proveedor_xml)
self.metadata.post_apply(self.fexml, self.root_fragment, self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
return self.fexml
def _comprobante_total(self):
devengados_total = self.root_fragment.get_element_text_or_attribute(
'./DevengadosTotal', '0.0')
deducciones_total = self.root_fragment.get_element_text_or_attribute(
'./DeduccionesTotal', '0.0')
devengados_total = self.root_fragment.get_element_text_or_attribute('./DevengadosTotal', '0.0')
deducciones_total = self.root_fragment.get_element_text_or_attribute('./DeduccionesTotal', '0.0')
comprobante_total = Amount(devengados_total) - \
Amount(deducciones_total)
comprobante_total = Amount(devengados_total) - Amount(deducciones_total)
self.root_fragment.set_element(
'./ComprobanteTotal', str(round(comprobante_total, 2)))
self.root_fragment.set_element('./ComprobanteTotal', str(round(comprobante_total, 2)))
def _deducciones_total(self):
xpaths = [
self.root_fragment.xpath_from_root(
'/Deducciones/Salud/@Deduccion'),
self.root_fragment.xpath_from_root(
'/Deducciones/FondoPension/@Deduccion')]
self.root_fragment.xpath_from_root('/Deducciones/Salud/@Deduccion'),
self.root_fragment.xpath_from_root('/Deducciones/FondoPension/@Deduccion')
]
deducciones = map(lambda valor: Amount(valor),
self._values_of_xpaths(xpaths))
@@ -681,19 +540,15 @@ class DIANNominaXML:
for deduccion in deducciones:
deducciones_total += deduccion
self.root_fragment.set_element(
'./DeduccionesTotal', str(round(deducciones_total, 2)))
self.root_fragment.set_element('./DeduccionesTotal', str(round(deducciones_total, 2)))
def _devengados_total(self):
xpaths = [
self.root_fragment.xpath_from_root(
'/Devengados/Basico/@SueldoTrabajado'),
self.root_fragment.xpath_from_root(
'/Devengados/Transporte/@AuxilioTransporte'),
self.root_fragment.xpath_from_root(
'/Devengados/Transporte/@ViaticoManuAlojS'),
self.root_fragment.xpath_from_root(
'/Devengados/Transporte/@ViaticoManuAlojNS')]
self.root_fragment.xpath_from_root('/Devengados/Basico/@SueldoTrabajado'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@AuxilioTransporte'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@ViaticoManuAlojS'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@ViaticoManuAlojNS')
]
devengados = map(lambda valor: Amount(valor),
self._values_of_xpaths(xpaths))
@@ -702,14 +557,11 @@ class DIANNominaXML:
devengados_total += devengado
# TODO(bit4bit) nque valor va redondeado?
# NIE186
self.root_fragment.set_element('./Redondeo', str(round(0, 2)))
self.root_fragment.set_element(
'./DevengadosTotal', str(round(devengados_total, 2)))
self.root_fragment.set_element('./Redondeo', str(round(0,2)))
self.root_fragment.set_element('./DevengadosTotal', str(round(devengados_total,2)))
def _values_of_xpaths(self, xpaths):
xpaths_values_of_values = map(
lambda val: self.fexml.get_element_text_or_attribute(
val, multiple=True), xpaths)
xpaths_values_of_values = map(lambda val: self.fexml.get_element_text_or_attribute(val, multiple=True), xpaths)
xpaths_values = []
# toda esta carreta para hacer un aplano de lista
for xpath_values in xpaths_values_of_values:
@@ -721,22 +573,15 @@ class DIANNominaXML:
return filter(lambda val: val is not None, xpaths_values)
class DIANNominaIndividual(DIANNominaXML):
def __init__(self):
schema = (
"dian:gov:co:facturaelectronica:NominaIndividual"
" NominaIndividualElectronicaXSD.xsd"
)
schema = "dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"
super().__init__('NominaIndividual', schemaLocation=schema)
self.informacion_general_version = (
'V1.0: Documento Soporte de Pago de Nómina Electrónica')
self.informacion_general_version = 'V1.0: Documento Soporte de Pago de Nómina Electrónica'
# TODO(bit4bit) confirmar que no tienen en comun con NominaIndividual
class DIANNominaIndividualDeAjuste(DIANNominaXML):
class Reemplazar(DIANNominaXML):
@@ -749,39 +594,28 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
def apply(self, fragment):
# NIAE214
fragment.set_element('./TipoNota', '1')
fragment.set_element(
'./Reemplazar/ReemplazandoPredecesor', None,
# NIAE090
NumeroPred=self.numero,
# NIAE191
CUNEPred=self.cune,
# NIAE192
FechaGenPred=self.fecha_generacion
fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None,
# NIAE090
NumeroPred = self.numero,
# NIAE191
CUNEPred = self.cune,
# NIAE192
FechaGenPred = self.fecha_generacion
)
def __init__(self):
schema = (
"dian:gov:co:facturaelectronica:NominaIndividualDeAjuste"
" NominaIndividualDeAjusteElectronicaXSD.xsd"
)
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__(
'NominaIndividualDeAjuste',
'./Reemplazar',
schemaLocation=schema,
namespace_ajuste=(
'dian:gov:co:facturaelectronica:'
'NominaIndividualDeAjuste'))
super().__init__('NominaIndividualDeAjuste', './Reemplazar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste')
self.informacion_general_version = (
'V1.0: Nota de Ajuste de Documento Soporte de Pago de '
'Nómina Electrónica')
self.informacion_general_version = 'V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica'
def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor):
raise ValueError("se espera tipo Predecesor")
predecesor.apply(self.fexml)
class Eliminar(DIANNominaXML):
@dataclass
@@ -794,29 +628,18 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
fragment.set_element('./TipoNota', '2')
fragment.set_element('./Eliminar/EliminandoPredecesor', None,
# NIAE090
NumeroPred=self.numero,
NumeroPred = self.numero,
# NIAE191
CUNEPred=self.cune,
CUNEPred = self.cune,
# NIAE192
FechaGenPred=self.fecha_generacion
FechaGenPred = self.fecha_generacion
)
def __init__(self):
schema = (
"dian:gov:co:facturaelectronica:NominaIndividualDeAjuste"
" NominaIndividualDeAjusteElectronicaXSD.xsd"
)
super().__init__(
'NominaIndividualDeAjuste',
'./Eliminar',
schemaLocation=schema,
namespace_ajuste=(
'dian:gov:co:facturaelectronica:'
'NominaIndividualDeAjuste'))
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste', './Eliminar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste')
self.informacion_general_version = (
"V1.0: Nota de Ajuste de Documento Soporte de Pago de "
"Nómina Electrónica")
self.informacion_general_version = "V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica"
def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor):
@@ -825,3 +648,4 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
def __init__(self):
super().__init__('NominaIndividualDeAjuste')

View File

@@ -1,5 +1,4 @@
from .. import form
class Amount(form.Amount):
pass

View File

@@ -2,9 +2,9 @@
# al crear objetos de valor
# se debe exportar en __all__
from .deduccion import Deduccion
from .salud import DeduccionSalud
from .fondo_pension import DeduccionFondoPension
from .deduccion import *
from .salud import *
from .fondo_pension import *
__all__ = [
'Deduccion',

View File

@@ -3,7 +3,6 @@ from dataclasses import dataclass
from ..amount import Amount
from .deduccion import Deduccion
@dataclass
class DeduccionFondoPension(Deduccion):
porcentaje: Amount
@@ -11,9 +10,9 @@ class DeduccionFondoPension(Deduccion):
def apply(self, fragment):
fragment.set_element('./FondoPension', None,
append_=True,
append_ = True,
# NIE164
Porcentaje=str(round(self.porcentaje, 2)),
Porcentaje = str(round(self.porcentaje, 2)),
# NIE166
Deduccion=self.deduccion
Deduccion = self.deduccion
)

View File

@@ -3,7 +3,6 @@ from dataclasses import dataclass
from ..amount import Amount
from .deduccion import Deduccion
@dataclass
class DeduccionSalud(Deduccion):
porcentaje: Amount
@@ -11,9 +10,10 @@ class DeduccionSalud(Deduccion):
def apply(self, fragment):
fragment.set_element('./Salud', None,
append_=True,
append_ = True,
# NIE161
Porcentaje=str(round(self.porcentaje, 2)),
Porcentaje = str(round(self.porcentaje, 2)),
# NIE163
Deduccion=self.deduccion
Deduccion = self.deduccion
)

View File

@@ -1,5 +1,4 @@
from .. import form
class Departamento(form.CountrySubentity):
pass

View File

@@ -1,17 +1,8 @@
from .basico import DevengadoBasico
from .transporte import DevengadoTransporte
from .devengado import Devengado
from .horas_extras import (
DevengadoHoraExtra,
DevengadoHorasExtrasDiarias,
DevengadoHorasExtrasNocturnas,
DevengadoHorasRecargoNocturno,
DevengadoHorasExtrasDiariasDominicalesYFestivos,
DevengadoHorasRecargoDiariasDominicalesYFestivos,
DevengadoHorasExtrasNocturnasDominicalesYFestivos,
DevengadoHorasRecargoNocturnoDominicalesYFestivos,
)
from .basico import *
from .transporte import *
from .devengado import *
from .horas_extras import *
__all__ = [
'Devengado',

View File

@@ -14,7 +14,7 @@ class DevengadoBasico(Devengado):
fragment.set_attributes('/Basico',
# NIE069
DiasTrabajados=str(self.dias_trabajados),
DiasTrabajados = str(self.dias_trabajados),
# NIE070
SueldoTrabajado=round(self.sueldo_trabajado, 2)
SueldoTrabajado = round(self.sueldo_trabajado, 2)
)

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List
from ..amount import Amount
from .devengado import Devengado
@@ -30,17 +30,16 @@ class DevengadoHoraExtra:
@dataclass
class DevengadoHorasExtrasDiarias(Devengado):
horas_extras: list[DevengadoHoraExtra]
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HEDs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HED', hora_extra_xml)
@dataclass
class DevengadoHorasExtrasNocturnas(Devengado):
horas_extras: list[DevengadoHoraExtra]
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HENs')
@@ -50,47 +49,43 @@ 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')
for hora_extra in self.horas_extras:
hora_extra.apply('./HRN', hora_extra_xml)
@dataclass
class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra]
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HEDDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HEDDF', hora_extra_xml)
@dataclass
class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra]
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HRDDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HRDDF', hora_extra_xml)
@dataclass
class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra]
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HENDFs')
for hora_extra in self.horas_extras:
hora_extra.apply('./HENDF', hora_extra_xml)
@dataclass
class DevengadoHorasRecargoNocturnoDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra]
horas_extras: List[DevengadoHoraExtra]
def apply(self, fragment):
hora_extra_xml = fragment.fragment('./HRNDFs')

View File

@@ -3,7 +3,6 @@ from dataclasses import dataclass
from ..amount import Amount
from .devengado import Devengado
@dataclass
class DevengadoTransporte(Devengado):
auxilio_transporte: Amount = None
@@ -12,12 +11,11 @@ class DevengadoTransporte(Devengado):
def apply(self, fragment):
fragment.set_element('./Transporte', None,
append_=True,
append_ = True,
# NIE071
AuxilioTransporte=self.auxilio_transporte,
AuxilioTransporte = self.auxilio_transporte,
# NIE072
ViaticoManuAlojS=self.viatico_manutencion,
ViaticoManuAlojS = self.viatico_manutencion,
# NIE073
ViaticoManuAlojNS=(
self.viatico_manutencion_no_salarial)
ViaticoManuAlojNS = self.viatico_manutencion_no_salarial
)

View File

@@ -4,7 +4,6 @@ from ..pais import Pais
from ..departamento import Departamento
from ..municipio import Municipio
@dataclass
class Empleador:
razon_social: str
@@ -18,17 +17,19 @@ class Empleador:
def apply(self, fragment):
fragment.set_attributes('./Empleador',
# NIE033
NIT=self.nit,
NIT = self.nit,
# NIE034
DV=self.dv,
DV = self.dv,
# NIE035
Pais=self.pais.code,
Pais = self.pais.code,
# NIE036
DepartamentoEstado=self.departamento.code,
DepartamentoEstado = self.departamento.code,
# NIE037
MunicipioCiudad=self.municipio.code,
MunicipioCiudad = self.municipio.code,
# NIE038
Direccion=self.direccion,
Direccion = self.direccion,
RazonSocial=self.razon_social
)

View File

@@ -3,7 +3,6 @@ import datetime
from facho import fe
class Habilitacion:
@dataclass
@@ -17,18 +16,18 @@ class Habilitacion:
self.metadata = metadata
def generar(self, zipname, fecha):
fe.DianZIP(open(zipname, 'w'))
nominas = []
dianzip = fe.DianZIP(open(zipname, 'w'))
fechabase = datetime.datetime.now()
consecutivo = 0
for _ in range(1, 11):
consecutivo += 1
fechabase += datetime.timedelta(days=1)
self._crear_nomina_individual()
nomina = self._crear_nomina_individual()
# pag 96
"nie%010d%s%08x.xml" % (
int(self.nit), fecha.strftime('%s'), consecutivo)
nombre = "nie%010d%s%08x.xml" % (int(self.nit), fecha.strftime('%s'), consecutivo)
def _crear_nomina_individual_reemplazar(self, nomina, fechabase):
metadata = self.metadata
@@ -37,15 +36,10 @@ class Habilitacion:
nomina_ajuste = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
self._poblar_nomina(nomina_ajuste, metadata, fecha, prefijo='R')
nomina.informacion_general()
informacion_general = nomina.informacion_general()
def _poblar_nomina(
self,
nomina,
metadata,
fecha,
prefijo='N',
consecutivo='0001'):
def _poblar_nomina(self, nomina, metadata, fecha, prefijo='N', consecutivo='0001'):
nomina.asignar_fecha_pago(fecha)
nomina.asignar_metadata(fe.nomina.Metadata(
@@ -54,14 +48,14 @@ class Habilitacion:
consecutivo=consecutivo
),
lugar_generacion=fe.nomina.Lugar(
pais=fe.nomina.Pais(
code='CO'
pais = fe.nomina.Pais(
code = 'CO'
),
departamento=fe.nomina.Departamento(
code='05'
departamento = fe.nomina.Departamento(
code = '05'
),
municipio=fe.nomina.Municipio(
code='05001'
municipio = fe.nomina.Municipio(
code = '05001'
),
),
proveedor=fe.nomina.Proveedor(
@@ -80,12 +74,12 @@ class Habilitacion:
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion=fecha,
hora_generacion='20:09:00-05:00',
tipo_ambiente=fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin=metadata.software_pin,
periodo_nomina=fe.nomina.PeriodoNomina(code='1'),
tipo_moneda=fe.nomina.TipoMoneda(code='COP')
fecha_generacion = fecha,
hora_generacion = '20:09:00-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRUEBAS,
software_pin = metadata.software_pin,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_pago(fe.nomina.Pago(
@@ -97,53 +91,53 @@ class Habilitacion:
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
nit=metadata.nit,
dv='0',
pais=fe.nomina.Pais(
code='CO'
nit = metadata.nit,
dv = '0',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento=fe.nomina.Departamento(
code='05'
departamento = fe.nomina.Departamento(
code = '05'
),
municipio=fe.nomina.Municipio(
code='05001'
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion='calle etrivial'
direccion = 'calle etrivial'
))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato=fe.nomina.TipoContrato(
code='1'
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo=False,
tipo_documento=fe.nomina.TipoDocumento(
code='11'
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido='gnu',
segundo_apellido='emacs',
primer_nombre='facho',
lugar_trabajo=fe.nomina.LugarTrabajo(
pais=fe.nomina.Pais(code='CO'),
departamento=fe.nomina.Departamento(code='05'),
municipio=fe.nomina.Municipio(code='05001'),
direccion='calle facho'
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento=metadata.nit,
tipo=fe.nomina.TipoTrabajador(
code='01'
numero_documento = metadata.nit,
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral=True,
sueldo=fe.nomina.Amount(1_500_000)
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados=60,
sueldo_trabajado=fe.nomina.Amount(3_500_000)
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(3_500_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje=fe.nomina.Amount(19),
deduccion=fe.nomina.Amount(1_000_000)
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionFondoPension(
@@ -158,3 +152,4 @@ class Habilitacion:
nomina = fe.nomina.DIANNominaIndividual()
self._poblar_nomina(nomina, metadata, fecha)

View File

@@ -5,7 +5,6 @@ from .departamento import Departamento
from .municipio import Municipio
from facho.fe.data.dian import codelist
@dataclass
class Lugar:
pais: Pais

View File

@@ -1,5 +1,5 @@
from .. import form
class Municipio(form.City):
pass

View File

@@ -3,7 +3,6 @@ from dataclasses import dataclass
from .forma_pago import FormaPago
from .metodo_pago import MetodoPago
@dataclass
class Pago:
forma: FormaPago
@@ -12,6 +11,6 @@ class Pago:
def apply(self, fragment):
fragment.set_attributes('./Pago',
# NIE064
Forma=self.forma.code,
Forma = self.forma.code,
# NIE065
Metodo=self.metodo.code)
Metodo = self.metodo.code)

View File

@@ -2,7 +2,6 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist
@dataclass
class FormaPago:
code: str

View File

@@ -2,7 +2,6 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist
@dataclass
class MetodoPago:
code: str
@@ -12,3 +11,4 @@ class MetodoPago:
if self.code not in codelist.MediosPago:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.MediosPago[self.code]['name']

View File

@@ -1,5 +1,4 @@
from .. import form
class Pais(form.Country):
pass

View File

@@ -1,12 +1,13 @@
from dataclasses import dataclass, field
from dataclasses import dataclass
from ..amount import Amount
from .tipo_contrato import TipoContrato
from .tipo_documento import TipoDocumento
from .lugar_trabajo import LugarTrabajo
from .tipo_trabajador import TipoTrabajador
from .sub_tipo_trabajador import SubTipoTrabajador
from .tipo_contrato import *
from .tipo_documento import *
from .lugar_trabajo import *
from .tipo_trabajador import *
from .sub_tipo_trabajador import *
@dataclass
@@ -28,47 +29,45 @@ class Trabajador:
codigo_trabajador: str = None
otros_nombres: str = None
sub_tipo: SubTipoTrabajador = field(
default_factory=lambda: SubTipoTrabajador(code="00")
)
sub_tipo: SubTipoTrabajador = SubTipoTrabajador(code='00')
def apply(self, fragment):
fragment.set_attributes(
"./Trabajador",
# NIE041
TipoTrabajador=self.tipo.code,
# NIE042
SubTipoTrabajador=self.sub_tipo.code,
# NIE043
AltoRiesgoPension=str(self.alto_riesgo).lower(),
# NIE044
TipoDocumento=self.tipo_documento.code,
# NIE045
NumeroDocumento=self.numero_documento,
# NIE046
PrimerApellido=self.primer_apellido,
# NIE047
SegundoApellido=self.segundo_apellido,
# NIE048
PrimerNombre=self.primer_nombre,
# NIE049
OtrosNombres=self.otros_nombres,
# NIE050
LugarTrabajoPais=self.lugar_trabajo.pais.code,
# NIE051
LugarTrabajoDepartamentoEstado=(
self.lugar_trabajo.departamento.code
),
# NIE052
LugarTrabajoMunicipioCiudad=self.lugar_trabajo.municipio.code,
# NIE053
LugarTrabajoDireccion=self.lugar_trabajo.direccion,
# NIE056
SalarioIntegral=str(self.salario_integral).lower(),
# NIE061
TipoContrato=self.tipo_contrato.code,
# NIE062
Sueldo=str(self.sueldo),
# NIE063
CodigoTrabajador=self.codigo_trabajador,
)
fragment.set_attributes('./Trabajador',
# NIE041
TipoTrabajador = self.tipo.code,
# NIE042
SubTipoTrabajador = self.sub_tipo.code,
# NIE043
AltoRiesgoPension = str(self.alto_riesgo).lower(),
# NIE044
TipoDocumento = self.tipo_documento.code,
# NIE045
NumeroDocumento = self.numero_documento,
# NIE046
PrimerApellido = self.primer_apellido,
# NIE047
SegundoApellido = self.segundo_apellido,
# NIE048
PrimerNombre = self.primer_nombre,
# NIE049
OtrosNombres = self.otros_nombres,
# NIE050
LugarTrabajoPais = self.lugar_trabajo.pais.code,
# NIE051
LugarTrabajoDepartamentoEstado = self.lugar_trabajo.departamento.code,
# NIE052
LugarTrabajoMunicipioCiudad = self.lugar_trabajo.municipio.code,
# NIE053
LugarTrabajoDireccion = self.lugar_trabajo.direccion,
# NIE056
SalarioIntegral = str(self.salario_integral).lower(),
# NIE061
TipoContrato = self.tipo_contrato.code,
# NIE062
Sueldo = str(self.sueldo),
# NIE063
CodigoTrabajador = self.codigo_trabajador
)

View File

@@ -1,9 +1,10 @@
from dataclasses import dataclass
from . import *
from ..pais import Pais
from ..departamento import Departamento
from ..municipio import Municipio
@dataclass
class LugarTrabajo:
pais: Pais

View File

@@ -2,13 +2,12 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist
@dataclass
class SubTipoTrabajador:
code: str
name: str = ""
name: str = ''
def __post_init__(self):
if self.code not in codelist.SubTipoTrabajador:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.SubTipoTrabajador[self.code]["name"]
self.name = codelist.SubTipoTrabajador[self.code]['name']

View File

@@ -2,13 +2,14 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist
@dataclass
class TipoContrato:
code: str
name: str = ""
name: str = ''
def __post_init__(self):
if self.code not in codelist.TipoContrato:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoContrato[self.code]["name"]
self.name = codelist.TipoContrato[self.code]['name']

View File

@@ -2,13 +2,12 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist
@dataclass
class TipoDocumento:
code: str
name: str = ""
name: str = ''
def __post_init__(self):
if self.code not in codelist.TipoIdFiscal:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoIdFiscal[self.code]["name"]
self.name = codelist.TipoIdFiscal[self.code]['name']

View File

@@ -2,13 +2,12 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist
@dataclass
class TipoTrabajador:
code: str
name: str = ""
name: str = ''
def __post_init__(self):
if self.code not in codelist.TipoTrabajador:
raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoTrabajador[self.code]["name"]
self.name = codelist.TipoTrabajador[self.code]['name']

1499
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +0,0 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "facho"
version = "0.1.0"
description = "Facturacion Electronica Colombia"
readme = "README.rst"
license = { text = "GPL-3.0-or-later" }
authors = [
{ name = "Jovany Leandro G.C", email = "bit4bit@riseup.net" },
]
keywords = ["facho"]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
requires-python = ">=3.10"
dependencies = [
"Click>=8.1.7",
"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",
"xmlschema>=3.0.0",
]
[project.scripts]
facho = "facho.cli:main"
[project.urls]
Repository = "https://github.com/bit4bit/facho"
[tool.poetry.group.dev.dependencies]
build = ">=1.2.2"
coverage = ">=7.8.0"
flake8 = ">=7.0.0"
pytest = ">=9.1.1,<10.0.0"
tox = ">=4.23.2"
[tool.setuptools]
packages = { find = { exclude = ["tests*"] } }
[tool.setuptools.package-data]
"*" = ["*.gc", "*.xsd", "politicadefirmav2.pdf"]
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.bumpversion]
current_version = "0.1.0"
commit = true
tag = true
[[tool.bumpversion.files]]
filename = "pyproject.toml"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'
[[tool.bumpversion.files]]
filename = "facho/__init__.py"
search = "__version__ = '{current_version}'"
replace = "__version__ = '{new_version}'"

25
setup.cfg Normal file
View File

@@ -0,0 +1,25 @@
[bumpversion]
current_version = 0.1.0
commit = True
tag = True
[bumpversion:file:setup.py]
search = version='{current_version}'
replace = version='{new_version}'
[bumpversion:file:facho/__init__.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'
[bdist_wheel]
universal = 1
[flake8]
exclude = docs
[aliases]
# Define setup.py command aliases here
test = pytest
[tool:pytest]
collect_ignore = ['setup.py']

71
setup.py Normal file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
"""The setup script."""
from setuptools import setup, find_packages
with open('README.rst') as readme_file:
readme = readme_file.read()
with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = ['Click>=6.0',
'zeep==4.0.0',
'lxml==4.6.3',
'cryptography==3.3.2',
'pyOpenSSL==20.0.1',
'xmlsig==0.1.7',
'xades==0.2.2',
'xmlsec==1.3.12',
# usamos esta dependencia en runtime
# para forzar uso de policy_id de archivo local
'mock>=2.0.0',
'xmlschema>=1.8']
setup_requirements = ['pytest-runner', ]
test_requirements = ['pytest', ]
setup(
author="Jovany Leandro G.C",
author_email='bit4bit@riseup.net',
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
description="Facturacion Electronica Colombia",
entry_points={
'console_scripts': [
'facho=facho.cli:main',
],
},
install_requires=requirements,
license="GNU General Public License v3",
long_description=readme + '\n\n' + history,
long_description_content_type='text/x-rst',
include_package_data=True,
package_data = {
# If any package contains *.txt or *.rst files, include them:
'': ['*.gc', '*.xsd', 'politicadefirmav2.pdf']
},
keywords='facho',
name='facho',
packages=find_packages(exclude=("tests",)),
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/bit4bit/facho',
version='0.2.0',
zip_safe=False,
)

View File

@@ -1 +0,0 @@
907e4444decc9e59c160a2fb3b6659b33dc5b632a5008922b9a62f83f757b1c448e47f5867f2b50dbdb96f48c7681168

View File

View File

@@ -2,82 +2,70 @@ import pytest
import facho.fe.form as form
from datetime import datetime
@pytest.fixture
def simple_debit_note_without_lines():
inv = form.DebitNote(form.InvoiceDocumentReference(
'1234', 'xx', datetime.now()))
inv = form.DebitNote(form.InvoiceDocumentReference('1234', 'xx', datetime.now()))
inv.set_period(datetime.now(), datetime.now())
inv.set_issue(datetime.now())
inv.set_ident('ABC123')
inv.set_operation_type('30')
inv.set_payment_mean(form.PaymentMean(
form.PaymentMean.DEBIT, '41', datetime.now(), '1234'))
inv.set_payment_mean(form.PaymentMean(form.PaymentMean.DEBIT, '41', datetime.now(), '1234'))
inv.set_supplier(form.Party(
name='facho-supplier',
ident=form.PartyIdentification('123', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.set_customer(form.Party(
name='facho-customer',
ident=form.PartyIdentification('321', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-customer',
ident = form.PartyIdentification('321', '', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
return inv
@pytest.fixture
def simple_credit_note_without_lines():
inv = form.CreditNote(
form.InvoiceDocumentReference(
'1234', 'xx', datetime.now()))
inv = form.CreditNote(form.InvoiceDocumentReference('1234', 'xx', datetime.now()))
inv.set_period(datetime.now(), datetime.now())
inv.set_issue(datetime.now())
inv.set_ident('ABC123')
inv.set_operation_type('20')
inv.set_payment_mean(
form.PaymentMean(
form.PaymentMean.DEBIT,
'41',
datetime.now(),
'1234'))
inv.set_payment_mean(form.PaymentMean(form.PaymentMean.DEBIT, '41', datetime.now(), '1234'))
inv.set_supplier(form.Party(
name='facho-supplier',
ident=form.PartyIdentification('123', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.set_customer(form.Party(
name='facho-customer',
ident=form.PartyIdentification('321', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-customer',
ident = form.PartyIdentification('321', '', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
return inv
@pytest.fixture
def simple_invoice_without_lines():
inv = form.NationalSalesInvoice()
@@ -85,37 +73,31 @@ def simple_invoice_without_lines():
inv.set_issue(datetime.now())
inv.set_ident('ABC123')
inv.set_operation_type('10')
inv.set_payment_mean(
form.PaymentMean(
form.PaymentMean.DEBIT,
'41',
datetime.now(),
'1234'))
inv.set_payment_mean(form.PaymentMean(form.PaymentMean.DEBIT, '41', datetime.now(), '1234'))
inv.set_supplier(form.Party(
name='facho-supplier',
ident=form.PartyIdentification('123', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.set_customer(form.Party(
name='facho-customer',
ident=form.PartyIdentification('321', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-customer',
ident = form.PartyIdentification('321', '', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
return inv
@pytest.fixture
def simple_invoice():
inv = form.NationalSalesInvoice()
@@ -123,48 +105,42 @@ def simple_invoice():
inv.set_issue(datetime.now())
inv.set_ident('ABC123')
inv.set_operation_type('10')
inv.set_payment_mean(
form.PaymentMean(
form.PaymentMean.DEBIT,
'41',
datetime.now(),
' 1234'))
inv.set_payment_mean(form.PaymentMean(form.PaymentMean.DEBIT, '41', datetime.now(), ' 1234'))
inv.set_supplier(form.Party(
name='facho-supplier',
ident=form.PartyIdentification('123', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-supplier',
ident = form.PartyIdentification('123','', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.set_customer(form.Party(
name='facho-customer',
ident=form.PartyIdentification('321', '', '31'),
responsability_code=form.Responsability(['ZZ']),
responsability_regime_code='48',
organization_code='1',
address=form.Address(
name = 'facho-customer',
ident = form.PartyIdentification('321','', '31'),
responsability_code = form.Responsability(['O-07']),
responsability_regime_code = '48',
organization_code = '1',
address = form.Address(
'', '', form.City('05001', 'Medellín'),
form.Country('CO', 'Colombia'),
form.CountrySubentity('05', 'Antioquia'))
))
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(form.Amount(100.0), '01', ''),
tax=form.TaxTotal(
tax_amount=form.Amount(0.0),
taxable_amount=form.Amount(0.0),
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem( 9999),
price = form.Price(form.Amount(100.0), '01', ''),
tax = form.TaxTotal(
tax_amount = form.Amount(0.0),
taxable_amount = form.Amount(0.0),
subtotals = [
form.TaxSubTotal(
percent=19.0,
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
return inv

View File

@@ -14,7 +14,6 @@ def test_amount_positive():
with pytest.raises(ValueError):
form.Amount(-1.0)
def test_amount_equals():
price1 = form.Amount(110.0)
price2 = form.Amount(100 + 10.0)
@@ -23,7 +22,6 @@ def test_amount_equals():
assert price1 == form.Amount(10) * form.Amount(10) + form.Amount(10)
assert form.Amount(110) == (form.Amount(1.10) * form.Amount(100))
def test_round_half_even():
# https://www.w3.org/TR/xpath-functions-31/#func-round-half-to-even
assert form.Amount(0.5).round(0).float() == 0.0
@@ -32,20 +30,16 @@ def test_round_half_even():
assert form.Amount(3.567812e+3).round(2).float() == 3567.81e0
assert form.Amount(4.7564e-3).round(2).float() == 0.0e0
def test_round():
# Entre 0 y 5 Mantener el dígito menos significativo
assert form.Amount(1.133).round(2) == form.Amount(1.13)
# Entre 6 y 9 Incrementar el dígito menos significativo
assert form.Amount(1.166).round(2) == form.Amount(1.17)
# 5, y el segundo dígito siguiente al dígito menos significativo es cero o
# par Mantener el dígito menos significativo
# 5, y el segundo dígito siguiente al dígito menos significativo es cero o par Mantener el dígito menos significativo
assert str(form.Amount(1.1542).round(2)) == str(form.Amount(1.15))
# 5, y el segundo dígito siguiente al dígito menos significativo es impar
# Incrementar el dígito menos significativo
# 5, y el segundo dígito siguiente al dígito menos significativo es impar Incrementar el dígito menos significativo
assert str(form.Amount(1.1563).round(2)) == str(form.Amount(1.16))
def test_amount_truncate():
assert form.Amount(1.1569).truncate_as_string(2) == '1.15'
assert form.Amount(587.0700).truncate_as_string(2) == '587.07'
@@ -55,6 +49,5 @@ def test_amount_truncate():
assert form.Amount(10000.02245).truncate_as_string(2) == '10000.02'
assert form.Amount(10000.02357).truncate_as_string(2) == '10000.02'
def test_amount_format():
assert str(round(form.Amount(1.1569), 2)) == '1.16'
assert str(round(form.Amount(1.1569),2)) == '1.16'

View File

@@ -2,15 +2,16 @@
# -*- coding: utf-8 -*-
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
# from datetime import datetime
from datetime import datetime
# import pytest
# from facho.fe import form_xml
import pytest
from facho.fe import form_xml
# import helpers
import helpers
def test_xml_with_required_elements():
doc = form_xml.AttachedDocument(id='123')
xml = doc.toFachoXML()
assert xml.get_element_text('/atd:AttachedDocument/cbc:ID') == '123'
# def test_xml_with_required_elements():
# doc = form_xml.AttachedDocument(id='123')
# xml = doc.toFachoXML()
# assert xml.get_element_text('/atd:AttachedDocument/cbc:ID') == '123'

View File

@@ -4,7 +4,12 @@
# this repository contains the full copyright notices and license terms.
from facho.fe.client import dian
from facho import facho
import pytest
from datetime import datetime
class FakeDianClient(dian.DianClient):
def __init__(self, username, password, resp):

View File

@@ -4,26 +4,21 @@
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
from facho.fe.data.dian import codelist
import pytest
from facho.fe.data.dian import codelist
def test_tiporesponsabilidad():
assert codelist.TipoResponsabilidad.short_name == 'TipoResponsabilidad'
assert codelist.TipoResponsabilidad.by_name(
'Autorretenedor')['name'] == 'Autorretenedor'
assert codelist.TipoResponsabilidad.by_name('Autorretenedor')['name'] == 'Autorretenedor'
def test_tipoorganizacion():
assert codelist.TipoOrganizacion.short_name == 'TipoOrganizacion'
assert codelist.TipoOrganizacion.by_name(
'Persona Natural')['name'] == 'Persona Natural'
assert codelist.TipoOrganizacion.by_name('Persona Natural')['name'] == 'Persona Natural'
def test_tipodocumento():
assert codelist.TipoDocumento.short_name == 'TipoDocumento'
assert codelist.TipoDocumento.by_name(
'Factura electrónica de Venta')['code'] == '01'
assert codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'] == '01'
def test_departamento():
assert codelist.Departamento['05']['name'] == 'Antioquia'

View File

@@ -7,10 +7,10 @@
import pytest
# from click.testing import CliRunner
#from click.testing import CliRunner
from facho import facho
# from facho import cli
#from facho import cli
def test_facho_xml():
@@ -20,19 +20,18 @@ def test_facho_xml():
invoice.text = 'Test'
assert xml.tostring() == '<root><Invoice>Test</Invoice></root>'
xml.find_or_create_element('/root/Invoice/Line')
invoice_line = xml.find_or_create_element('/root/Invoice/Line')
assert xml.tostring() == '<root><Invoice>Test<Line/></Invoice></root>'
def test_facho_xml_with_attr():
xml = facho.FachoXML('root')
xml.find_or_create_element('/root/Invoice[id=123]')
invoice = xml.find_or_create_element('/root/Invoice[id=123]')
assert xml.tostring() == '<root><Invoice id="123"/></root>'
def test_facho_xml_idempotent():
xml = facho.FachoXML('root')
xml.find_or_create_element('/root/Invoice')
invoice = xml.find_or_create_element('/root/Invoice')
assert xml.tostring() == '<root><Invoice/></root>'
xml.find_or_create_element('/root/Invoice')
@@ -47,7 +46,6 @@ def test_facho_xml_idempotent():
xml.find_or_create_element('/root/Invoice/Line')
assert xml.tostring() == '<root><Invoice><Line/></Invoice></root>'
def test_facho_xml_aliases():
xml = facho.FachoXML('root')
xml.register_alias_xpath('Invoice', '/root/Invoice')
@@ -56,42 +54,31 @@ def test_facho_xml_aliases():
invoice.text = 'Test'
assert xml.tostring() == '<root><Invoice>Test</Invoice></root>'
def test_facho_xmlns():
xml = facho.FachoXML('root', nsmap={
'ext': 'https://ext',
'sts': 'https://sts',
})
invoiceAuthorization = xml.find_or_create_element(
'/root/ext:UBLExtensions/ext:UBLExtension/'
'ext:ExtensionContent/sts:DianExtensions/'
'sts:InvoiceControl/sts:InvoiceAuthorization')
assert xml.tostring().strip() == (
'<root xmlns:ext="https://ext" xmlns:sts="https://sts">'
'<ext:UBLExtensions>'
'<ext:UBLExtension>'
'<ext:ExtensionContent>'
'<sts:DianExtensions>'
'<sts:InvoiceControl>'
'<sts:InvoiceAuthorization/>'
'</sts:InvoiceControl></sts:DianExtensions>'
'</ext:ExtensionContent></ext:UBLExtension>'
'</ext:UBLExtensions></root>')
invoiceAuthorization = xml.find_or_create_element('/root/ext:UBLExtensions/ext:UBLExtension/'
'ext:ExtensionContent/sts:DianExtensions/'
'sts:InvoiceControl/sts:InvoiceAuthorization')
assert xml.tostring().strip() == '<root xmlns:ext="https://ext" xmlns:sts="https://sts"><ext:UBLExtensions>'\
'<ext:UBLExtension>'\
'<ext:ExtensionContent>'\
'<sts:DianExtensions>'\
'<sts:InvoiceControl>'\
'<sts:InvoiceAuthorization/>'\
'</sts:InvoiceControl></sts:DianExtensions></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions></root>'
invoiceAuthorization.text = '123456789'
assert xml.tostring().strip() == (
'<root xmlns:ext="https://ext" xmlns:sts="https://sts">'
'<ext:UBLExtensions>'
'<ext:UBLExtension>'
'<ext:ExtensionContent>'
'<sts:DianExtensions>'
'<sts:InvoiceControl>'
'<sts:InvoiceAuthorization>123456789</sts:InvoiceAuthorization>'
'</sts:InvoiceControl></sts:DianExtensions>'
'</ext:ExtensionContent></ext:UBLExtension>'
'</ext:UBLExtensions></root>')
assert xml.tostring().strip() == '<root xmlns:ext="https://ext" xmlns:sts="https://sts"><ext:UBLExtensions>'\
'<ext:UBLExtension>'\
'<ext:ExtensionContent>'\
'<sts:DianExtensions>'\
'<sts:InvoiceControl>'\
'<sts:InvoiceAuthorization>123456789</sts:InvoiceAuthorization>'\
'</sts:InvoiceControl></sts:DianExtensions></ext:ExtensionContent></ext:UBLExtension></ext:UBLExtensions></root>'
def test_facho_xmlns_idempotent():
xml = facho.FachoXML('root', nsmap={
@@ -100,22 +87,16 @@ def test_facho_xmlns_idempotent():
})
xml.find_or_create_element('/root/ext:Extension/sts:Sotoros')
assert xml.tostring() == (
'<root xmlns:ext="https://ext" xmlns:sts="https://sts">'
'<ext:Extension><sts:Sotoros/></ext:Extension></root>')
assert xml.tostring() == '<root xmlns:ext="https://ext" xmlns:sts="https://sts"><ext:Extension><sts:Sotoros/></ext:Extension></root>'
xml.find_or_create_element('/root/ext:Extension/sts:Sotoros')
assert xml.tostring() == (
'<root xmlns:ext="https://ext" xmlns:sts="https://sts">'
'<ext:Extension><sts:Sotoros/></ext:Extension></root>')
assert xml.tostring() == '<root xmlns:ext="https://ext" xmlns:sts="https://sts"><ext:Extension><sts:Sotoros/></ext:Extension></root>'
def test_facho_xml_set_element_with_format():
xml = facho.FachoXML('root')
xml.set_element('/root/Invoice', 1, format_='%02d')
invoice = xml.set_element('/root/Invoice', 1, format_='%02d')
assert xml.tostring() == '<root><Invoice>01</Invoice></root>'
def test_facho_xml_fragment():
xml = facho.FachoXML('root')
invoice = xml.fragment('/root/Invoice')
@@ -135,10 +116,7 @@ def test_facho_xml_fragments():
line = xml.fragment('/Invoice/Line', append=True)
line.set_element('/Line/Id', 3)
assert xml.tostring() == (
'<Invoice><Line><Id>1</Id></Line><Line><Id>2</Id></Line>'
'<Line><Id>3</Id></Line></Invoice>')
assert xml.tostring() == '<Invoice><Line><Id>1</Id></Line><Line><Id>2</Id></Line><Line><Id>3</Id></Line></Invoice>'
def test_facho_xml_nested_fragments():
xml = facho.FachoXML('Invoice')
@@ -150,11 +128,7 @@ def test_facho_xml_nested_fragments():
party.set_element('/Party/LastName', 'test')
assert xml.tostring() == (
'<Invoice><Party><Name>test</Name>'
'<Address><Line>line 1</Line></Address>'
'<LastName>test</LastName></Party></Invoice>')
assert xml.tostring() == '<Invoice><Party><Name>test</Name><Address><Line>line 1</Line></Address><LastName>test</LastName></Party></Invoice>'
def test_facho_xml_get_element_text_of_fragment():
xml = facho.FachoXML('root')
@@ -163,7 +137,6 @@ def test_facho_xml_get_element_text_of_fragment():
assert invoice.get_element_text('/Invoice/Id') == '1'
def test_facho_xml_get_element_text():
xml = facho.FachoXML('Invoice')
xml.set_element('/Invoice/ID', 'ABC123')
@@ -174,7 +147,6 @@ def test_facho_xml_get_element_text():
line.set_element('/Line/Quantity', 5)
assert line.get_element_text('/Line/Quantity', format_=int) == 5
def test_facho_xml_get_element_text_next_child():
xml = facho.FachoXML('Invoice')
xml.set_element('/Invoice/ID', 'ABC123')
@@ -194,32 +166,25 @@ def test_facho_xml_set_element_relative():
assert xml.get_element_text('/Invoice/ID') == 'ABC123'
def test_facho_xml_set_element_relative_with_namespace():
xml = facho.FachoXML(
'{%s}Invoice' %
('http://www.dian.gov.co/contratos/facturaelectronica/v1'),
nsmap={
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1'})
xml = facho.FachoXML('{%s}Invoice' % ('http://www.dian.gov.co/contratos/facturaelectronica/v1'),
nsmap={'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1'})
xml.set_element('./ID', 'ABC123')
assert xml.get_element_text('/fe:Invoice/ID') == 'ABC123'
def test_facho_xml_fragment_relative():
xml = facho.FachoXML('root')
invoice = xml.fragment('./Invoice')
invoice.set_element('./Id', 1)
assert xml.tostring() == '<root><Invoice><Id>1</Id></Invoice></root>'
def test_facho_xml_get_element_fragment_relative():
xml = facho.FachoXML('root')
invoice = xml.fragment('./Invoice')
invoice.set_element('./Id', 1)
assert invoice.get_element_text('./Id') == '1'
def test_facho_xml_replacement_for():
xml = facho.FachoXML('root')
xml.placeholder_for('./child/type')
@@ -227,47 +192,37 @@ def test_facho_xml_replacement_for():
'./child/code', 'test')
assert xml.tostring() == '<root><child><code>test</code></child></root>'
def test_facho_xml_set_element_content_invalid_validation():
xml = facho.FachoXML('root')
with pytest.raises(facho.FachoValueInvalid):
with pytest.raises(facho.FachoValueInvalid) as e:
xml.set_element_validator('./Id', lambda text, attrs: text == 'mero')
xml.set_element('./Id', 'bad')
def test_facho_xml_set_element_content_valid_validation():
xml = facho.FachoXML('root')
xml.set_element_validator('./Id', lambda text, attrs: text == 'mero')
xml.set_element('./Id', 'mero')
def test_facho_xml_set_element_attribute_invalid_validation():
xml = facho.FachoXML('root')
with pytest.raises(facho.FachoValueInvalid):
xml.set_element_validator(
'./Id', lambda text, attrs: attrs['code'] == 'ABC')
xml.set_element('./Id', 'mero', code='CBA')
with pytest.raises(facho.FachoValueInvalid) as e:
xml.set_element_validator('./Id', lambda text, attrs: attrs['code'] == 'ABC')
xml.set_element('./Id', 'mero', code = 'CBA')
def test_facho_xml_set_element_attribute_valid_validation():
xml = facho.FachoXML('root')
xml.set_element_validator(
'./Id',
lambda text,
attrs: attrs['code'] == 'ABC')
xml.set_element('./Id', 'mero', code='ABC')
xml.set_element_validator('./Id', lambda text, attrs: attrs['code'] == 'ABC')
xml.set_element('./Id', 'mero', code = 'ABC')
def test_facho_xml_get_element_attribute():
xml = facho.FachoXML('root')
xml.set_element('./Id', 'mero', code='ABC')
xml.set_element('./Id', 'mero', code = 'ABC')
assert xml.get_element_attribute('/root/Id', 'code') == 'ABC'
def test_facho_xml_keep_orden_slibing():
xml = facho.FachoXML('root')
xml.find_or_create_element('./A')
@@ -278,7 +233,6 @@ def test_facho_xml_keep_orden_slibing():
assert xml.tostring() == '<root><A/><A/><B/><B/><C/></root>'
def test_facho_xml_placeholder_optional():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
@@ -287,7 +241,6 @@ def test_facho_xml_placeholder_optional():
assert xml.tostring() == '<root><A/><C/></root>'
def test_facho_xml_placeholder_append_to_optional():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
@@ -297,7 +250,6 @@ def test_facho_xml_placeholder_append_to_optional():
xml.find_or_create_element('./B')
assert xml.tostring() == '<root><A/><B/><C/></root>'
def test_facho_xml_placeholder_set_element_to_optional():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
@@ -307,7 +259,6 @@ def test_facho_xml_placeholder_set_element_to_optional():
xml.set_element('./B', '2')
assert xml.tostring() == '<root><A/><B>2</B><C/></root>'
def test_facho_xml_placeholder_set_element_to_optional_with_append():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
@@ -324,8 +275,8 @@ def test_facho_xml_set_attributes():
xml.find_or_create_element('./A')
xml.set_attributes('./A',
value1='1',
value2='2'
value1 = '1',
value2 = '2'
)
assert xml.get_element_attribute('/root/A', 'value1') == '1'
assert xml.get_element_attribute('/root/A', 'value2') == '2'
@@ -336,14 +287,13 @@ def test_facho_xml_set_attributes_not_set_optional():
xml.find_or_create_element('./A')
xml.set_attributes('./A',
value1=None,
value2='2'
value1 = None,
value2 = '2'
)
with pytest.raises(KeyError):
xml.get_element_attribute('/root/A', 'value1')
assert xml.get_element_attribute('/root/A', 'value2') == '2'
def test_facho_xml_placeholder_with_fragment():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
@@ -356,9 +306,7 @@ def test_facho_xml_placeholder_with_fragment():
AA = xml.fragment('./AA/Child', append=True)
assert xml.tostring() == (
'<root><A/><AA><Child><B/><B/></Child><Child/></AA><AAA/></root>')
assert xml.tostring() == '<root><A/><AA><Child><B/><B/></Child><Child/></AA><AAA/></root>'
def test_facho_xml_create_on_first_append():
xml = facho.FachoXML('root')
@@ -366,7 +314,6 @@ def test_facho_xml_create_on_first_append():
xml.find_or_create_element('./A', append=True)
assert xml.tostring() == '<root><A/></root>'
def test_facho_xml_create_on_first_append_multiple_appends():
xml = facho.FachoXML('root')
@@ -377,7 +324,6 @@ def test_facho_xml_create_on_first_append_multiple_appends():
xml.find_or_create_element('./C', append=True)
assert xml.tostring() == '<root><B/><A/><A/><A/><C/></root>'
def test_facho_xml_fragment_create_on_first_append():
xml = facho.FachoXML('root')
@@ -387,7 +333,6 @@ def test_facho_xml_fragment_create_on_first_append():
A.find_or_create_element('./C')
assert xml.tostring() == '<root><A><B/></A><A><C/></A></root>'
def test_facho_xml_placeholder_optional_and_fragment():
xml = facho.FachoXML('root')
@@ -401,7 +346,6 @@ def test_facho_xml_placeholder_optional_and_fragment():
assert xml.tostring() == '<root><A><AA><B/><C/></AA></A></root>'
def test_facho_xml_placeholder_optional_and_set_attributes():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
@@ -410,7 +354,6 @@ def test_facho_xml_placeholder_optional_and_set_attributes():
assert xml.get_element_attribute('/root/A', 'prueba') == 'OK'
assert xml.tostring() == '<root><A prueba="OK"/></root>'
def test_facho_xml_placeholder_optional_and_fragment_with_set_element():
xml = facho.FachoXML('root')
@@ -422,19 +365,17 @@ def test_facho_xml_placeholder_optional_and_fragment_with_set_element():
assert xml.tostring() == '<root><A><AA prueba="OK"/></A></root>'
assert xml.get_element_attribute('/root/A/AA', 'prueba') == 'OK'
def test_facho_xml_exist_element():
xml = facho.FachoXML('root')
xml.placeholder_for('./A')
assert xml.exist_element('/root/A') is False
assert xml.exist_element('/root/A') == False
assert xml.tostring() == '<root><A/></root>'
xml.find_or_create_element('./A')
assert xml.exist_element('/root/A')
assert xml.exist_element('/root/A') == True
assert xml.tostring() == '<root><A/></root>'
def test_facho_xml_query_element_text_or_attribute():
xml = facho.FachoXML('root')
@@ -443,7 +384,6 @@ def test_facho_xml_query_element_text_or_attribute():
assert xml.get_element_text_or_attribute('/root/A') == 'contenido'
assert xml.get_element_text_or_attribute('/root/A/@clave') == 'valor'
def test_facho_xml_query_element_text_or_attribute_from_fragment():
xml = facho.FachoXML('root')
@@ -452,7 +392,6 @@ def test_facho_xml_query_element_text_or_attribute_from_fragment():
assert invoice.get_element_text_or_attribute('/Invoice/A') == 'contenido'
def test_facho_xml_build_xml_absolute():
xml = facho.FachoXML('root')
@@ -461,23 +400,18 @@ def test_facho_xml_build_xml_absolute():
def test_facho_xml_build_xml_absolute_namespace():
xml = facho.FachoXML(
'{%s}root' %
('http://www.dian.gov.co/contratos/facturaelectronica/v1'),
nsmap={
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1'})
xml = facho.FachoXML('{%s}root' % ('http://www.dian.gov.co/contratos/facturaelectronica/v1'),
nsmap={'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1'})
xpath = xml.xpath_from_root('/A')
assert xpath == '/fe:root/A'
def test_facho_xml_build_xml_absolute_namespace_from_fragment():
xml = facho.FachoXML(
'{%s}root' %
('http://www.dian.gov.co/contratos/facturaelectronica/v1'),
nsmap={
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1'})
xml = facho.FachoXML('{%s}root' % ('http://www.dian.gov.co/contratos/facturaelectronica/v1'),
nsmap={'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1'})
invoice = xml.fragment('/root/Invoice')
xpath = invoice.xpath_from_root('/A')
assert xpath == '/fe:root/Invoice/A'

View File

@@ -4,14 +4,16 @@
# this repository contains the full copyright notices and license terms.
from datetime import datetime
import pytest
from facho import fe
import helpers
def test_xmlsigned_build(monkeypatch):
# openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1
# -newkey rsa:2048 -keyout example.key -out example.pem
# openssl pkcs12 -export -out example.p12 -inkey example.key -in
# example.pem
#openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1 -newkey rsa:2048 -keyout example.key -out example.pem
#openssl pkcs12 -export -out example.p12 -inkey example.key -in example.pem
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
xml = fe.FeXML('Invoice',
@@ -19,90 +21,68 @@ def test_xmlsigned_build(monkeypatch):
signer.sign_xml_element(xml.root)
elem = xml.find_or_create_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent'
'/ds:Signature')
elem = xml.find_or_create_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature')
assert elem is not None
# assert elem.findall('ds:SignedInfo', fe.NAMESPACES) is not None
#assert elem.findall('ds:SignedInfo', fe.NAMESPACES) is not None
def test_xmlsigned_with_passphrase_build(monkeypatch):
# openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1
# -newkey rsa:2048 -keyout example.key -out example.pem
# openssl pkcs12 -export -out example.p12 -inkey example.key -in
# example.pem
signer = fe.DianXMLExtensionSigner(
'./tests/example-with-passphrase.p12', 'test')
#openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1 -newkey rsa:2048 -keyout example.key -out example.pem
#openssl pkcs12 -export -out example.p12 -inkey example.key -in example.pem
signer = fe.DianXMLExtensionSigner('./tests/example-with-passphrase.p12', 'test')
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
signer.sign_xml_element(xml.root)
elem = xml.find_or_create_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent'
'/ds:Signature')
elem = xml.find_or_create_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature')
assert elem is not None
# assert elem.findall('ds:SignedInfo', fe.NAMESPACES) is not None
#assert elem.findall('ds:SignedInfo', fe.NAMESPACES) is not None
def test_dian_extension_software_security_code():
security_code = fe.DianXMLExtensionSoftwareSecurityCode(
'idsoftware', '1234', '1')
security_code = fe.DianXMLExtensionSoftwareSecurityCode('idsoftware', '1234', '1')
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.add_extension(security_code)
content = xml.get_element_text(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent'
'/sts:DianExtensions/sts:SoftwareSecurityCode')
content = xml.get_element_text('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode')
assert content is not None
def test_dian_extension_invoice_authorization():
invoice_authorization = '18762002346472'
inv_auth_ext = fe.DianXMLExtensionInvoiceAuthorization(
invoice_authorization, datetime(
2017, 2, 23), datetime(
2019, 8, 23), 'MD', 100001, 174999)
inv_auth_ext = fe.DianXMLExtensionInvoiceAuthorization(invoice_authorization,
datetime(2017, 2, 23),
datetime(2019, 8, 23),
'MD', 100001, 174999)
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.add_extension(inv_auth_ext)
auth = xml.get_element_text(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent'
'/sts:DianExtensions/sts:InvoiceControl/sts:InvoiceAuthorization')
auth = xml.get_element_text('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl/sts:InvoiceAuthorization')
assert auth == invoice_authorization
def test_dian_extension_software_provider():
nit = '123456789'
id_software = 'ABCDASDF123'
software_provider_extension = fe.DianXMLExtensionSoftwareProvider(
nit, '', id_software)
software_provider_extension = fe.DianXMLExtensionSoftwareProvider(nit, '', id_software)
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.add_extension(software_provider_extension)
give_nit = xml.get_element_text(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent'
'/sts:DianExtensions/sts:SoftwareProvider/sts:ProviderID')
give_nit = xml.get_element_text('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider/sts:ProviderID')
assert nit == give_nit
def test_dian_extension_authorization_provider():
auth_provider_extension = fe.DianXMLExtensionAuthorizationProvider()
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.add_extension(auth_provider_extension)
dian_nit = xml.get_element_text(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/'
'ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/'
'sts:AuthorizationProviderID')
dian_nit = xml.get_element_text('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID')
assert dian_nit == '800197268'
def test_dian_invoice_without_namespace_in_root():
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
@@ -112,14 +92,10 @@ def test_dian_invoice_without_namespace_in_root():
def test_xml_sign_dian(monkeypatch):
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.find_or_create_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
ublextension = xml.fragment(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension',
append=True)
ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.find_or_create_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
ublextension = xml.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True)
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
xmlstring = xml.tostring()
print(xmlstring)
@@ -127,17 +103,12 @@ def test_xml_sign_dian(monkeypatch):
xmlsigned = signer.sign_xml_string(xmlstring)
assert "Signature" in xmlsigned
def test_xml_sign_dian_using_bytes(monkeypatch):
xml = fe.FeXML('Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.find_or_create_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
ublextension = xml.fragment(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension',
append=True)
ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.find_or_create_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
ublextension = xml.fragment('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True)
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent')
xmlstring = xml.tostring()
p12_data = open('./tests/example.p12', 'rb').read()
@@ -145,21 +116,3 @@ def test_xml_sign_dian_using_bytes(monkeypatch):
xmlsigned = signer.sign_xml_string(xmlstring)
assert "Signature" in xmlsigned
def test_xml_signature_timestamp(monkeypatch):
xml = fe.FeXML(
'Invoice',
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
xml.find_or_create_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent')
ublextension = xml.fragment(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True)
ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
xmlstring = xml.tostring()
signer = fe.DianXMLExtensionSigner('./tests/example.p12')
xmlsigned = signer.sign_xml_string(xmlstring)
with open('invoice.xml', 'w') as file_:
file_.write(xmlsigned)

View File

@@ -5,49 +5,24 @@
"""Tests for `facho` package."""
import pytest
from datetime import datetime
import io
import zipfile
import facho.fe.form as form
from facho import fe
from facho.fe.form_xml import (
DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML)
from fixtures import (
simple_invoice,
simple_invoice_without_lines,
simple_credit_note_without_lines,
simple_debit_note_without_lines)
try:
CUDE_ = open("./tests/cude.txt", 'r').read().strip()
except FileNotFoundError:
raise Exception("Archivo Cude No encontrado")
CUFE_ = (
'8bb918b19ba22a694f1da'
'11c643b5e9de39adf60311c'
'f179179e9b33381030bcd4c3c'
'3f156c506ed5908f9276f5bd9b4')
simple_invoice = simple_invoice
simple_invoice_without_lines = simple_invoice_without_lines
simple_credit_note_without_lines = simple_credit_note_without_lines
simple_debit_note_without_lines = simple_debit_note_without_lines
from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML
from fixtures import *
def test_invoicesimple_build(simple_invoice):
xml = DIANInvoiceXML(simple_invoice)
supplier_name = xml.get_element_text(
'/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc'
':Name')
supplier_name = xml.get_element_text('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name')
assert supplier_name == simple_invoice.invoice_supplier.name
customer_name = xml.get_element_text(
'/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc'
':Name')
customer_name = xml.get_element_text('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name')
assert customer_name == simple_invoice.invoice_customer.name
@@ -67,27 +42,22 @@ def test_invoicesimple_xml_signed(monkeypatch, simple_invoice):
print(xml.tostring())
xml.add_extension(signer)
elem = xml.get_element(
'/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext'
':ExtensionContent/ds:Signature')
elem = xml.get_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent/ds:Signature')
assert elem.text is not None
def test_invoicesimple_zip(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
zipdata = io.BytesIO()
with fe.DianZIP(zipdata) as dianzip:
name_invoice = dianzip.add_invoice_xml(
simple_invoice.invoice_ident, str(xml_invoice))
name_invoice = dianzip.add_invoice_xml(simple_invoice.invoice_ident, str(xml_invoice))
# el zip ademas de archivar debe comprimir los archivos
# de lo contrario la DIAN lo rechaza
with zipfile.ZipFile(zipdata) as dianzip:
dianzip.testzip()
for zipinfo in dianzip.infolist():
assert zipinfo.compress_type == zipfile.ZIP_DEFLATED, (
"se espera el zip comprimido")
assert zipinfo.compress_type == zipfile.ZIP_DEFLATED, "se espera el zip comprimido"
with zipfile.ZipFile(zipdata) as dianzip:
xml_data = dianzip.open(name_invoice).read().decode('utf-8')
@@ -96,86 +66,66 @@ def test_invoicesimple_zip(simple_invoice):
def test_bug_cbcid_empty_on_invoice_line(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
cbc_id = xml_invoice.get_element_text(
'/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int)
cbc_id = xml_invoice.get_element_text('/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int)
assert cbc_id == 1
def test_invoice_line_count_numeric(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
count = xml_invoice.get_element_text(
'/fe:Invoice/cbc:LineCountNumeric', format_=int)
count = xml_invoice.get_element_text('/fe:Invoice/cbc:LineCountNumeric', format_=int)
assert count == len(simple_invoice.invoice_lines)
def test_invoice_profileexecutionid(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)
xml_invoice.add_extension(cufe_extension)
id_ = xml_invoice.get_element_text(
'/fe:Invoice/cbc:ProfileExecutionID', format_=int)
id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int)
assert id_ == 2
def test_invoice_invoice_type_code(simple_invoice):
xml_invoice = DIANInvoiceXML(simple_invoice)
id_ = xml_invoice.get_element_text(
'/fe:Invoice/cbc:InvoiceTypeCode', format_=int)
id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:InvoiceTypeCode', format_=int)
assert id_ == 1
def test_invoice_totals(simple_invoice_without_lines):
simple_invoice = simple_invoice_without_lines
simple_invoice.invoice_ident = '323200000129'
simple_invoice.invoice_issue = datetime.strptime(
'2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = '700085371'
simple_invoice.invoice_customer.ident = '800199436'
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto',
item=form.StandardItem(9999),
price=form.Price(form.Amount(1_500_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto',
item = form.StandardItem(9999),
price = form.Price(form.Amount(1_500_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('01'),
percent=19.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('01'),
percent = 19.0
)])
))
simple_invoice.calculate()
assert 1 == len(simple_invoice.invoice_lines)
assert form.Amount(1_500_000) == (
simple_invoice.invoice_legal_monetary_total.line_extension_amount)
assert form.Amount(1_785_000) == (
simple_invoice.invoice_legal_monetary_total.payable_amount)
assert form.Amount(1_500_000) == simple_invoice.invoice_legal_monetary_total.line_extension_amount
assert form.Amount(1_785_000) == simple_invoice.invoice_legal_monetary_total.payable_amount
def test_invoice_cufe(simple_invoice_without_lines):
simple_invoice = simple_invoice_without_lines
simple_invoice.invoice_ident = '323200000129'
simple_invoice.invoice_issue = datetime.strptime(
'2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification(
'700085371', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification(
'800199436', '5', '31')
simple_invoice.invoice_issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification('700085371', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification('800199436', '5', '31')
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(
1.00, '94'),
description='producto',
item=form.StandardItem(111),
price=form.Price(form.Amount(1_500_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1.00, '94'),
description = 'producto',
item = form.StandardItem(111),
price = form.Price(form.Amount(1_500_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('01'),
percent=19.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('01'),
percent = 19.0
)])
))
simple_invoice.calculate()
@@ -183,87 +133,65 @@ def test_invoice_cufe(simple_invoice_without_lines):
cufe_extension = fe.DianXMLExtensionCUFE(
simple_invoice,
tipo_ambiente=fe.AMBIENTE_PRODUCCION,
clave_tecnica='693ff6f2a553c3646a063436fd4dd9ded0311471'
tipo_ambiente = fe.AMBIENTE_PRODUCCION,
clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471'
)
formatVars = cufe_extension.formatVars()
# NumFac
#NumFac
assert formatVars[0] == '323200000129', "NumFac"
# FecFac
#FecFac
assert formatVars[1] == '2019-01-16', "FecFac"
# HoraFac
#HoraFac
assert formatVars[2] == '10:53:10-05:00', "HoraFac"
# ValorBruto
#ValorBruto
assert formatVars[3] == '1500000.00', "ValorBruto"
# CodImpuesto1
#CodImpuesto1
assert formatVars[4] == '01', "CodImpuesto1"
# ValorImpuesto1
#ValorImpuesto1
assert formatVars[5] == '285000.00', "ValorImpuesto1"
# CodImpuesto2
#CodImpuesto2
assert formatVars[6] == '04', "CodImpuesto2"
# ValorImpuesto2
#ValorImpuesto2
assert formatVars[7] == '0.00', "ValorImpuesto2"
# CodImpuesto3
#CodImpuesto3
assert formatVars[8] == '03', "CodImpuesto3"
# ValorImpuesto3
#ValorImpuesto3
assert formatVars[9] == '0.00', "ValorImpuesto3"
# ValTotFac
#ValTotFac
assert formatVars[10] == '1785000.00', "ValTotFac"
# NitOFE
#NitOFE
assert formatVars[11] == '700085371', "NitOFE"
# NumAdq
#NumAdq
assert formatVars[12] == '800199436', "NumAdq"
# ClTec
assert formatVars[13] == (
'693ff6f2a553c3646a063436fd4dd9ded0311471'), "ClTec"
# TipoAmbiente
#ClTec
assert formatVars[13] == '693ff6f2a553c3646a063436fd4dd9ded0311471', "ClTec"
#TipoAmbiente
assert formatVars[14] == '1', "TipoAmbiente"
xml_invoice.add_extension(cufe_extension)
cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID')
# RESOLUCION 004: pagina 689
assert cufe == CUFE_
assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'
def test_credit_note_cude(simple_credit_note_without_lines):
simple_invoice = simple_credit_note_without_lines
simple_invoice.invoice_ident = '8110007871'
simple_invoice.invoice_issue = datetime.strptime(
'2019-01-12 07:00:00-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification(
'900373076', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification(
'8355990', '5', '31')
simple_invoice.invoice_issue = datetime.strptime('2019-01-12 07:00:00-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification('900373076', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification('8355990', '5', '31')
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(
1, '94'),
description='producto',
item=form.StandardItem(111),
price=form.Price(
form.Amount(5_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto',
item = form.StandardItem(111),
price = form.Price(form.Amount(5_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('01'),
percent=19.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('01'),
percent = 19.0
)])
))
simple_invoice.calculate()
@@ -272,39 +200,33 @@ def test_credit_note_cude(simple_credit_note_without_lines):
cude_extension = fe.DianXMLExtensionCUDE(
simple_invoice,
'12301',
tipo_ambiente=fe.AMBIENTE_PRODUCCION,
tipo_ambiente = fe.AMBIENTE_PRODUCCION,
)
xml_invoice.add_extension(cude_extension)
cude = xml_invoice.get_element_text('/fe:CreditNote/cbc:UUID')
# pag 612
assert cude == CUDE_
assert cude == '907e4444decc9e59c160a2fb3b6659b33dc5b632a5008922b9a62f83f757b1c448e47f5867f2b50dbdb96f48c7681168'
# pag 614
def test_debit_note_cude(simple_debit_note_without_lines):
simple_invoice = simple_debit_note_without_lines
simple_invoice.invoice_ident = 'ND1001'
simple_invoice.invoice_issue = datetime.strptime(
'2019-01-18 10:58:00-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification(
'900197264', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification(
'10254102', '5', '31')
simple_invoice.invoice_issue = datetime.strptime('2019-01-18 10:58:00-05:00', '%Y-%m-%d %H:%M:%S%z')
simple_invoice.invoice_supplier.ident = form.PartyIdentification('900197264', '5', '31')
simple_invoice.invoice_customer.ident = form.PartyIdentification('10254102', '5', '31')
simple_invoice.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto',
item=form.StandardItem(111),
price=form.Price(form.Amount(30_000), '01', ''),
tax=form.TaxTotal(
subtotals=[
quantity = form.Quantity(1, '94'),
description = 'producto',
item = form.StandardItem(111),
price = form.Price(form.Amount(30_000), '01', ''),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
scheme=form.TaxScheme('04'),
percent=8.0
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
scheme = form.TaxScheme('04'),
percent = 8.0
)])
))
simple_invoice.calculate()
@@ -313,7 +235,7 @@ def test_debit_note_cude(simple_debit_note_without_lines):
cude_extension = fe.DianXMLExtensionCUDE(
simple_invoice,
'10201',
tipo_ambiente=fe.AMBIENTE_PRUEBAS,
tipo_ambiente = fe.AMBIENTE_PRUEBAS,
)
build_vars = cude_extension.buildVars()
assert build_vars['NumFac'] == 'ND1001'
@@ -329,14 +251,10 @@ def test_debit_note_cude(simple_debit_note_without_lines):
assert build_vars['Software-PIN'] == '10201'
assert build_vars['TipoAmb'] == 2
cude_composicion = "".join(cude_extension.formatVars())
assert cude_composicion == (
'ND10012019-01-1810:58:00-05:0030000.00010.00042400.00030.0032400.009'
'001'
'9726410254102102012')
cude_composicion = "".join(cude_extension.formatVars())
assert cude_composicion == 'ND10012019-01-1810:58:00-05:0030000.00010.00042400.00030.0032400.0090019726410254102102012'
xml_invoice.add_extension(cude_extension)
cude = xml_invoice.get_element_text('/fe:DebitNote/cbc:UUID')
assert cude == (
'3fa73a86d57d9341c536afde1f85c4efd9d4591c2c22bce4dfb0e6b0d2e83b8f047a8'
'bde7098292e9d2493e60d1c31da')
assert cude == '3fa73a86d57d9341c536afde1f85c4efd9d4591c2c22bce4dfb0e6b0d2e83b8f047a8bde7098292e9d2493e60d1c31da'

View File

@@ -6,164 +6,132 @@
"""Tests for `facho` package."""
import pytest
# from datetime import datetime
# import io
# import zipfile
from datetime import datetime
import io
import zipfile
import facho.fe.form as form
# from facho import fe
from fixtures import (
simple_invoice,
simple_invoice_without_lines,
simple_credit_note_without_lines,
simple_debit_note_without_lines)
simple_invoice = simple_invoice
simple_invoice_without_lines = simple_invoice_without_lines
simple_credit_note_without_lines = simple_credit_note_without_lines
simple_debit_note_without_lines = simple_debit_note_without_lines
from facho import fe
def test_invoice_legalmonetary():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='producto facho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.0,
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == (
form.Amount(119.0))
assert inv.invoice_legal_monetary_total.charge_total_amount == (
form.Amount(0.0))
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0)
def test_allowancecharge_as_discount():
discount = form.AllowanceChargeAsDiscount(amount=form.Amount(1000.0))
assert discount.isDiscount()
assert discount.isDiscount() == True
def test_FAU10():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.0)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == (
form.Amount(119.0))
assert inv.invoice_legal_monetary_total.charge_total_amount == (
form.Amount(19.0))
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(19.0)
def test_FAU14():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(
subtotals=[
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent=19.0,
)]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
percent = 19.0,
)
]
)
))
inv.add_allowance_charge(form.AllowanceCharge(
amount=form.Amount(19.0)))
inv.add_prepaid_payment(form.PrePaidPayment(
paid_amount=form.Amount(50.0)))
inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.add_prepaid_payment(form.PrePaidPayment(paid_amount = form.Amount(50.0)))
inv.calculate()
wants = form.Amount(119.0 + 19.0 - 50.0)
assert inv.invoice_legal_monetary_total.payable_amount == wants, (
"got %s want %s" % (
inv.invoice_legal_monetary_total.payable_amount, wants))
assert inv.invoice_legal_monetary_total.payable_amount == wants, "got %s want %s" % (inv.invoice_legal_monetary_total.payable_amount, wants)
def test_invalid_tipo_operacion_nota_debito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.DebitNote(reference)
with pytest.raises(ValueError):
inv.set_operation_type(22)
def test_valid_tipo_operacion_nota_debito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.DebitNote(reference)
inv.set_operation_type('30')
def test_invalid_tipo_operacion_nota_credito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.DebitNote(reference)
with pytest.raises(ValueError):
inv.set_operation_type('990')
def test_valid_tipo_operacion_nota_credito():
reference = form.InvoiceDocumentReference(
ident='11111',
uuid='21312312',
date='2020-05-05'
ident = '11111',
uuid = '21312312',
date = '2020-05-05'
)
inv = form.CreditNote(reference)
inv.set_operation_type('20')
@@ -173,52 +141,41 @@ def test_quantity():
quantity1 = form.Quantity(10, '94')
assert quantity1 * form.Amount(3) == form.Amount(30)
def test_invoice_line_quantity_without_taxes():
line = form.InvoiceLine(
quantity=form.Quantity(10, '94'),
description='',
item=form.StandardItem('test', 9999),
price=form.Price(
amount=form.Amount(30.00),
type_code='01',
type='x'),
tax=form.TaxTotal(subtotals=[]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
)
quantity = form.Quantity(10, '94'),
description = '',
item = form.StandardItem('test', 9999),
price = form.Price(
amount = form.Amount(30.00),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(subtotals=[]))
line.calculate()
assert line.total_amount == form.Amount(300)
assert line.tax_amount == form.Amount(0)
def test_invoice_legalmonetary_with_taxes():
inv = form.NationalSalesInvoice()
inv.add_invoice_line(form.InvoiceLine(
quantity=form.Quantity(1, '94'),
description='productofacho',
item=form.StandardItem(9999),
price=form.Price(
amount=form.Amount(100.0),
type_code='01',
type='x'
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax=form.TaxTotal(subtotals=[]),
withholding=form.WithholdingTaxTotal(
subtotals=[])
tax = form.TaxTotal(subtotals=[])
))
inv.calculate()
assert inv.invoice_legal_monetary_total.line_extension_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.charge_total_amount == (
form.Amount(0.0))
assert inv.invoice_legal_monetary_total.payable_amount == (
form.Amount(100.0))
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(100.0)
assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0)
assert inv.invoice_legal_monetary_total.payable_amount == form.Amount(100.0)
def test_invoice_ident_prefix_automatic_invalid():
@@ -226,7 +183,6 @@ def test_invoice_ident_prefix_automatic_invalid():
with pytest.raises(ValueError):
inv.set_ident('SETPQJQJ1234567')
def test_invoice_ident_prefix_automatic():
inv = form.NationalSalesInvoice()
inv.set_ident('SETP1234567')
@@ -244,15 +200,13 @@ def test_invoice_ident_prefix_automatic():
inv.set_ident('1234567')
assert inv.invoice_ident_prefix == ''
def test_invoice_ident_prefix_manual():
inv = form.NationalSalesInvoice()
inv.set_ident('SETP1234567')
inv.set_ident_prefix('SETA')
assert inv.invoice_ident_prefix == 'SETA'
def test_invoice_ident_prefix_automatic_debit():
inv = form.DebitNote(form.BillingReference('', '', ''))
inv = form.DebitNote(form.BillingReference('','',''))
inv.set_ident('ABCDEF1234567')
assert inv.invoice_ident_prefix == 'ABCDEF'

View File

@@ -6,21 +6,13 @@
"""Tests for `facho` package."""
import pytest
from datetime import datetime
import copy
from facho.fe import form
from facho.fe import form_xml
# from fixtures import *
from fixtures import (
simple_invoice,
simple_invoice_without_lines,
simple_credit_note_without_lines,
simple_debit_note_without_lines)
simple_invoice = simple_invoice
simple_invoice_without_lines = simple_invoice_without_lines
simple_credit_note_without_lines = simple_credit_note_without_lines
simple_debit_note_without_lines = simple_debit_note_without_lines
from fixtures import *
def test_import_DIANInvoiceXML():
try:
@@ -35,82 +27,70 @@ def test_import_DIANDebitNoteXML():
except AttributeError:
pytest.fail("unexpected not found")
def test_import_DIANCreditNoteXML():
try:
form_xml.DIANCreditNoteXML
except AttributeError:
pytest.fail("unexpected not found")
def test_allowance_charge_in_invoice(simple_invoice_without_lines):
inv = copy.copy(simple_invoice_without_lines)
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.0,
)
]
)
))
inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
inv.calculate()
# def test_allowance_charge_in_invoice(simple_invoice_without_lines):
# inv = copy.copy(simple_invoice_without_lines)
# inv.add_invoice_line(form.InvoiceLine(
# quantity=form.Quantity(1, '94'),
# description='productofacho',
# item=form.StandardItem(9999),
# price=form.Price(
# amount=form.Amount(100.0),
# type_code='01',
# type='x'
# ),
# tax=form.TaxTotal(
# subtotals=[
# form.TaxSubTotal(
# percent=19.0,
# )]),
# withholding=form.WithholdingTaxTotal(
# subtotals=[])
# ))
xml = form_xml.DIANInvoiceXML(inv)
assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1'
assert xml.get_element_text('./cac:AllowanceCharge/cbc:ChargeIndicator') == 'true'
assert xml.get_element_text('./cac:AllowanceCharge/cbc:Amount') == '19.0'
assert xml.get_element_text('./cac:AllowanceCharge/cbc:BaseAmount') == '100.0'
# inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0)))
# inv.calculate()
def test_allowance_charge_in_invoice_line(simple_invoice_without_lines):
inv = copy.copy(simple_invoice_without_lines)
inv.add_invoice_line(form.InvoiceLine(
quantity = form.Quantity(1, '94'),
description = 'producto facho',
item = form.StandardItem(9999),
price = form.Price(
amount = form.Amount(100.0),
type_code = '01',
type = 'x'
),
tax = form.TaxTotal(
subtotals = [
form.TaxSubTotal(
percent = 19.0,
)
]
),
allowance_charge = [
form.AllowanceChargeAsDiscount(amount=form.Amount(10.0))
]
))
inv.calculate()
# xml = form_xml.DIANInvoiceXML(inv)
# assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1'
# assert xml.get_element_text(
# './cac:AllowanceCharge/cbc:ChargeIndicator') == 'true'
# assert xml.get_element_text(
# './cac:AllowanceCharge/cbc:Amount') == '19.0'
# assert xml.get_element_text(
# './cac:AllowanceCharge/cbc:BaseAmount') == '100.0'
# se aplico descuento
assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(90.0)
xml = form_xml.DIANInvoiceXML(inv)
# def test_allowance_charge_in_invoice_line(simple_invoice_without_lines):
# inv = copy.copy(simple_invoice_without_lines)
# inv.add_invoice_line(form.InvoiceLine(
# quantity=form.Quantity(1, '94'),
# description='producto facho',
# item=form.StandardItem(9999),
# price=form.Price(
# amount=form.Amount(100.0),
# type_code='01',
# type='x'
# ),
# tax=form.TaxTotal(
# subtotals=[
# form.TaxSubTotal(
# percent=19.0,
# )]),
# withholding=form.WithholdingTaxTotal(
# subtotals=[]),
# allowance_charge=[
# form.AllowanceChargeAsDiscount(amount=form.Amount(10.0))
# ]
# ))
# inv.calculate()
# # se aplico descuento
# assert inv.invoice_legal_monetary_total.line_extension_amount == (
# form.Amount(90.0))
# xml = form_xml.DIANInvoiceXML(inv)
# with pytest.raises(AttributeError):
# assert xml.get_element_text(
# '/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1'
# xml.get_element_text(
# '/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1'
# xml.get_element_text(
# '/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount'
# ) == '100.0'
with pytest.raises(AttributeError):
assert xml.get_element_text('/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1'
xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1'
xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount') == '100.0'

View File

@@ -4,535 +4,467 @@
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
# import re
import re
# import pytest
import pytest
# from facho import fe
from facho import fe
# import helpers
import helpers
# def assert_error(errors, msg):
# for error in errors:
# if str(error) == msg:
# return True
def assert_error(errors, msg):
for error in errors:
if str(error) == msg:
return True
# raise "wants error: %s" % (msg)
raise "wants error: %s" % (msg)
# def test_adicionar_devengado_Basico():
# nomina = fe.nomina.DIANNominaIndividual()
def test_adicionar_devengado_Basico():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 30,
# sueldo_trabajado = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 30,
sueldo_trabajado = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
# assert
# xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico',
# 'DiasTrabajados') == '30'
# assert
# xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico',
# 'SueldoTrabajado') == '1000000.00'
xml = nomina.toFachoXML()
assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30'
assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') == '1000000.00'
# def test_adicionar_devengado_transporte():
# nomina = fe.nomina.DIANNominaIndividual()
def test_adicionar_devengado_transporte():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
# auxilio_transporte = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
auxilio_transporte = fe.nomina.Amount(2_000_000)
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert
# xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Transporte',
# 'AuxilioTransporte') == '2000000.0'
assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Transporte', 'AuxilioTransporte') == '2000000.0'
# def test_adicionar_devengado_comprobante_total():
# nomina = fe.nomina.DIANNominaIndividual()
def test_adicionar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(2_000_000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# assert
# xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') ==
# '1000000.00'
# def test_adicionar_devengado_comprobante_total_cero():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(1_000_000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# assert
# xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '0.00'
# def test_adicionar_devengado_transporte_muchos():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
# auxilio_transporte = fe.nomina.Amount(2_000_000)
# ))
# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
# auxilio_transporte = fe.nomina.Amount(3_000_000)
# ))
# xml = nomina.toFachoXML()
# print(xml)
# assert xml.get_element_text('/nomina:NominaIndividual/DevengadosTotal')
# == '5000000.00'
# def test_adicionar_deduccion_salud():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(1000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1000)
# ))
# xml = nomina.toFachoXML()
# print(xml)
# assert
# xml.get_element_text('/nomina:NominaIndividual/DeduccionesTotal') ==
# '1000.00'
# def test_nomina_obligatorios_segun_anexo_tecnico():
# nomina = fe.nomina.DIANNominaIndividual()
# errors = nomina.validate()
# assert_error(errors, 'se requiere Periodo')
# assert_error(errors, 'se requiere DevengadoBasico')
# assert_error(errors, 'se requiere DeduccionSalud')
# assert_error(errors, 'se requiere DeduccionFondoPension')
# def test_nomina_xml():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.asignar_metadata(fe.nomina.Metadata(
# novedad=fe.nomina.Novedad(
# activa = True,
# cune = "N0111"
# ),
# secuencia=fe.nomina.NumeroSecuencia(
# prefijo = 'N',
# consecutivo='00001'
# ),
# lugar_generacion=fe.nomina.Lugar(
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# ),
# proveedor=fe.nomina.Proveedor(
# nit='999999',
# dv=2,
# software_id='xx',
# software_pin='12',
# razon_social='facho'
# )
# ))
# nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
# fecha_generacion = '2020-01-16',
# hora_generacion = '1053:10-05:00',
# tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
# software_pin = '693',
# tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_NORMAL,
# periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
# tipo_moneda = fe.nomina.TipoMoneda(code='COP')
# ))
# nomina.asignar_empleador(fe.nomina.Empleador(
# razon_social='facho',
# nit = '700085371',
# dv = '1',
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# direccion = 'calle etrivial'
# ))
# nomina.asignar_trabajador(fe.nomina.Trabajador(
# tipo_contrato = fe.nomina.TipoContrato(
# code = '1'
# ),
# alto_riesgo = False,
# tipo_documento = fe.nomina.TipoDocumento(
# code = '11'
# ),
# primer_apellido = 'gnu',
# segundo_apellido = 'emacs',
# primer_nombre = 'facho',
# lugar_trabajo = fe.nomina.LugarTrabajo(
# pais = fe.nomina.Pais(code='CO'),
# departamento = fe.nomina.Departamento(code='05'),
# municipio = fe.nomina.Municipio(code='05001'),
# direccion = 'calle facho'
# ),
# numero_documento = '800199436',
# tipo = fe.nomina.TipoTrabajador(
# code = '01'
# ),
# salario_integral = True,
# sueldo = fe.nomina.Amount(1_500_000)
# ))
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(3_500_000)
# ))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
# xml = nomina.toFachoXML()
# expected_cune =
# 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484
# b5da15ff585e773b0b0ab'
# assert
# xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral',
# 'CUNE') == expected_cune
# assert
# xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral',
# 'TipoXML') == '102'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaX
# ML/@Numero') == 'N00001'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaX
# ML/@Consecutivo') == '00001'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionX
# ML/@Pais') == 'CO'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionX
# ML/@DepartamentoEstado') == '05'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionX
# ML/@MunicipioCiudad') == '05001'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@NI
# T') == '999999'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@DV
# ') == '2'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@So
# ftwareID') == 'xx'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@So
# ftwareSC') is not None
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/CodigoQR') ==
# f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={expected_
# cune}"
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/Empleador/@NIT')
# == '700085371'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/Trabajador/@Nume
# roDocumento') == '800199436'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad') ==
# 'True'
# assert
# xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad/@CUNENov
# ') == 'N0111'
# # confirmar el namespace
# assert 'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"' in
# xml.tostring()
# def test_asignar_pago():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.asignar_pago(fe.nomina.Pago(
# forma = fe.nomina.FormaPago(code='1'),
# metodo = fe.nomina.MetodoPago(code='1')
# ))
# def test_nomina_xmlsign(monkeypatch):
# nomina = fe.nomina.DIANNominaIndividual()
# xml = nomina.toFachoXML()
# signer = fe.nomina.DianXMLExtensionSigner('./tests/example.p12')
# xml.add_extension(signer)
# print(xml.tostring())
# elem =
# xml.get_element('/nomina:NominaIndividual/ext:UBLExtensions/ext:UBLExtension
# /ext:ExtensionContent/ds:Signature')
# assert elem is not None
# def test_nomina_devengado_horas_extras_nocturnas():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnas(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras =
# xml.get_element('/nomina:NominaIndividual/Devengados/HENs/HEN',
# multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_recargo_nocturno():
# nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturno(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras =
# xml.get_element('/nomina:NominaIndividual/Devengados/HRNs/HRN',
# multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_extras_diarias_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
#
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiariasDominicalesY
# Festivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras =
# xml.get_element('/nomina:NominaIndividual/Devengados/HEDDFs/HEDDF',
# multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_recargo_diarias_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
#
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoDiariasDominicales
# YFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras =
# xml.get_element('/nomina:NominaIndividual/Devengados/HRDDFs/HRDDF',
# multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_extras_nocturnas_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
#
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnasDominicale
# sYFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras =
# xml.get_element('/nomina:NominaIndividual/Devengados/HENDFs/HENDF',
# multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_nomina_devengado_horas_recargo_nocturno_dominicales_y_festivos():
# nomina = fe.nomina.DIANNominaIndividual()
#
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturnoDominicale
# sYFestivos(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
# xml = nomina.toFachoXML()
# extras =
# xml.get_element('/nomina:NominaIndividual/Devengados/HRNDFs/HRNDF',
# multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
# def test_fecha_validacion():
# with pytest.raises(ValueError) as e:
# fe.nomina.Fecha('535-35-3')
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(2_000_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
xml = nomina.toFachoXML()
assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '1000000.00'
def test_adicionar_devengado_comprobante_total_cero():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(1_000_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
xml = nomina.toFachoXML()
assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '0.00'
def test_adicionar_devengado_transporte_muchos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
auxilio_transporte = fe.nomina.Amount(2_000_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoTransporte(
auxilio_transporte = fe.nomina.Amount(3_000_000)
))
xml = nomina.toFachoXML()
print(xml)
assert xml.get_element_text('/nomina:NominaIndividual/DevengadosTotal') == '5000000.00'
def test_adicionar_deduccion_salud():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(1000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1000)
))
xml = nomina.toFachoXML()
print(xml)
assert xml.get_element_text('/nomina:NominaIndividual/DeduccionesTotal') == '1000.00'
def test_nomina_obligatorios_segun_anexo_tecnico():
nomina = fe.nomina.DIANNominaIndividual()
errors = nomina.validate()
assert_error(errors, 'se requiere Periodo')
assert_error(errors, 'se requiere DevengadoBasico')
assert_error(errors, 'se requiere DeduccionSalud')
assert_error(errors, 'se requiere DeduccionFondoPension')
def test_nomina_xml():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(
activa = True,
cune = "N0111"
),
secuencia=fe.nomina.NumeroSecuencia(
prefijo = 'N',
consecutivo='00001'
),
lugar_generacion=fe.nomina.Lugar(
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
),
proveedor=fe.nomina.Proveedor(
nit='999999',
dv=2,
software_id='xx',
software_pin='12',
razon_social='facho'
)
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2020-01-16',
hora_generacion = '1053:10-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin = '693',
tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_NORMAL,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='facho',
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion = 'calle etrivial'
))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(3_500_000)
))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
xml = nomina.toFachoXML()
expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab'
assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune
assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'TipoXML') == '102'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@DepartamentoEstado') == '05'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@MunicipioCiudad') == '05001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@NIT') == '999999'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@DV') == '2'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareID') == 'xx'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareSC') is not None
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/CodigoQR') == f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={expected_cune}"
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Empleador/@NIT') == '700085371'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Trabajador/@NumeroDocumento') == '800199436'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad') == 'True'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad/@CUNENov') == 'N0111'
# confirmar el namespace
assert 'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"' in xml.tostring()
def test_asignar_pago():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_pago(fe.nomina.Pago(
forma = fe.nomina.FormaPago(code='1'),
metodo = fe.nomina.MetodoPago(code='1')
))
def test_nomina_xmlsign(monkeypatch):
nomina = fe.nomina.DIANNominaIndividual()
xml = nomina.toFachoXML()
signer = fe.nomina.DianXMLExtensionSigner('./tests/example.p12')
xml.add_extension(signer)
print(xml.tostring())
elem = xml.get_element('/nomina:NominaIndividual/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature')
assert elem is not None
def test_nomina_devengado_horas_extras_nocturnas():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnas(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENs/HEN', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_recargo_nocturno():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturno(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNs/HRN', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_extras_diarias_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiariasDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDDFs/HEDDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_recargo_diarias_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoDiariasDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRDDFs/HRDDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_extras_nocturnas_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnasDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENDFs/HENDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_recargo_nocturno_dominicales_y_festivos():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturnoDominicalesYFestivos(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNDFs/HRNDF', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_fecha_validacion():
with pytest.raises(ValueError) as e:
fe.nomina.Fecha('535-35-3')

View File

@@ -4,267 +4,232 @@
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
# import re
import re
# import pytest
import pytest
# from facho import fe
from facho import fe
# import helpers
import helpers
# def atest_nomina_ajuste_reemplazar():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def atest_nomina_ajuste_reemplazar():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# xml = nomina.toFachoXML()
# print(xml)
# assert False
xml = nomina.toFachoXML()
print(xml)
assert False
# def test_nomina_ajuste_reemplazar_asignacion_tipo_xml():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# nomina.asignar_metadata(fe.nomina.Metadata(
# novedad=fe.nomina.Novedad(
# activa = True,
# cune = "N0111"
# ),
# secuencia=fe.nomina.NumeroSecuencia(
# prefijo = 'N',
# consecutivo='00001'
# ),
# lugar_generacion=fe.nomina.Lugar(
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# ),
# proveedor=fe.nomina.Proveedor(
# nit='999999',
# dv=2,
# software_id='xx',
# software_pin='12',
# razon_social='facho'
# )
# ))
# nomina.asignar_empleador(fe.nomina.Empleador(
# razon_social='facho',
# nit = '700085371',
# dv = '1',
# pais = fe.nomina.Pais(
# code = 'CO'
# ),
# departamento = fe.nomina.Departamento(
# code = '05'
# ),
# municipio = fe.nomina.Municipio(
# code = '05001'
# ),
# direccion = 'calle etrivial'
# ))
def test_nomina_ajuste_reemplazar_asignacion_tipo_xml():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(
activa = True,
cune = "N0111"
),
secuencia=fe.nomina.NumeroSecuencia(
prefijo = 'N',
consecutivo='00001'
),
lugar_generacion=fe.nomina.Lugar(
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
),
proveedor=fe.nomina.Proveedor(
nit='999999',
dv=2,
software_id='xx',
software_pin='12',
razon_social='facho'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='facho',
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion = 'calle etrivial'
))
# nomina.asignar_trabajador(fe.nomina.Trabajador(
# tipo_contrato = fe.nomina.TipoContrato(
# code = '1'
# ),
# alto_riesgo = False,
# tipo_documento = fe.nomina.TipoDocumento(
# code = '11'
# ),
# primer_apellido = 'gnu',
# segundo_apellido = 'emacs',
# primer_nombre = 'facho',
# lugar_trabajo = fe.nomina.LugarTrabajo(
# pais = fe.nomina.Pais(code='CO'),
# departamento = fe.nomina.Departamento(code='05'),
# municipio = fe.nomina.Municipio(code='05001'),
# direccion = 'calle facho'
# ),
# numero_documento = '800199436',
# tipo = fe.nomina.TipoTrabajador(
# code = '01'
# ),
# salario_integral = True,
# sueldo = fe.nomina.Amount(1_500_000)
# ))
# nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
# fecha_generacion = '2020-01-16',
# hora_generacion = '1053:10-05:00',
# tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
# software_pin = '693',
# tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_AJUSTES,
# periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
# tipo_moneda = fe.nomina.TipoMoneda(code='COP')
# ))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2020-01-16',
hora_generacion = '1053:10-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin = '693',
tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_AJUSTES,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert
# xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar
# /InformacionGeneral', 'TipoXML') == '103'
assert xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/InformacionGeneral', 'TipoXML') == '103'
# def test_adicionar_reemplazar_devengado_comprobante_total():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def test_adicionar_reemplazar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(2_000_000)
))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert
# xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/Comp
# robanteTotal') == '1000000.00'
assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00'
# def test_adicionar_reemplazar_asignar_predecesor():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def test_adicionar_reemplazar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
#
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.
# Predecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
# assert
# xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Re
# emplazar/ReemplazandoPredecesor/@NumeroPred') == '123456'
# assert
# xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Re
# emplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456'
# assert
# xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Re
# emplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16'
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16'
# def test_adicionar_reemplazar_eliminar_predecesor_opcional():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
def test_adicionar_reemplazar_eliminar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
#
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.
# Predecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
xml = nomina.toFachoXML()
print(xml.tostring())
# assert
# xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/Reemplaza
# ndoPredecesor') is not None
# assert
# xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoP
# redecesor') is None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None
# def test_adicionar_eliminar_reemplazar_predecesor_opcional():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
def test_adicionar_eliminar_reemplazar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
#
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Pr
# edecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
# assert
# xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoP
# redecesor') is not None
# assert
# xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/Reemplaza
# ndoPredecesor') is None
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None
# def test_adicionar_eliminar_devengado_comprobante_total():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
def test_adicionar_eliminar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
# nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
# dias_trabajados = 60,
# sueldo_trabajado = fe.nomina.Amount(2_000_000)
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoBasico(
dias_trabajados = 60,
sueldo_trabajado = fe.nomina.Amount(2_000_000)
))
# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
# porcentaje = fe.nomina.Amount(19),
# deduccion = fe.nomina.Amount(1_000_000)
# ))
nomina.adicionar_deduccion(fe.nomina.DeduccionSalud(
porcentaje = fe.nomina.Amount(19),
deduccion = fe.nomina.Amount(1_000_000)
))
# xml = nomina.toFachoXML()
xml = nomina.toFachoXML()
# assert
# xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Eliminar/Compro
# banteTotal') == '1000000.00'
assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00'
# def test_adicionar_eliminar_asignar_predecesor():
# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
def test_adicionar_eliminar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
#
# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Pr
# edecesor(
# numero = '123456',
# cune = 'ABC123456',
# fecha_generacion = '2021-11-16'
# ))
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
# xml = nomina.toFachoXML()
# print(xml.tostring())
# assert
# xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/El
# iminar/EliminandoPredecesor/@NumeroPred') == '123456'
# assert
# xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/El
# iminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456'
# assert
# xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/El
# iminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16'
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16'
# def test_nomina_devengado_horas_extras_diarias():
# nomina = fe.nomina.DIANNominaIndividual()
def test_nomina_devengado_horas_extras_diarias():
nomina = fe.nomina.DIANNominaIndividual()
# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias(
# horas_extras=[
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T19:09:55',
# hora_fin='2021-11-30T20:09:55',
# cantidad=1,
# porcentaje=fe.nomina.Amount(1),
# pago=fe.nomina.Amount(100)
# ),
# fe.nomina.DevengadoHoraExtra(
# hora_inicio='2021-11-30T18:09:55',
# hora_fin='2021-11-30T19:09:55',
# cantidad=2,
# porcentaje=fe.nomina.Amount(2),
# pago=fe.nomina.Amount(200)
# )
# ]
# ))
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
# xml = nomina.toFachoXML()
# extras = xml.get_element(
# '/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True)
# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
# assert extras[0].get('Cantidad') == '1'
# assert extras[0].get('Porcentaje') == '1.00'
# assert extras[0].get('Pago') == '100.00'
# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
# assert extras[1].get('Cantidad') == '2'
# assert extras[1].get('Porcentaje') == '2.00'
# assert extras[1].get('Pago') == '200.00'
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'

View File

@@ -3,22 +3,16 @@
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
# import pytest
import pytest
import facho.fe.form as form
from facho import fe
from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML
from facho.fe.form_xml import DIANInvoiceXML
# from facho.fe.form_xml import (
# DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML)
from fixtures import simple_invoice
from fixtures import *
from facho.fe.form import query
simple_invoice = simple_invoice
def test_query_billing_reference(simple_invoice):
xml = DIANInvoiceXML(simple_invoice)
cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice)

22
tox.ini
View File

@@ -1,18 +1,26 @@
[tox]
envlist = py310, py311, py312, py313, flake8
envlist = py27, py34, py35, py36, flake8
[flake8]
exclude = docs, build, venv, .tox, .git, .pytest_cache, *.egg-info
[travis]
python =
3.6: py36
3.5: py35
3.4: py34
2.7: py27
[testenv:flake8]
basepython = python3.13
deps = flake8>=7.0.0
commands = flake8 facho tests examples
basepython = python
deps = flake8
commands = flake8 facho
[testenv]
setenv =
PYTHONPATH = {toxinidir}
deps = pytest>=9.1.1,<10.0.0
deps =
-r{toxinidir}/requirements_dev.txt
; If you want to make tox run the tests with the same versions, create a
; requirements.txt with the pinned versions and uncomment the following line:
; -r{toxinidir}/requirements.txt
commands =
pip install -U pip
py.test --basetemp={envtmpdir}