Fix: Formateo PEP8 (79 cols) y limpieza flake8 en paquete facho

- Líneas >79 cols reajustadas en todo el paquete
- Fix F821: etree.cleanup_namespaces(security) en signature.py (era 'header', NameError)
- Fix bug cune_xpath -> xpath en nomina/informacion_general() (NameError)
- Elimina clase TaxScheme duplicada en form/__init__.py
- Imports explícitos (F403/F405) y __all__ en form_xml/invoice.py, nomina
- Elimina imports y variables muertas (F401/F841), E712 (== None -> is None)
This commit is contained in:
2026-08-12 19:56:29 -05:00
parent 26ce89ca33
commit 4a7c2e2947
37 changed files with 1915 additions and 1103 deletions

View File

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

View File

@@ -156,6 +156,7 @@ class FachoXML:
""" """
Decora XML con funciones de consulta XPATH de un solo elemento Decora XML con funciones de consulta XPATH de un solo elemento
""" """
def __init__(self, root, builder=None, nsmap=None, fragment_prefix='', def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',
fragment_root_element=None): fragment_root_element=None):
if builder is None: if builder is None:

View File

@@ -14,3 +14,22 @@ from .fe import AMBIENTE_PRUEBAS
from .fe import AMBIENTE_PRODUCCION from .fe import AMBIENTE_PRODUCCION
from . import form_xml from . import form_xml
from . import nomina 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,23 +1,13 @@
from facho import facho
import zeep import zeep
from zeep.wsse.username import UsernameToken from zeep.wsse.username import UsernameToken
from .wsse.signature import Signature, BinarySignature from .wsse.signature import BinarySignature
from zeep.wsa import WsAddressingPlugin
import xmlsec import xmlsec
import urllib.request from dataclasses import dataclass, asdict
from datetime import datetime
from dataclasses import dataclass, asdict, field
import http.client
import hashlib
import secrets
import base64
__all__ = ['DianClient', __all__ = ['DianClient']
'ConsultaResolucionesFacturacionPeticion',
'ConsultaResolucionesFacturacionRespuesta']
class SOAPService: class SOAPService:
@@ -33,6 +23,7 @@ class SOAPService:
def todict(self): def todict(self):
return asdict(self) return asdict(self)
@dataclass @dataclass
class GetNumberingRangeResponse: class GetNumberingRangeResponse:
@@ -49,7 +40,6 @@ class GetNumberingRangeResponse:
NumberRangeResponse: list[NumberRangeResponse] NumberRangeResponse: list[NumberRangeResponse]
@classmethod @classmethod
def fromdict(cls, data): def fromdict(cls, data):
return cls( return cls(
@@ -100,6 +90,7 @@ class SendTestSetAsyncResponse:
data['ErrorMessageList'] or [] data['ErrorMessageList'] or []
) )
@dataclass @dataclass
class SendTestSetAsync(SOAPService): class SendTestSetAsync(SOAPService):
fileName: str fileName: str
@@ -115,6 +106,7 @@ class SendTestSetAsync(SOAPService):
def build_response(self, as_dict): def build_response(self, as_dict):
return SendTestSetAsyncResponse.fromdict(as_dict) return SendTestSetAsyncResponse.fromdict(as_dict)
@dataclass @dataclass
class SendBillSync(SOAPService): class SendBillSync(SOAPService):
fileName: str fileName: str
@@ -129,6 +121,7 @@ class SendBillSync(SOAPService):
def build_response(self, as_dict): def build_response(self, as_dict):
return as_dict return as_dict
@dataclass @dataclass
class GetStatusResponse: class GetStatusResponse:
IsValid: bool IsValid: bool
@@ -162,6 +155,7 @@ class GetStatus(SOAPService):
def build_response(self, as_dict): def build_response(self, as_dict):
return GetStatusResponse.fromdict(as_dict) return GetStatusResponse.fromdict(as_dict)
@dataclass @dataclass
class GetStatusZip(SOAPService): class GetStatusZip(SOAPService):
trackId: bytes trackId: bytes
@@ -175,6 +169,7 @@ class GetStatusZip(SOAPService):
def build_response(self, as_dict): def build_response(self, as_dict):
return GetStatusResponse.fromdict(as_dict[0]) return GetStatusResponse.fromdict(as_dict[0])
@dataclass @dataclass
class SendNominaSync(SOAPService): class SendNominaSync(SOAPService):
contentFile: bytes contentFile: bytes
@@ -220,6 +215,7 @@ class Habilitacion:
def get_wsdl(self): def get_wsdl(self):
return Habilitacion.WSDL return Habilitacion.WSDL
class DianGateway: class DianGateway:
def _open(self, service): def _open(self, service):
@@ -250,7 +246,11 @@ class DianClient(DianGateway):
self._password = password self._password = password
def _open(self, service): 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): class DianSignatureClient(DianGateway):
@@ -262,13 +262,10 @@ class DianSignatureClient(DianGateway):
def _open(self, service): def _open(self, service):
# RESOLUCCION 0004: pagina 756 # RESOLUCCION 0004: pagina 756
from zeep.wsse import utils
client = zeep.Client(service.get_wsdl(), wsse= client = zeep.Client(service.get_wsdl(), wsse=BinarySignature(
BinarySignature(
self.private_key_path, self.public_key_path, self.password, self.private_key_path, self.public_key_path, self.password,
signature_method=xmlsec.Transform.RSA_SHA256, signature_method=xmlsec.Transform.RSA_SHA256,
digest_method=xmlsec.Transform.SHA256) digest_method=xmlsec.Transform.SHA256),
,
) )
return client return client

View File

@@ -70,8 +70,11 @@ class MemorySignature(object):
def apply(self, envelope, headers): def apply(self, envelope, headers):
key = _make_sign_key(self.key_data, self.cert_data, self.password) key = _make_sign_key(self.key_data, self.cert_data, self.password)
_sign_envelope_with_key( _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 return envelope, headers
def verify(self, envelope): def verify(self, envelope):
@@ -81,7 +84,7 @@ class MemorySignature(object):
class Signature(MemorySignature): class Signature(MemorySignature):
"""Sign given SOAP envelope with WSSE sig using given key file and cert file.""" """Sign given SOAP envelope with WSSE sig using given key and cert file."""
def __init__( def __init__(
self, self,
@@ -101,15 +104,18 @@ class Signature(MemorySignature):
class BinarySignature(Signature): class BinarySignature(Signature):
"""Sign given SOAP envelope with WSSE sig using given key file and cert file. """Sign given SOAP envelope with WSSE sig using given key and cert file.
Place the key information into BinarySecurityElement.""" Place the key information into BinarySecurityElement."""
def apply(self, envelope, headers): def apply(self, envelope, headers):
key = _make_sign_key(self.key_data, self.cert_data, self.password) key = _make_sign_key(self.key_data, self.cert_data, self.password)
_sign_envelope_with_key_binary( _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 return envelope, headers
@@ -219,7 +225,9 @@ def sign_envelope(
""" """
# Load the signing key and certificate. # Load the signing key and certificate.
key = _make_sign_key(_read_file(keyfile), _read_file(certfile), password) 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): def get_timestamp(timestamp=None, delta=None):
timestamp = timestamp or datetime.now(timezone.utc) timestamp = timestamp or datetime.now(timezone.utc)
@@ -230,11 +238,15 @@ def get_timestamp(timestamp = None, delta=None):
timestamp = timestamp.replace(tzinfo=pytz.utc, microsecond=0) timestamp = timestamp.replace(tzinfo=pytz.utc, microsecond=0)
return timestamp.strftime(format_) return timestamp.strftime(format_)
def _append_timestamp(security, expires_dt=None): def _append_timestamp(security, expires_dt=None):
if expires_dt is None: if expires_dt is None:
expires_dt = timedelta(seconds=6000) 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 = 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.Created(get_timestamp()))
etimestamp.append(utils.WSU.Expires(get_timestamp(delta=expires_dt))) etimestamp.append(utils.WSU.Expires(get_timestamp(delta=expires_dt)))
security.insert(0, etimestamp) security.insert(0, etimestamp)
@@ -243,11 +255,16 @@ def _append_timestamp(security, expires_dt=None):
keep_ns_prefixes=security.nsmap, keep_ns_prefixes=security.nsmap,
top_nsmap=utils.NSMAP) top_nsmap=utils.NSMAP)
else: else:
etree.cleanup_namespaces(header) etree.cleanup_namespaces(security)
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.""" """Prepare envelope and sign."""
soap_env = detect_soap_env(envelope)
# Create the Signature node. # Create the Signature node.
signature = xmlsec.template.create( signature = xmlsec.template.create(
@@ -278,7 +295,7 @@ def _signature_prepare(envelope, key, signature_method, digest_method, expires_d
_append_timestamp(security, expires_dt=expires_dt) _append_timestamp(security, expires_dt=expires_dt)
timestamp = security.find(QName(ns.WSU, "Timestamp")) timestamp = security.find(QName(ns.WSU, "Timestamp"))
if timestamp != None: if timestamp is not None:
_sign_node(ctx, signature, timestamp, digest_method) _sign_node(ctx, signature, timestamp, digest_method)
ctx.sign(signature) ctx.sign(signature)
@@ -286,18 +303,30 @@ def _signature_prepare(envelope, key, signature_method, digest_method, expires_d
# KeyInfo. The recipient expects this structure, but we can't rearrange # KeyInfo. The recipient expects this structure, but we can't rearrange
# like this until after signing, because otherwise xmlsec won't populate # like this until after signing, because otherwise xmlsec won't populate
# the X509 data (because it doesn't understand WSSE). # 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 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( _, sec_token_ref, x509_data = _signature_prepare(
envelope, key, signature_method, digest_method, expires_dt=expires_dt envelope, key, signature_method, digest_method, expires_dt=expires_dt
) )
sec_token_ref.append(x509_data) 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( security, sec_token_ref, x509_data = _signature_prepare(
envelope, key, signature_method, digest_method, expires_dt=expires_dt envelope, key, signature_method, digest_method, expires_dt=expires_dt
) )
@@ -353,7 +382,10 @@ def _verify_envelope_with_key(envelope, key):
ctx = xmlsec.SignatureContext() ctx = xmlsec.SignatureContext()
# Find each signed element and register its ID with the signing context. # 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: for ref in refs:
# Get the reference URI and cut off the initial '#' # Get the reference URI and cut off the initial '#'
referenced_id = ref.get("URI")[1:] referenced_id = ref.get("URI")[1:]

View File

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

View File

@@ -60,6 +60,7 @@ class CodeList:
# nombres de variables igual a ./Identification/ShortName # nombres de variables igual a ./Identification/ShortName
# TODO: garantizar unica carga en python # TODO: garantizar unica carga en python
__all__ = ['TipoOrganizacion', __all__ = ['TipoOrganizacion',
'TipoResponsabilidad', 'TipoResponsabilidad',
'TipoAmbiente', 'TipoAmbiente',
@@ -72,38 +73,88 @@ __all__ = ['TipoOrganizacion',
'Municipio', 'Municipio',
'Departamento'] 'Departamento']
def path_for_codelist(name): def path_for_codelist(name):
return os.path.join(DATA_DIR, 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')\ TipoOrganizacion = CodeList(path_for_codelist(
.update(CodeList(path_for_codelist('TipoResponsabilidad-2.1.custom.gc'), 'code', 'name')) 'TipoOrganizacion-2.1.gc'), 'code', 'name')
TipoAmbiente = CodeList(path_for_codelist('TipoAmbiente-2.1.gc'), 'code', 'name') TipoResponsabilidad = CodeList(
TipoDocumento = CodeList(path_for_codelist('TipoDocumento-2.1.gc'), 'code', 'name') path_for_codelist('TipoResponsabilidad-2.1.gc'),
TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name')\ 'code',
.update(CodeList(path_for_codelist('TipoImpuesto-2.1.custom.gc'), 'code', 'name')) 'name') .update(
TarifaImpuesto = CodeList(path_for_codelist('TarifaImpuestoINC-2.1.gc'), 'code', 'name')\ CodeList(
.update(CodeList(path_for_codelist('TarifaImpuestoIVA-2.1.gc'), 'code', 'name'))\ path_for_codelist('TipoResponsabilidad-2.1.custom.gc'),
.update(CodeList(path_for_codelist('TarifaImpuestoReteIVA-2.1.gc'), 'code', 'name'))\ 'code',
.update(CodeList(path_for_codelist('TarifaImpuestoReteRenta-2.1.gc'), 'code', 'name')) 'name'))
CodigoPrecioReferencia = CodeList(path_for_codelist('CodigoPrecioReferencia-2.1.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') MediosPago = CodeList(path_for_codelist('MediosPago-2.1.gc'), 'code', 'name')
FormasPago = CodeList(path_for_codelist('FormasPago-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') RegimenFiscal = CodeList(path_for_codelist(
TipoOperacionNC = CodeList(path_for_codelist('TipoOperacionNC-2.1.gc'), 'code', 'name') 'RegimenFiscal-2.1.custom.gc'), 'code', 'name')
TipoOperacionNCDS = CodeList(path_for_codelist('TipoOperacionNCDS-2.1.gc'), 'code', 'name') TipoOperacionNC = CodeList(path_for_codelist(
TipoOperacionND = CodeList(path_for_codelist('TipoOperacionND-2.1 - copia.gc'), 'code', 'name') 'TipoOperacionNC-2.1.gc'), 'code', 'name')
TipoOperacionF = CodeList(path_for_codelist('TipoOperacionF-2.1.gc'), 'code', 'name')\ TipoOperacionNCDS = CodeList(path_for_codelist(
.update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name')) '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') 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') Paises = CodeList(path_for_codelist('Paises-2.1.gc'), 'code', 'name')
TipoIdFiscal = CodeList(path_for_codelist('TipoIdFiscal-2.1.gc'), 'code', 'name') TipoIdFiscal = CodeList(path_for_codelist(
CodigoDescuento = CodeList(path_for_codelist('CodigoDescuento-2.1.gc'), 'code', 'name') 'TipoIdFiscal-2.1.gc'), 'code', 'name')
UnidadesMedida = CodeList(path_for_codelist('UnidadesMedida-2.1.gc'), 'code', 'name') CodigoDescuento = CodeList(path_for_codelist(
TipoTrabajador = CodeList(path_for_codelist('TipoTrabajador-2.1.gc'), 'code', 'name') 'CodigoDescuento-2.1.gc'), 'code', 'name')
SubTipoTrabajador = CodeList(path_for_codelist('SubTipoTrabajador-2.1.gc'), 'code', 'name') UnidadesMedida = CodeList(path_for_codelist(
TipoContrato = CodeList(path_for_codelist('TipoContrato-2.1.gc'), 'code', 'name') 'UnidadesMedida-2.1.gc'), 'code', 'name')
PeriodoNomina = CodeList(path_for_codelist('PeriodoNomina-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') 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

@@ -37,31 +37,45 @@ AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code']
SCHEME_AGENCY_ATTRS = { SCHEME_AGENCY_ATTRS = {
'schemeAgencyName': 'CO, DIAN (Dirección de Impuestos y Aduanas Nacionales)', 'schemeAgencyName': 'CO, DIAN (Dirección de Impuestos y Aduanas'
'schemeAgencyID': '195' ' Nacionales)',
} 'schemeAgencyID': '195'}
# RESOLUCION 0001: pagina 516 # RESOLUCION 0001: pagina 516
POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf' POLICY_ID = (
POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.' '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.'
)
Bogota = tz.gettz('America/Bogota') Bogota = tz.gettz('America/Bogota')
# NAMESPACES = { # NAMESPACES = {
# 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', # 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
# 'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual', # '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', # 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
# 'xs': 'http://www.w3.org/2001/XMLSchema-instance', # 'xs': 'http://www.w3.org/2001/XMLSchema-instance',
# 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', # 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:'
# 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', # 'CommonAggregateComponents-2',
# 'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1', # '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', # 'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001',
# 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003', # 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:'
# 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', # 'IANAMIMEMediaType:2003',
# 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-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', # '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', # 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
# 'xades': 'http://uri.etsi.org/01903/v1.3.2#', # 'xades': 'http://uri.etsi.org/01903/v1.3.2#',
# 'xades141': 'http://uri.etsi.org/01903/v1.4.1#', # 'xades141': 'http://uri.etsi.org/01903/v1.4.1#',
@@ -72,12 +86,19 @@ Bogota = tz.gettz('America/Bogota')
NAMESPACES = { NAMESPACES = {
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cac': (
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', 'urn:oasis:names:specification:ubl:schema:xsd'
'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', ':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', 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2',
'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', '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', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'ds': 'http://www.w3.org/2000/09/xmldsig#', 'ds': 'http://www.w3.org/2000/09/xmldsig#',
'xades': 'http://uri.etsi.org/01903/v1.3.2#', 'xades': 'http://uri.etsi.org/01903/v1.3.2#',
@@ -119,14 +140,17 @@ class FeXML(FachoXML):
return super().from_string(document, namespaces=NAMESPACES) return super().from_string(document, namespaces=NAMESPACES)
def tostring(self, **kw): 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_namespace = self.root_namespace()
root_localname = self.root_localname() root_localname = self.root_localname()
xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace] xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace]
if root_localname == 'Invoice': 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': 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)\ return super().tostring(**kw)\
.replace(xmlns_name + ':', '')\ .replace(xmlns_name + ':', '')\
.replace('xmlns:' + xmlns_name, 'xmlns')\ .replace('xmlns:' + xmlns_name, 'xmlns')\
@@ -149,9 +173,12 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
def _get_qrcode(self, cufe): def _get_qrcode(self, cufe):
url_for = { url_for = {
AMBIENTE_PRUEBAS: 'https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey=', AMBIENTE_PRUEBAS: (
AMBIENTE_PRODUCCION: 'https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=' '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 return url_for[self.tipo_ambiente] + cufe
def build(self, fachoxml): def build(self, fachoxml):
@@ -162,21 +189,29 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
if self.schemeName() == "CUDS-SHA384": if self.schemeName() == "CUDS-SHA384":
if fachoxml.tag_document() == 'Invoice': if fachoxml.tag_document() == 'Invoice':
fachoxml.set_element('./cbc:ProfileID', fachoxml.set_element(
'DIAN 2.1: documento soporte en adquisiciones efectuadas a no obligados a facturar.') './cbc:ProfileID',
'DIAN 2.1: documento soporte en adquisiciones '
'efectuadas a no obligados a facturar.')
else: else:
fachoxml.set_element('./cbc:ProfileID', fachoxml.set_element(
'DIAN 2.1: Nota de ajuste al documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente') './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: 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')
# #DIAN 1.8.-2021: FAD03 # fachoxml.set_element('./cbc:ProfileID',
# fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') # 'DIAN 2.1: Factura Electrónica de Venta')
fachoxml.set_element( fachoxml.set_element(
'./cbc:ProfileExecutionID', self._tipo_ambiente_int()) './cbc:ProfileExecutionID', self._tipo_ambiente_int())
# DIAN 1.7.-2020: FAB36 # DIAN 1.7.-2020: FAB36
fachoxml.set_element( fachoxml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:QRCode',
self._get_qrcode(cufe)) self._get_qrcode(cufe))
def issue_time(self, datetime_): def issue_time(self, datetime_):
@@ -192,7 +227,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
build_vars['FecFac'] = self.issue_date(invoice.invoice_issue) build_vars['FecFac'] = self.issue_date(invoice.invoice_issue)
build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue) build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue)
# PAG 601 # PAG 601
build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount build_vars['ValorBruto'] = (
invoice.invoice_legal_monetary_total.line_extension_amount)
build_vars['ValorTotalPagar' build_vars['ValorTotalPagar'
] = invoice.invoice_legal_monetary_total.payable_amount ] = invoice.invoice_legal_monetary_total.payable_amount
ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0)) ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0))
@@ -204,7 +240,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension):
if subtotal.scheme is not None: if subtotal.scheme is not None:
# TODO cual es la naturaleza de tax_scheme_ident? # TODO cual es la naturaleza de tax_scheme_ident?
codigo_impuesto = subtotal.scheme.code 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 ValorImpuestoPara[codigo_impuesto] += subtotal.tax_amount
build_vars['ValorImpuestoPara'] = ValorImpuestoPara build_vars['ValorImpuestoPara'] = ValorImpuestoPara
@@ -244,21 +281,35 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE):
CodImpuesto2 = build_vars['CodImpuesto2'] CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3'] CodImpuesto3 = build_vars['CodImpuesto3']
return [ return [
'%s' % build_vars['NumFac'], '%s' %
'%s' % build_vars['FecFac'], build_vars['NumFac'],
'%s' % build_vars['HoraFac'], '%s' %
form.Amount(build_vars['ValorBruto']).truncate_as_string(2), build_vars['FecFac'],
'%s' %
build_vars['HoraFac'],
form.Amount(
build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1, 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, 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, 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), build_vars['ValorTotalPagar'].truncate_as_string(2),
'%s' % build_vars['NitOFE'], '%s' %
'%s' % build_vars['NumAdq'], build_vars['NitOFE'],
'%s' % build_vars['ClTec'], '%s' %
'%d' % build_vars['TipoAmb'], build_vars['NumAdq'],
'%s' %
build_vars['ClTec'],
'%d' %
build_vars['TipoAmb'],
] ]
@@ -282,21 +333,39 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE):
CodImpuesto2 = build_vars['CodImpuesto2'] CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3'] CodImpuesto3 = build_vars['CodImpuesto3']
return [ return [
'%s' % build_vars['NumFac'], '%s' %
'%s' % build_vars['FecFac'], build_vars['NumFac'],
'%s' % build_vars['HoraFac'], '%s' %
form.Amount(build_vars['ValorBruto']).truncate_as_string(2), build_vars['FecFac'],
'%s' %
build_vars['HoraFac'],
form.Amount(
build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1, 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, 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, CodImpuesto3,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto3, 0.0)).truncate_as_string(2), form.Amount(
form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2), build_vars['ValorImpuestoPara'].get(
'%s' % build_vars['NitOFE'], CodImpuesto3,
'%s' % build_vars['NumAdq'], 0.0)).truncate_as_string(2),
'%s' % build_vars['Software-PIN'], form.Amount(
'%d' % build_vars['TipoAmb'], build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' %
build_vars['NitOFE'],
'%s' %
build_vars['NumAdq'],
'%s' %
build_vars['Software-PIN'],
'%d' %
build_vars['TipoAmb'],
] ]
@@ -317,20 +386,30 @@ class DianXMLExtensionCUDS(DianXMLExtensionCUDFE):
def formatVars(self): def formatVars(self):
build_vars = self.buildVars() build_vars = self.buildVars()
CodImpuesto1 = build_vars['CodImpuesto1'] CodImpuesto1 = build_vars['CodImpuesto1']
CodImpuesto2 = build_vars['CodImpuesto2']
CodImpuesto3 = build_vars['CodImpuesto3']
return [ return [
'%s' % build_vars['NumFac'], '%s' %
'%s' % build_vars['FecFac'], build_vars['NumFac'],
'%s' % build_vars['HoraFac'], '%s' %
form.Amount(build_vars['ValorBruto']).truncate_as_string(2), build_vars['FecFac'],
'%s' %
build_vars['HoraFac'],
form.Amount(
build_vars['ValorBruto']).truncate_as_string(2),
CodImpuesto1, CodImpuesto1,
form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2), form.Amount(
form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2), build_vars['ValorImpuestoPara'].get(
'%s' % build_vars['NitOFE'], CodImpuesto1,
'%s' % build_vars['NumAdq'], 0.0)).truncate_as_string(2),
'%s' % build_vars['Software-PIN'], form.Amount(
'%d' % build_vars['TipoAmb'], build_vars['ValorTotalPagar']).truncate_as_string(2),
'%s' %
build_vars['NitOFE'],
'%s' %
build_vars['NumAdq'],
'%s' %
build_vars['Software-PIN'],
'%d' %
build_vars['TipoAmb'],
] ]
@@ -344,14 +423,19 @@ class DianXMLExtensionSoftwareProvider(FachoXMLExtension):
def build(self, fexml): def build(self, fexml):
software_provider = fexml.fragment( software_provider = fexml.fragment(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareProvider')
provider_id_attrs = SCHEME_AGENCY_ATTRS.copy() provider_id_attrs = SCHEME_AGENCY_ATTRS.copy()
provider_id_attrs.update({'schemeID': self.dv}) provider_id_attrs.update({'schemeID': self.dv})
# DIAN 1.7.-2020: FAB23 # DIAN 1.7.-2020: FAB23
provider_id_attrs.update({'schemeName': '31'}) provider_id_attrs.update({'schemeName': '31'})
software_provider.set_element('/sts:SoftwareProvider/sts:ProviderID', self.nit, software_provider.set_element(
'/sts:SoftwareProvider/sts:ProviderID',
self.nit,
**provider_id_attrs) **provider_id_attrs)
software_provider.set_element('/sts:SoftwareProvider/sts:SoftwareID', self.id_software, software_provider.set_element(
'/sts:SoftwareProvider/sts:SoftwareID',
self.id_software,
**SCHEME_AGENCY_ATTRS) **SCHEME_AGENCY_ATTRS)
@@ -364,7 +448,10 @@ class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension):
self.invoice_ident = invoice_ident self.invoice_ident = invoice_ident
def build(self, fexml): 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) code = str(self.id_software) + str(self.pin) + str(self.invoice_ident)
m = hashlib.sha384() m = hashlib.sha384()
m.update(code.encode('utf-8')) m.update(code.encode('utf-8'))
@@ -418,8 +505,11 @@ class DianXMLExtensionSigner:
xml.append(signature) xml.append(signature)
ref = xmlsig.template.add_reference( 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) xmlsig.template.add_transform(ref, xmlsig.constants.TransformEnveloped)
id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid) id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid)
@@ -438,9 +528,8 @@ class DianXMLExtensionSigner:
id_props = "xmldsig-%s-signedprops" % (id_uuid) id_props = "xmldsig-%s-signedprops" % (id_uuid)
props_ref = xmlsig.template.add_reference( props_ref = xmlsig.template.add_reference(
signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props), signature, xmlsig.constants.TransformSha256, uri="#%s" %
uri_type="http://uri.etsi.org/01903#SignedProperties" (id_props), uri_type="http://uri.etsi.org/01903#SignedProperties")
)
xmlsig.template.add_transform( xmlsig.template.add_transform(
props_ref, xmlsig.constants.TransformInclC14N) props_ref, xmlsig.constants.TransformInclC14N)
@@ -481,7 +570,9 @@ class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
def build(self, fexml): def build(self, fexml):
attrs = {'schemeID': '4', 'schemeName': '31'} attrs = {'schemeID': '4', 'schemeName': '31'}
attrs.update(SCHEME_AGENCY_ATTRS) 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', authorization_provider.set_element('./sts:AuthorizationProviderID',
'800197268', '800197268',
**attrs) **attrs)
@@ -490,12 +581,17 @@ class DianXMLExtensionAuthorizationProvider(FachoXMLExtension):
class DianXMLExtensionInvoiceSource(FachoXMLExtension): class DianXMLExtensionInvoiceSource(FachoXMLExtension):
# CAB13 # CAB13
def build(self, fexml): def build(self, fexml):
dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode' dian_path = (
'/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionCo'
'ntent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode'
)
fexml.set_element( fexml.set_element(
dian_path, 'CO', dian_path, 'CO',
listAgencyID="6", listAgencyID="6",
listAgencyName="United Nations Economic Commission for Europe", listAgencyName="United Nations Economic Commission for Europe",
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") listSchemeURI=(
"urn:oasis:names:specification:ubl:codelist:gc:"
"CountryIdentificationCode-2.1"))
class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension): class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
@@ -512,28 +608,38 @@ class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension):
self.to = to self.to = to
def build(self, fexml): def build(self, fexml):
invoice_control = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl') invoice_control = fexml.fragment(
invoice_control.set_element('/sts:InvoiceControl/sts:InvoiceAuthorization', self.authorization) './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:StartDate', ':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')) self.period_startdate.strftime('%Y-%m-%d'))
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:EndDate', invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizationPeriod/cbc:EndDate',
self.period_enddate.strftime('%Y-%m-%d')) self.period_enddate.strftime('%Y-%m-%d'))
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:Prefix', invoice_control.set_element(
'/sts:InvoiceControl/sts:AuthorizedInvoices/sts:Prefix',
self.prefix) self.prefix)
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:From', invoice_control.set_element(
self.from_) '/sts:InvoiceControl/sts:AuthorizedInvoices/sts:From', self.from_)
invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To', invoice_control.set_element(
self.to) '/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To', self.to)
fexml.set_element( fexml.set_element(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode', './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceSource/cbc:IdentificationCode',
'CO', 'CO',
# DIAN 1.7.-2020: FAB15 # DIAN 1.7.-2020: FAB15
listAgencyID="6", listAgencyID="6",
# DIAN 1.7.-2020: FAB16 # DIAN 1.7.-2020: FAB16
listAgencyName="United Nations Economic Commission for Europe", listAgencyName="United Nations Economic Commission for Europe",
# DIAN 1.7.-2020: FAB17 # DIAN 1.7.-2020: FAB17
listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") listSchemeURI=(
"urn:oasis:names:specification:ubl:codelist:gc:"
"CountryIdentificationCode-2.1"))
class DianZIP: class DianZIP:
@@ -576,7 +682,11 @@ class DianZIP:
class DianXMLExtensionSignerVerifier: 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._pkcs12_path_or_bytes = pkcs12_path_or_bytes
self._passphrase = None self._passphrase = None
self._localpolicy = localpolicy self._localpolicy = localpolicy
@@ -610,5 +720,5 @@ class DianXMLExtensionSignerVerifier:
else: else:
ctx.verify(signature) ctx.verify(signature)
return True return True
except: except BaseException:
return False return False

View File

@@ -5,9 +5,11 @@
# from functools import reduce # from functools import reduce
# import copy # import copy
from __future__ import annotations
import dataclasses import dataclasses
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime, date from datetime import datetime
# from collections import defaultdict # from collections import defaultdict
import decimal import decimal
from decimal import Decimal from decimal import Decimal
@@ -278,17 +280,6 @@ class Responsability:
raise ValueError("code %s not found" % (code)) 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 @dataclass
class Party: class Party:
name: str name: str

View File

@@ -6,7 +6,10 @@ from .. import form
from ..fe import fe_from_string from ..fe import fe_from_string
from datetime import datetime 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 construye BillingReference desde XMLDOCUMENT
usando KLASS como clase. usando KLASS como clase.

View File

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

View File

@@ -1,9 +1,18 @@
from .. import fe
from ..form import *
from collections import defaultdict from collections import defaultdict
from .. import fe
from ..form import (
Amount,
CreditNoteDocumentReference,
DebitNoteDocumentReference,
InvoiceDocumentReference,
TaxTotalOmit,
WithholdingTaxTotalOmit,
)
__all__ = ['DIANInvoiceXML'] __all__ = ['DIANInvoiceXML']
class DIANInvoiceXML(fe.FeXML): class DIANInvoiceXML(fe.FeXML):
""" """
DianInvoiceXML mapea objeto form.Invoice a XML segun DianInvoiceXML mapea objeto form.Invoice a XML segun
@@ -11,16 +20,31 @@ class DIANInvoiceXML(fe.FeXML):
""" """
def __init__(self, invoice, tag_document='Invoice'): def __init__(self, invoice, tag_document='Invoice'):
super().__init__(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1') super().__init__(
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl') tag_document,
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource') 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') self.placeholder_for(
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID') ':DianExtensions/sts:InvoiceControl')
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceSource')
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareProvider')
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareSecurityCode')
self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/'
'sts:DianExtensions/sts:AuthorizationProvider/'
'sts:AuthorizationProviderID')
# ZE02 se requiere existencia para firmar # ZE02 se requiere existencia para firmar
ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True) ublextension = self.fragment(
extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent') './ext:UBLExtensions/ext:UBLExtension', append=True)
ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice) self.attach_invoice(invoice)
def set_supplier(fexml, invoice): def set_supplier(fexml, invoice):
@@ -28,72 +52,99 @@ class DIANInvoiceXML(fe.FeXML):
# DIAN 1.7.-2020: CAJ02 # DIAN 1.7.-2020: CAJ02
# DIAN 1.7.-2020: FAJ02 # DIAN 1.7.-2020: FAJ02
fexml.set_element('./cac:AccountingSupplierParty/cbc:AdditionalAccountID', fexml.set_element(
'./cac:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code) invoice.invoice_supplier.organization_code)
# DIAN 1.7.-2020: CAJ06 # DIAN 1.7.-2020: CAJ06
# DIAN 1.7.-2020: FAJ06 # DIAN 1.7.-2020: FAJ06
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_supplier.name) invoice.invoice_supplier.name)
# DIAN 1.7.-2020: CAJ07, CAJ08 # DIAN 1.7.-2020: CAJ07, CAJ08
# DIAN 1.7.-2020: FAJ07 # DIAN 1.7.-2020: FAJ07
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address')
# DIAN 1.7.-2020: FAJ08 # DIAN 1.7.-2020: FAJ08
# DIAN 1.7.-2020: CAJ09 # DIAN 1.7.-2020: CAJ09
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:ID',
invoice.invoice_supplier.address.city.code) invoice.invoice_supplier.address.city.code)
# DIAN 1.7.-2020: FAJ09 # DIAN 1.7.-2020: FAJ09
# DIAN 1.7.-2020: CAJ10 # DIAN 1.7.-2020: CAJ10
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CityName',
invoice.invoice_supplier.address.city.name) invoice.invoice_supplier.address.city.name)
# DIAN 1.7.-2020: FAJ11 # DIAN 1.7.-2020: FAJ11
# DIAN 1.7.-2020: CAJ11 # DIAN 1.7.-2020: CAJ11
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name) invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.7.-2020: FAJ12 # DIAN 1.7.-2020: FAJ12
# DIAN 1.7.-2020: CAJ12 # DIAN 1.7.-2020: CAJ12
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code) invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.7.-2020: FAJ14 # DIAN 1.7.-2020: FAJ14
# DIAN 1.7.-2020: CAJ13, CAJ14 # DIAN 1.7.-2020: CAJ13, CAJ14
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street) invoice.invoice_supplier.address.street)
# DIAN 1.7.-2020: FAJ16 # DIAN 1.7.-2020: FAJ16
# DIAN 1.7.-2020: CAJ16, CAJ16 # DIAN 1.7.-2020: CAJ16, CAJ16
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code) invoice.invoice_supplier.address.country.code)
# DIAN 1.7.-2020: FAJ17 # DIAN 1.7.-2020: FAJ17
# DIAN 1.7.-2020: CAJ17 # DIAN 1.7.-2020: CAJ17
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name, invoice.invoice_supplier.address.country.name,
# DIAN 1.7.-2020: FAJ18 # DIAN 1.7.-2020: FAJ18
languageID='es') languageID='es')
supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv, supplier_company_id_attrs.update(
{
'schemeID': invoice.invoice_supplier.ident.dv,
'schemeName': invoice.invoice_supplier.ident.type_fiscal}) 'schemeName': invoice.invoice_supplier.ident.type_fiscal})
# DIAN 1.7.-2020: FAJ19 # DIAN 1.7.-2020: FAJ19
# DIAN 1.7.-2020: CAJ19 # DIAN 1.7.-2020: CAJ19
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.7.-2020: FAJ20 # DIAN 1.7.-2020: FAJ20
# DIAN 1.7.-2020: CAJ20 # DIAN 1.7.-2020: CAJ20
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':RegistrationName',
invoice.invoice_supplier.legal_name) invoice.invoice_supplier.legal_name)
# DIAN 1.7.-2020: FAJ21 # DIAN 1.7.-2020: FAJ21
# DIAN 1.7.-2020: CAJ21 # DIAN 1.7.-2020: CAJ21
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':CompanyID',
invoice.invoice_supplier.ident, invoice.invoice_supplier.ident,
# DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25 # DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25
**supplier_company_id_attrs) **supplier_company_id_attrs)
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':TaxLevelCode',
# DIAN 1.7.-2020: FAJ26 # DIAN 1.7.-2020: FAJ26
# DIAN 1.7.-2020: CAJ26 # DIAN 1.7.-2020: CAJ26
invoice.invoice_supplier.responsability_code, invoice.invoice_supplier.responsability_code,
@@ -102,139 +153,197 @@ class DIANInvoiceXML(fe.FeXML):
listName=invoice.invoice_supplier.responsability_regime_code) listName=invoice.invoice_supplier.responsability_regime_code)
# DIAN 1.7.-2020: FAJ28 # DIAN 1.7.-2020: FAJ28
# DIAN 1.7.-2020: CAJ28 # DIAN 1.7.-2020: CAJ28
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress')
# DIAN 1.7.-2020: FAJ29 # DIAN 1.7.-2020: FAJ29
# DIAN 1.7.-2020: CAJ29 # DIAN 1.7.-2020: CAJ29
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:ID',
invoice.invoice_supplier.address.city.code) invoice.invoice_supplier.address.city.code)
# DIAN 1.7.-2020: FAJ30 # DIAN 1.7.-2020: FAJ30
# DIAN 1.7.-2020: CAJ30 # DIAN 1.7.-2020: CAJ30
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city.name) fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:CityName',
invoice.invoice_supplier.address.city.name)
# DIAN 1.7.-2020: FAJ31 # DIAN 1.7.-2020: FAJ31
# DIAN 1.7.-2020: CAJ31 # DIAN 1.7.-2020: CAJ31
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name) invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.7.-2020: FAJ32 # DIAN 1.7.-2020: FAJ32
# DIAN 1.7.-2020: CAJ32 # DIAN 1.7.-2020: CAJ32
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code) invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.7.-2020: FAJ33,FAJ34 # DIAN 1.7.-2020: FAJ33,FAJ34
# DIAN 1.7.-2020: CAJ33,CAJ34 # DIAN 1.7.-2020: CAJ33,CAJ34
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street) invoice.invoice_supplier.address.street)
# DIAN 1.7.-2020: FAJ35,FAJ36 # DIAN 1.7.-2020: FAJ35,FAJ36
# DIAN 1.7.-2020: CAJ35,CAJ36 # DIAN 1.7.-2020: CAJ35,CAJ36
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code) invoice.invoice_supplier.address.country.code)
# DIAN 1.7.-2020: FAJ37,FAJ38 # DIAN 1.7.-2020: FAJ37,FAJ38
# DIAN 1.7.-2020: CAJ37,CAJ38 # DIAN 1.7.-2020: CAJ37,CAJ38
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name, invoice.invoice_supplier.address.country.name,
languageID='es') languageID='es')
# DIAN 1.7.-2020: FAJ39 # DIAN 1.7.-2020: FAJ39
# DIAN 1.7.-2020: CAJ39 # DIAN 1.7.-2020: CAJ39
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme')
# DIAN 1.7.-2020: CAJ40 # DIAN 1.7.-2020: CAJ40
# DIAN 1.7.-2020: FAJ40 # DIAN 1.7.-2020: FAJ40
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code) invoice.invoice_customer.tax_scheme.code)
# DIAN 1.7.-2020: CAJ41 # DIAN 1.7.-2020: CAJ41
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name) invoice.invoice_customer.tax_scheme.name)
# DIAN 1.7.-2020: FAJ42 # DIAN 1.7.-2020: FAJ42
# DIAN 1.7.-2020: CAJ42 # DIAN 1.7.-2020: CAJ42
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity')
# DIAN 1.7.-2020: FAJ43 # DIAN 1.7.-2020: FAJ43
# DIAN 1.7.-2020: CAJ43 # DIAN 1.7.-2020: CAJ43
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc'
':RegistrationName',
invoice.invoice_supplier.legal_name) invoice.invoice_supplier.legal_name)
# DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48 # DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48
# DIAN 1.7.-2020: CAJ44,CAJ45,CAJ46,CAJ47,CAJ48 # DIAN 1.7.-2020: CAJ44,CAJ45,CAJ46,CAJ47,CAJ48
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc'
':CompanyID',
invoice.invoice_supplier.ident, invoice.invoice_supplier.ident,
**supplier_company_id_attrs) **supplier_company_id_attrs)
# DIAN 1.7.-2020: FAJ49 # DIAN 1.7.-2020: FAJ49
# DIAN 1.7.-2020: CAJ49 # DIAN 1.7.-2020: CAJ49
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac'
':CorporateRegistrationScheme')
# DIAN 1.7.-2020: FAJ50 # DIAN 1.7.-2020: FAJ50
# DIAN 1.7.-2020: CAJ50 # DIAN 1.7.-2020: CAJ50
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac'
':CorporateRegistrationScheme/cbc:ID',
invoice.invoice_ident_prefix) invoice.invoice_ident_prefix)
# DIAN 1.7.-2020: CAJ67 # DIAN 1.7.-2020: CAJ67
fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:Contact') fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:Contact')
# DIAN 1.7.-2020: FAJ71 # DIAN 1.7.-2020: FAJ71
# DIAN 1.7.-2020: CAJ71 # DIAN 1.7.-2020: CAJ71
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc'
':ElectronicMail',
invoice.invoice_supplier.email) invoice.invoice_supplier.email)
def set_customer(fexml, invoice): def set_customer(fexml, invoice):
fexml.placeholder_for('./cac:AccountingCustomerParty') fexml.placeholder_for('./cac:AccountingCustomerParty')
fexml.set_element('./cac:AccountingCustomerParty/cbc:AdditionalAccountID', fexml.set_element(
'./cac:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code) invoice.invoice_customer.organization_code)
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification'
'/cbc:ID',
invoice.invoice_customer.ident) invoice.invoice_customer.ident)
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name',
invoice.invoice_customer.name) invoice.invoice_customer.name)
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation')
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
# DIAN 1.7.-2020: FAK25 # DIAN 1.7.-2020: FAK25
# DIAN 1.7.-2020: CAK25 # DIAN 1.7.-2020: CAK25
customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv, customer_company_id_attrs.update(
{
'schemeID': invoice.invoice_customer.ident.dv,
'schemeName': invoice.invoice_customer.ident.type_fiscal}) 'schemeName': invoice.invoice_customer.ident.type_fiscal})
# DIAN 1.7.-2020: FAK07 # DIAN 1.7.-2020: FAK07
# DIAN 1.7.-2020: CAK07 # DIAN 1.7.-2020: CAK07
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address')
# DIAN 1.7.-2020: FAK08 # DIAN 1.7.-2020: FAK08
# DIAN 1.7.-2020: CAK08 # DIAN 1.7.-2020: CAK08
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:ID',
invoice.invoice_customer.address.city.code) invoice.invoice_customer.address.city.code)
# DIAN 1.7.-2020: FAK09 # DIAN 1.7.-2020: FAK09
# DIAN 1.7.-2020: CAK09 # DIAN 1.7.-2020: CAK09
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', invoice.invoice_customer.address.city.name) fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CityName',
invoice.invoice_customer.address.city.name)
# DIAN 1.7.-2020: FAK11 # DIAN 1.7.-2020: FAK11
# DIAN 1.7.-2020: CAK11 # DIAN 1.7.-2020: CAK11
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name) invoice.invoice_customer.address.countrysubentity.name)
# DIAN 1.7.-2020: FAK12 # DIAN 1.7.-2020: FAK12
# DIAN 1.7.-2020: CAK12 # DIAN 1.7.-2020: CAK12
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code) invoice.invoice_customer.address.countrysubentity.code)
# DIAN 1.7.-2020: CAK13, CAK14 # DIAN 1.7.-2020: CAK13, CAK14
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street) invoice.invoice_customer.address.street)
# DIAN 1.7.-2020: CAK16 # DIAN 1.7.-2020: CAK16
# DIAN 1.7.-2020: FAK16 # DIAN 1.7.-2020: FAK16
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code) invoice.invoice_customer.address.country.code)
# DIAN 1.7.-2020: FAK17 # DIAN 1.7.-2020: FAK17
# DIAN 1.7.-2020: CAK17 # DIAN 1.7.-2020: CAK17
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:Name',
invoice.invoice_customer.address.country.name, invoice.invoice_customer.address.country.name,
# DIAN 1.7.-2020: FAK18 # DIAN 1.7.-2020: FAK18
# DIAN 1.7.-2020: CAK18 # DIAN 1.7.-2020: CAK18
@@ -242,22 +351,29 @@ class DIANInvoiceXML(fe.FeXML):
# DIAN 1.7.-2020: FAK17,FAK19 # DIAN 1.7.-2020: FAK17,FAK19
# DIAN 1.7.-2020: CAK19 # DIAN 1.7.-2020: CAK19
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme')
# DIAN 1.7.-2020: FAK17,FAK20 # DIAN 1.7.-2020: FAK17,FAK20
# DIAN 1.7.-2020: CAK20 # DIAN 1.7.-2020: CAK20
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':RegistrationName',
invoice.invoice_customer.legal_name) invoice.invoice_customer.legal_name)
# DIAN 1.7.-2020: CAK21 # DIAN 1.7.-2020: CAK21
# DIAN 1.7.-2020: FAK21 # DIAN 1.7.-2020: FAK21
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':CompanyID',
invoice.invoice_customer.ident, invoice.invoice_customer.ident,
# DIAN 1.7.-2020: CAK22, CAK23, CAK24, CAK25 # DIAN 1.7.-2020: CAK22, CAK23, CAK24, CAK25
**customer_company_id_attrs) **customer_company_id_attrs)
# DIAN 1.7.-2020: CAK26 # DIAN 1.7.-2020: CAK26
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':TaxLevelCode',
# DIAN 1.7.-2020: FAK26 # DIAN 1.7.-2020: FAK26
invoice.invoice_customer.responsability_code, invoice.invoice_customer.responsability_code,
# DIAN 1.7.-2020: FAK27 # DIAN 1.7.-2020: FAK27
@@ -266,98 +382,140 @@ class DIANInvoiceXML(fe.FeXML):
# DIAN 1.7.-2020: FAK28 # DIAN 1.7.-2020: FAK28
# DIAN 1.7.-2020: CAK28 # DIAN 1.7.-2020: CAK28
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress')
# DIAN 1.7.-2020: FAK29 # DIAN 1.7.-2020: FAK29
# DIAN 1.7.-2020: CAK29 # DIAN 1.7.-2020: CAK29
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:ID',
invoice.invoice_customer.address.city.code) invoice.invoice_customer.address.city.code)
# DIAN 1.7.-2020: FAK30 # DIAN 1.7.-2020: FAK30
# DIAN 1.7.-2020: CAK30 # DIAN 1.7.-2020: CAK30
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:CityName',
invoice.invoice_customer.address.city.name) invoice.invoice_customer.address.city.name)
# DIAN 1.7.-2020: FAK31 # DIAN 1.7.-2020: FAK31
# DIAN 1.7.-2020: CAK31 # DIAN 1.7.-2020: CAK31
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:CountrySubentity',
invoice.invoice_customer.address.countrysubentity.name) invoice.invoice_customer.address.countrysubentity.name)
# DIAN 1.7.-2020: FAK32 # DIAN 1.7.-2020: FAK32
# DIAN 1.7.-2020: CAK32 # DIAN 1.7.-2020: CAK32
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cbc:CountrySubentityCode',
invoice.invoice_customer.address.countrysubentity.code) invoice.invoice_customer.address.countrysubentity.code)
# DIAN 1.7.-2020: FAK33 # DIAN 1.7.-2020: FAK33
# DIAN 1.7.-2020: CAK33 # DIAN 1.7.-2020: CAK33
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:AddressLine')
# DIAN 1.7.-2020: FAK34 # DIAN 1.7.-2020: FAK34
# DIAN 1.7.-2020: CAK34 # DIAN 1.7.-2020: CAK34
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address.street) invoice.invoice_customer.address.street)
# DIAN 1.7.-2020: CAK35 # DIAN 1.7.-2020: CAK35
# DIAN 1.7.-2020: FAK35 # DIAN 1.7.-2020: FAK35
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:Country')
# DIAN 1.7.-2020: CAK36 # DIAN 1.7.-2020: CAK36
# DIAN 1.7.-2020: FAK36 # DIAN 1.7.-2020: FAK36
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code) invoice.invoice_customer.address.country.code)
# DIAN 1.7.-2020: CAK37 # DIAN 1.7.-2020: CAK37
# DIAN 1.7.-2020: FAK37 # DIAN 1.7.-2020: FAK37
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', invoice.invoice_customer.address.country.name) fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:Country/cbc:Name',
invoice.invoice_customer.address.country.name)
# DIAN 1.7.-2020: FAK38 # DIAN 1.7.-2020: FAK38
# DIAN 1.7.-2020: CAK38 # DIAN 1.7.-2020: CAK38
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':RegistrationAddress/cac:Country/cbc:IdentificationCode',
invoice.invoice_customer.address.country.code, invoice.invoice_customer.address.country.code,
languageID='es') languageID='es')
# DIAN 1.7.-2020: CAK39 # DIAN 1.7.-2020: CAK39
# DIAN 1.7.-2020: FAK39 # DIAN 1.7.-2020: FAK39
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme')
# DIAN 1.7.-2020: CAK40 Machete Construir Validación # DIAN 1.7.-2020: CAK40 Machete Construir Validación
# DIAN 1.7.-2020: FAK40 # DIAN 1.7.-2020: FAK40
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code) invoice.invoice_customer.tax_scheme.code)
# DIAN 1.7.-2020: FAK41 # DIAN 1.7.-2020: FAK41
# DIAN 1.7.-2020: CAK41 Machete Construir Validación # DIAN 1.7.-2020: CAK41 Machete Construir Validación
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name) invoice.invoice_customer.tax_scheme.name)
# DIAN 1.7.-2020: FAK42 # DIAN 1.7.-2020: FAK42
# DIAN 1.7.-2020: CAK42 # DIAN 1.7.-2020: CAK42
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
# DIAN 1.7.-2020: FAK43 # DIAN 1.7.-2020: FAK43
# DIAN 1.7.-2020: CAK43 # DIAN 1.7.-2020: CAK43
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc'
':RegistrationName',
invoice.invoice_customer.legal_name) invoice.invoice_customer.legal_name)
# DIAN 1.7.-2020: CAK44 # DIAN 1.7.-2020: CAK44
# DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48 # DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc'
':CompanyID',
invoice.invoice_customer.ident, invoice.invoice_customer.ident,
**customer_company_id_attrs) **customer_company_id_attrs)
fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity') fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity')
# DIAN 1.7.-2020: FAK55 # DIAN 1.7.-2020: FAK55
# DIAN 1.7.-2020: CAK51, CAK55 # DIAN 1.7.-2020: CAK51, CAK55
fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail', fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc'
':ElectronicMail',
invoice.invoice_customer.email) invoice.invoice_customer.email)
def set_payment_mean(fexml, invoice): def set_payment_mean(fexml, invoice):
payment_mean = invoice.invoice_payment_mean payment_mean = invoice.invoice_payment_mean
fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id) fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id)
fexml.set_element('./cac:PaymentMeans/cbc:PaymentMeansCode', payment_mean.code) fexml.set_element(
fexml.set_element('./cac:PaymentMeans/cbc:PaymentDueDate', payment_mean.due_at.strftime('%Y-%m-%d')) './cac:PaymentMeans/cbc:PaymentMeansCode',
fexml.set_element('./cac:PaymentMeans/cbc:PaymentID', payment_mean.payment_id) payment_mean.code)
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentDueDate',
payment_mean.due_at.strftime('%Y-%m-%d'))
fexml.set_element(
'./cac:PaymentMeans/cbc:PaymentID',
payment_mean.payment_id)
def set_element_amount_for(fexml, xml, xpath, amount): def set_element_amount_for(fexml, xml, xpath, amount):
if not isinstance(amount, Amount): if not isinstance(amount, Amount):
@@ -372,38 +530,48 @@ class DIANInvoiceXML(fe.FeXML):
fexml.set_element(xpath, amount, currencyID=amount.currency.code) fexml.set_element(xpath, amount, currencyID=amount.currency.code)
def set_legal_monetary(fexml, invoice): def set_legal_monetary(fexml, invoice):
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:LineExtensionAmount', fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount) invoice.invoice_legal_monetary_total.line_extension_amount)
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount) invoice.invoice_legal_monetary_total.tax_exclusive_amount)
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount) invoice.invoice_legal_monetary_total.tax_inclusive_amount)
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:ChargeTotalAmount', fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount) invoice.invoice_legal_monetary_total.charge_total_amount)
fexml.set_element_amount('./cac:LegalMonetaryTotal/cbc:PayableAmount', fexml.set_element_amount(
'./cac:LegalMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount) invoice.invoice_legal_monetary_total.payable_amount)
def _set_invoice_document_reference(fexml, reference): 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): 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): 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): def _do_set_billing_reference(fexml, reference, tag_document):
fexml.set_element('./cac:BillingReference/%s/cbc:ID' % (tag_document), fexml.set_element('./cac:BillingReference/%s/cbc:ID' % (tag_document),
reference.ident) reference.ident)
fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID', fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID',
reference.uuid, reference.uuid,
schemeName='CUFE-SHA384') schemeName='CUFE-SHA384')
fexml.set_element('./cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate', fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/'
'cbc:IssueDate',
reference.date.strftime("%Y-%m-%d")) reference.date.strftime("%Y-%m-%d"))
def set_billing_reference(fexml, invoice): def set_billing_reference(fexml, invoice):
@@ -421,7 +589,8 @@ class DIANInvoiceXML(fe.FeXML):
def set_invoice_totals(fexml, invoice): def set_invoice_totals(fexml, invoice):
tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0)))
withholding_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) withholding_amount_for = defaultdict(
lambda: defaultdict(lambda: Amount(0.0)))
percent_for = defaultdict(lambda: None) percent_for = defaultdict(lambda: None)
# requeridos para CUFE # requeridos para CUFE
@@ -439,9 +608,12 @@ class DIANInvoiceXML(fe.FeXML):
for invoice_line in invoice.invoice_lines: for invoice_line in invoice.invoice_lines:
for subtotal in invoice_line.tax.subtotals: for subtotal in invoice_line.tax.subtotals:
if subtotal.scheme is not None: if subtotal.scheme is not None:
tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount scheme = subtotal.scheme.code
tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount tax_amount_for[scheme][
tax_amount_for[subtotal.scheme.code]['name'] = subtotal.scheme.name 'tax_amount'] += subtotal.tax_amount
tax_amount_for[scheme][
'taxable_amount'] += invoice_line.taxable_amount
tax_amount_for[scheme]['name'] = subtotal.scheme.name
# MACHETE ojo InvoiceLine.tax pasar a Invoice # MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal.scheme.code] = subtotal.percent percent_for[subtotal.scheme.code] = subtotal.percent
@@ -450,12 +622,16 @@ class DIANInvoiceXML(fe.FeXML):
for subtotal_withholding in invoice_line.withholding.subtotals: for subtotal_withholding in invoice_line.withholding.subtotals:
if subtotal_withholding.scheme is not None: if subtotal_withholding.scheme is not None:
withholding_amount_for[subtotal_withholding.scheme.code]['tax_amount'] += subtotal_withholding.tax_amount scheme = subtotal_withholding.scheme.code
withholding_amount_for[subtotal_withholding.scheme.code]['taxable_amount'] += invoice_line.withholding_taxable_amount withholding_amount_for[scheme][
'tax_amount'] += subtotal_withholding.tax_amount
withholding_amount_for[scheme][
'taxable_amount'] += (
invoice_line.withholding_taxable_amount)
# MACHETE ojo InvoiceLine.tax pasar a Invoice # MACHETE ojo InvoiceLine.tax pasar a Invoice
percent_for[subtotal_withholding.scheme.code] = subtotal_withholding.percent percent_for[scheme] = subtotal_withholding.percent
total_withholding_amount += subtotal_withholding.tax_amount total_withholding_amount += subtotal_withholding.tax_amount
@@ -477,12 +653,14 @@ class DIANInvoiceXML(fe.FeXML):
tax_amount) tax_amount)
# DIAN 1.7.-2020: FAS05 # DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', '/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount']) amount_of['taxable_amount'])
# DIAN 1.7.-2020: FAU06 # DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', '/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount']) amount_of['tax_amount'])
@@ -491,14 +669,19 @@ class DIANInvoiceXML(fe.FeXML):
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent', line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto]) percent_for[cod_impuesto])
if percent_for[cod_impuesto]: if percent_for[cod_impuesto]:
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/'
'cbc:Percent',
percent_for[cod_impuesto]) percent_for[cod_impuesto])
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme'
'/cbc:ID',
cod_impuesto) cod_impuesto)
line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme'
'/cbc:Name',
amount_of['name']) amount_of['name'])
for index, item in enumerate(withholding_amount_for.items()): for index, item in enumerate(withholding_amount_for.items()):
@@ -506,36 +689,45 @@ class DIANInvoiceXML(fe.FeXML):
next_append = index > 0 next_append = index > 0
# DIAN 1.7.-2020: FAS01 # DIAN 1.7.-2020: FAS01
line = fexml.fragment('./cac:WithholdingTaxTotal', append=next_append) line = fexml.fragment(
'./cac:WithholdingTaxTotal',
append=next_append)
# DIAN 1.7.-2020: FAU06 # DIAN 1.7.-2020: FAU06
tax_amount = amount_of['tax_amount'] tax_amount = amount_of['tax_amount']
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
'/cac:WithholdingTaxTotal/cbc:TaxAmount', line, '/cac:WithholdingTaxTotal/cbc:TaxAmount', tax_amount)
tax_amount)
# DIAN 1.7.-2020: FAS05 # DIAN 1.7.-2020: FAS05
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
amount_of['taxable_amount']) amount_of['taxable_amount'])
# DIAN 1.7.-2020: FAU06 # DIAN 1.7.-2020: FAU06
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount',
amount_of['tax_amount']) amount_of['tax_amount'])
# DIAN 1.7.-2020: FAS07 # DIAN 1.7.-2020: FAS07
if percent_for[cod_impuesto]: if percent_for[cod_impuesto]:
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:Percent', line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:Percent',
percent_for[cod_impuesto]) percent_for[cod_impuesto])
if percent_for[cod_impuesto]: if percent_for[cod_impuesto]:
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory'
'/cbc:Percent',
percent_for[cod_impuesto]) percent_for[cod_impuesto])
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac'
':TaxScheme/cbc:ID',
cod_impuesto) cod_impuesto)
line.set_element('/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', line.set_element(
'/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac'
':TaxScheme/cbc:Name',
'ReteRenta') 'ReteRenta')
# abstract method # abstract method
@@ -552,50 +744,87 @@ class DIANInvoiceXML(fe.FeXML):
invoice_line.tax_amount) invoice_line.tax_amount)
# DIAN 1.7.-2020: FAX05 # DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.taxable_amount) invoice_line.taxable_amount)
for subtotal in invoice_line.tax.subtotals: 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: 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: if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15 # DIAN 1.7.-2020: FAX15
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) line.set_element(
line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) './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_line_withholding(fexml, line, invoice_line): def set_invoice_line_withholding(fexml, line, invoice_line):
fexml.set_element_amount_for(line, fexml.set_element_amount_for(line,
'./cac:WithholdingTaxTotal/cbc:TaxAmount', './cac:WithholdingTaxTotal/cbc:TaxAmount',
invoice_line.withholding_amount) invoice_line.withholding_amount)
# DIAN 1.7.-2020: FAX05 # DIAN 1.7.-2020: FAX05
fexml.set_element_amount_for(line, fexml.set_element_amount_for(
line,
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', './cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount',
invoice_line.withholding_taxable_amount) invoice_line.withholding_taxable_amount)
for subtotal in invoice_line.withholding.subtotals: 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: 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: if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15 # DIAN 1.7.-2020: FAX15
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) line.set_element(
line.set_element('./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) './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_lines(fexml, invoice): def set_invoice_lines(fexml, invoice):
next_append = False next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines): 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 next_append = True
line.set_element('./cbc:ID', index + 1) 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, fexml.set_element_amount_for(line,
'./cbc:LineExtensionAmount', './cbc:LineExtensionAmount',
invoice_line.total_amount) invoice_line.total_amount)
@@ -603,18 +832,26 @@ class DIANInvoiceXML(fe.FeXML):
if not isinstance(invoice_line.tax, TaxTotalOmit): if not isinstance(invoice_line.tax, TaxTotalOmit):
fexml.set_invoice_line_tax(line, invoice_line) 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) fexml.set_invoice_line_withholding(line, invoice_line)
line.set_element('./cac:Item/cbc:Description', invoice_line.item.description) line.set_element(
'./cac:Item/cbc:Description',
invoice_line.item.description)
line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', line.set_element(
'./cac:Item/cac:StandardItemIdentification/cbc:ID',
invoice_line.item.id, invoice_line.item.id,
schemeID=invoice_line.item.scheme_id, schemeID=invoice_line.item.scheme_id,
schemeName=invoice_line.item.scheme_name, schemeName=invoice_line.item.scheme_name,
schemeAgencyID=invoice_line.item.scheme_agency_id) 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) line.set_element(
'./cac:Price/cbc:PriceAmount',
invoice_line.price.amount,
currencyID=invoice_line.price.amount.currency.code)
# DIAN 1.7.-2020: FBB04 # DIAN 1.7.-2020: FBB04
line.set_element('./cac:Price/cbc:BaseQuantity', line.set_element('./cac:Price/cbc:BaseQuantity',
invoice_line.price.quantity, invoice_line.price.quantity,
@@ -622,7 +859,8 @@ class DIANInvoiceXML(fe.FeXML):
for idx, charge in enumerate(invoice_line.allowance_charge): for idx, charge in enumerate(invoice_line.allowance_charge):
next_append_charge = idx > 0 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): def set_allowance_charge(fexml, invoice):
for idx, charge in enumerate(invoice.invoice_allowance_charge): for idx, charge in enumerate(invoice.invoice_allowance_charge):
@@ -656,18 +894,28 @@ class DIANInvoiceXML(fe.FeXML):
fexml.placeholder_for('./ext:UBLExtensions') fexml.placeholder_for('./ext:UBLExtensions')
fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1') 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:ProfileID')
fexml.placeholder_for('./cbc:ProfileExecutionID') fexml.placeholder_for('./cbc:ProfileExecutionID')
fexml.set_element('./cbc:ID', invoice.invoice_ident) fexml.set_element('./cbc:ID', invoice.invoice_ident)
fexml.placeholder_for('./cbc:UUID') fexml.placeholder_for('./cbc:UUID')
fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) fexml.set_element(
'./cbc:IssueDate',
invoice.invoice_issue.strftime('%Y-%m-%d'))
# DIAN 1.7.-2020: FAD10 # DIAN 1.7.-2020: FAD10
fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) fexml.set_element(
fexml.set_element('./cbc:%sTypeCode' % (fexml.tag_document()), './cbc:IssueTime',
invoice.invoice_issue.strftime('%H:%M:%S-05:00'))
fexml.set_element(
'./cbc:%sTypeCode' %
(fexml.tag_document()),
invoice.invoice_type_code, invoice.invoice_type_code,
listAgencyID='195', listAgencyID='195',
listAgencyName='No matching global declaration available for the validation root', listAgencyName=(
'No matching global declaration available for the '
'validation root'),
listURI='http://www.dian.gov.co') listURI='http://www.dian.gov.co')
fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') fexml.set_element('./cbc:DocumentCurrencyCode', 'COP')
fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines))

View File

@@ -14,36 +14,45 @@ __all__ = ['DIANSupportDocumentXML']
class DIANSupportDocumentXML(fe.FeXML): class DIANSupportDocumentXML(fe.FeXML):
""" """
DianSupportDocumentXML mapea objeto form.Invoice a XML segun 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'): def __init__(self, invoice, tag_document='Invoice'):
super().__init__(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1') super().__init__(
tag_document,
'http://www.dian.gov.co/contratos/facturaelectronica/v1')
# DIAN 1.1.-2021: DSAB03 # DIAN 1.1.-2021: DSAB03
# DIAN 1.1.-2021: NSAB03 # DIAN 1.1.-2021: NSAB03
self.placeholder_for( self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceControl')
# DIAN 1.1.-2021: DSAB13 # DIAN 1.1.-2021: DSAB13
# DIAN 1.1.-2021: NSAB13 # DIAN 1.1.-2021: NSAB13
self.placeholder_for( self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:InvoiceSource')
# DIAN 1.1.-2021: DSAB18 # DIAN 1.1.-2021: DSAB18
# DIAN 1.1.-2021: NSAB18 # DIAN 1.1.-2021: NSAB18
self.placeholder_for( self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareProvider')
# DIAN 1.1.-2021: DSAB27 # DIAN 1.1.-2021: DSAB27
# DIAN 1.1.-2021: NSAB27 # DIAN 1.1.-2021: NSAB27
self.placeholder_for( self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts'
':DianExtensions/sts:SoftwareSecurityCode')
# DIAN 1.1.-2021: DSAB30 DSAB31 # DIAN 1.1.-2021: DSAB30 DSAB31
# DIAN 1.1.-2021: NSAB30 NSAB31 # DIAN 1.1.-2021: NSAB30 NSAB31
self.placeholder_for( self.placeholder_for(
'./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID') './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/'
'sts:DianExtensions/sts:AuthorizationProvider/'
'sts:AuthorizationProviderID')
# ZE02 se requiere existencia para firmar # ZE02 se requiere existencia para firmar
# DIAN 1.1.-2021: DSAA02 DSAB01 # DIAN 1.1.-2021: DSAA02 DSAB01
@@ -52,7 +61,7 @@ class DIANSupportDocumentXML(fe.FeXML):
'./ext:UBLExtensions/ext:UBLExtension', append=True) './ext:UBLExtensions/ext:UBLExtension', append=True)
# DIAN 1.1.-2021: DSAB02 # DIAN 1.1.-2021: DSAB02
# DIAN 1.1.-2021: NSAB02 # DIAN 1.1.-2021: NSAB02
extcontent = ublextension.find_or_create_element( ublextension.find_or_create_element(
'/ext:UBLExtension/ext:ExtensionContent') '/ext:UBLExtension/ext:ExtensionContent')
self.attach_invoice(invoice) self.attach_invoice(invoice)
@@ -70,51 +79,61 @@ class DIANSupportDocumentXML(fe.FeXML):
# DIAN 1.1.-2021: DSAJ07 DSAJ08 # DIAN 1.1.-2021: DSAJ07 DSAJ08
# DIAN 1.1.-2021: NSAJ07 NSAJ08 # DIAN 1.1.-2021: NSAJ07 NSAJ08
fexml.placeholder_for( fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address')
# DIAN 1.1.-2021: DSAJ09 # DIAN 1.1.-2021: DSAJ09
# DIAN 1.1.-2021: NSAJ09 # DIAN 1.1.-2021: NSAJ09
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:ID',
invoice.invoice_supplier.address.city.code) invoice.invoice_supplier.address.city.code)
# DIAN 1.1.-2021: DSAJ10 # DIAN 1.1.-2021: DSAJ10
# DIAN 1.1.-2021: NSAJ10 # DIAN 1.1.-2021: NSAJ10
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CityName',
invoice.invoice_supplier.address.city.name) invoice.invoice_supplier.address.city.name)
# DIAN 1.1.-2021: DSAJ73 # DIAN 1.1.-2021: DSAJ73
# DIAN 1.1.-2021: NSAJ73 # DIAN 1.1.-2021: NSAJ73
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:PostalZone', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:PostalZone',
invoice.invoice_supplier.address.postalzone.code) invoice.invoice_supplier.address.postalzone.code)
# DIAN 1.1.-2021: DSAJ11 # DIAN 1.1.-2021: DSAJ11
# DIAN 1.1.-2021: NSAJ11 # DIAN 1.1.-2021: NSAJ11
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentity',
invoice.invoice_supplier.address.countrysubentity.name) invoice.invoice_supplier.address.countrysubentity.name)
# DIAN 1.1.-2021: DSAJ12 # DIAN 1.1.-2021: DSAJ12
# DIAN 1.1.-2021: NSAJ12 # DIAN 1.1.-2021: NSAJ12
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cbc:CountrySubentityCode',
invoice.invoice_supplier.address.countrysubentity.code) invoice.invoice_supplier.address.countrysubentity.code)
# DIAN 1.1.-2021: NSAJ13 NSAJ14 # DIAN 1.1.-2021: NSAJ13 NSAJ14
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address.street) invoice.invoice_supplier.address.street)
# DIAN 1.1.-2021: DSAJ15 DSAJ16 # DIAN 1.1.-2021: DSAJ15 DSAJ16
# DIAN 1.1.-2021: NSAJ15 NSAJ16 # DIAN 1.1.-2021: NSAJ15 NSAJ16
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:IdentificationCode',
invoice.invoice_supplier.address.country.code) invoice.invoice_supplier.address.country.code)
# DIAN 1.1.-2021: DSAJ17 # DIAN 1.1.-2021: DSAJ17
# DIAN 1.1.-2021: NSAJ17 # DIAN 1.1.-2021: NSAJ17
fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac'
':Address/cac:Country/cbc:Name',
invoice.invoice_supplier.address.country.name, invoice.invoice_supplier.address.country.name,
# DIAN 1.1.-2021: DSAJ18 # DIAN 1.1.-2021: DSAJ18
# # DIAN 1.1.-2021: NSAJ18 # # DIAN 1.1.-2021: NSAJ18
@@ -134,13 +153,15 @@ class DIANSupportDocumentXML(fe.FeXML):
# DIAN 1.1.-2021: DSAJ20 # DIAN 1.1.-2021: DSAJ20
# DIAN 1.1.-2021: NSAJ20 # DIAN 1.1.-2021: NSAJ20
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':RegistrationName',
invoice.invoice_supplier.legal_name) invoice.invoice_supplier.legal_name)
# DIAN 1.1.-2021: DSAJ21 # DIAN 1.1.-2021: DSAJ21
# DIAN 1.1.-2021: NSAJ21 # DIAN 1.1.-2021: NSAJ21
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':CompanyID',
invoice.invoice_supplier.ident, invoice.invoice_supplier.ident,
# DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25 # DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25
# DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25 # DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25
@@ -149,25 +170,29 @@ class DIANSupportDocumentXML(fe.FeXML):
# DIAN 1.1.-2021: DSAJ26 # DIAN 1.1.-2021: DSAJ26
# DIAN 1.1.-2021: NSAJ26 # DIAN 1.1.-2021: NSAJ26
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc'
':TaxLevelCode',
invoice.invoice_supplier.responsability_code, invoice.invoice_supplier.responsability_code,
listName=invoice.invoice_supplier.responsability_regime_code) listName=invoice.invoice_supplier.responsability_regime_code)
# DIAN 1.1.-2021: DSAJ39 # DIAN 1.1.-2021: DSAJ39
# DIAN 1.1.-2021: NSAJ39 # DIAN 1.1.-2021: NSAJ39
fexml.placeholder_for( fexml.placeholder_for(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme')
# DIAN 1.1.-2021: DSAJ40 # DIAN 1.1.-2021: DSAJ40
# DIAN 1.1.-2021: NSAJ40 # DIAN 1.1.-2021: NSAJ40
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code) invoice.invoice_customer.tax_scheme.code)
# DIAN 1.1.-2021: DSAJ41 # DIAN 1.1.-2021: DSAJ41
# DIAN 1.1.-2021: NSAJ41 # DIAN 1.1.-2021: NSAJ41
fexml.set_element( fexml.set_element(
'./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name) invoice.invoice_customer.tax_scheme.name)
def set_customer(fexml, invoice): def set_customer(fexml, invoice):
@@ -193,7 +218,8 @@ class DIANSupportDocumentXML(fe.FeXML):
# DIAN 1.1.-2021: DSAK20 # DIAN 1.1.-2021: DSAK20
# DIAN 1.1.-2021: NSAK20 # DIAN 1.1.-2021: NSAK20
fexml.set_element( fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':RegistrationName',
invoice.invoice_customer.legal_name) invoice.invoice_customer.legal_name)
customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy()
@@ -205,7 +231,8 @@ class DIANSupportDocumentXML(fe.FeXML):
# DIAN 1.1.-2021: DSAK21 # DIAN 1.1.-2021: DSAK21
# DIAN 1.1.-2021: NSAK21 # DIAN 1.1.-2021: NSAK21
fexml.set_element( fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':CompanyID',
invoice.invoice_customer.ident, invoice.invoice_customer.ident,
# DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25 # DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25
# DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25 # DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25
@@ -214,24 +241,28 @@ class DIANSupportDocumentXML(fe.FeXML):
# DIAN 1.1.-2021: DSAK26 # DIAN 1.1.-2021: DSAK26
# DIAN 1.1.-2021: NSAK26 # DIAN 1.1.-2021: NSAK26
fexml.set_element( fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc'
':TaxLevelCode',
invoice.invoice_customer.responsability_code) invoice.invoice_customer.responsability_code)
# DIAN 1.1.-2021: DSAK39 # DIAN 1.1.-2021: DSAK39
# DIAN 1.1.-2021: NSAK39 # DIAN 1.1.-2021: NSAK39
fexml.placeholder_for( fexml.placeholder_for(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme')
# DIAN 1.1.-2021: DSAK40 # DIAN 1.1.-2021: DSAK40
# DIAN 1.1.-2021: NSAK40 # DIAN 1.1.-2021: NSAK40
fexml.set_element( fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:ID',
invoice.invoice_customer.tax_scheme.code) invoice.invoice_customer.tax_scheme.code)
# DIAN 1.1.-2021: DSAK41 # DIAN 1.1.-2021: DSAK41
# DIAN 1.1.-2021: NSAK41 # DIAN 1.1.-2021: NSAK41
fexml.set_element( fexml.set_element(
'./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac'
':TaxScheme/cbc:Name',
invoice.invoice_customer.tax_scheme.name) invoice.invoice_customer.tax_scheme.name)
def set_payment_mean(fexml, invoice): def set_payment_mean(fexml, invoice):
@@ -328,7 +359,8 @@ class DIANSupportDocumentXML(fe.FeXML):
reference.uuid, reference.uuid,
schemeName=schemeName) schemeName=schemeName)
fexml.set_element( fexml.set_element(
'./cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate', './cac:BillingReference/cac:InvoiceDocumentReference/'
'cbc:IssueDate',
reference.date.strftime("%Y-%m-%d")) reference.date.strftime("%Y-%m-%d"))
def set_billing_reference(fexml, invoice): def set_billing_reference(fexml, invoice):
@@ -419,14 +451,17 @@ class DIANSupportDocumentXML(fe.FeXML):
if percent_for[cod_impuesto]: if percent_for[cod_impuesto]:
line.set_element( line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/'
'cbc:Percent',
percent_for[cod_impuesto]) percent_for[cod_impuesto])
line.set_element( line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', '/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme'
'/cbc:ID',
cod_impuesto) cod_impuesto)
line.set_element( line.set_element(
'/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', 'IVA') '/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme'
'/cbc:Name', 'IVA')
# abstract method # abstract method
@@ -455,7 +490,8 @@ class DIANSupportDocumentXML(fe.FeXML):
if subtotal.percent is not None: if subtotal.percent is not None:
line.set_element( line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac'
':TaxCategory/cbc:Percent',
'%0.2f' % '%0.2f' %
round( round(
subtotal.percent, subtotal.percent,
@@ -464,10 +500,12 @@ class DIANSupportDocumentXML(fe.FeXML):
if subtotal.scheme is not None: if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15 # DIAN 1.7.-2020: FAX15
line.set_element( line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac'
':TaxCategory/cac:TaxScheme/cbc:ID',
subtotal.scheme.code) subtotal.scheme.code)
line.set_element( line.set_element(
'./cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac'
':TaxCategory/cac:TaxScheme/cbc:Name',
subtotal.scheme.name) subtotal.scheme.name)
def set_invoice_line_tax(fexml, line, invoice_line): def set_invoice_line_tax(fexml, line, invoice_line):
@@ -488,7 +526,8 @@ class DIANSupportDocumentXML(fe.FeXML):
if subtotal.percent is not None: if subtotal.percent is not None:
line.set_element( line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc'
':Percent',
'%0.2f' % '%0.2f' %
round( round(
subtotal.percent, subtotal.percent,
@@ -497,10 +536,12 @@ class DIANSupportDocumentXML(fe.FeXML):
if subtotal.scheme is not None: if subtotal.scheme is not None:
# DIAN 1.7.-2020: FAX15 # DIAN 1.7.-2020: FAX15
line.set_element( line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac'
':TaxScheme/cbc:ID',
subtotal.scheme.code) subtotal.scheme.code)
line.set_element( line.set_element(
'./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac'
':TaxScheme/cbc:Name',
subtotal.scheme.name) subtotal.scheme.name)
def set_invoice_lines(fexml, invoice): def set_invoice_lines(fexml, invoice):
@@ -615,7 +656,9 @@ class DIANSupportDocumentXML(fe.FeXML):
(fexml.tag_document()), (fexml.tag_document()),
invoice.invoice_type_code, invoice.invoice_type_code,
listAgencyID='195', listAgencyID='195',
listAgencyName='No matching global declaration available for the validation root', listAgencyName=(
'No matching global declaration available for the '
'validation root'),
listURI='http://www.dian.gov.co') listURI='http://www.dian.gov.co')
fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines))
fexml.set_element( fexml.set_element(

View File

@@ -11,18 +11,88 @@ import hashlib
from .. import fe from .. import fe
from .. import form
from ..data.dian import codelist from ..data.dian import codelist
from .devengado import *
from .deduccion import *
from .trabajador import *
from .empleador import *
from .pago import *
from .lugar import Lugar
from .amount import Amount from .amount import Amount
from .exception import * 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 .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',
]
class Fecha: class Fecha:
def __init__(self, fecha): def __init__(self, fecha):
@@ -46,6 +116,7 @@ class Fecha:
def __str__(self): def __str__(self):
return self.value return self.value
class FechaPago(Fecha): class FechaPago(Fecha):
def apply(self, fragment): def apply(self, fragment):
fragment.set_element('./FechaPago', self.value) fragment.set_element('./FechaPago', self.value)
@@ -84,6 +155,7 @@ class NumeroSecuencia:
# NIE012 # NIE012
Numero=numero) Numero=numero)
@dataclass @dataclass
class Periodo: class Periodo:
fecha_ingreso: str | Fecha fecha_ingreso: str | Fecha
@@ -96,7 +168,8 @@ class Periodo:
def __post_init__(self): def __post_init__(self):
self.fecha_ingreso = Fecha.cast(self.fecha_ingreso) 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_liquidacion_fin = Fecha.cast(self.fecha_liquidacion_fin)
self.fecha_retiro = Fecha.cast(self.fecha_retiro, optional=True) self.fecha_retiro = Fecha.cast(self.fecha_retiro, optional=True)
@@ -107,7 +180,8 @@ class Periodo:
# NIE003 # NIE003
FechaRetiro=self.fecha_retiro, FechaRetiro=self.fecha_retiro,
# NIE004 # NIE004
FechaLiquidacionInicio=self.fecha_liquidacion_inicio, FechaLiquidacionInicio=(
self.fecha_liquidacion_inicio),
# NIE005 # NIE005
FechaLiquidacionFin=self.fecha_liquidacion_fin, FechaLiquidacionFin=self.fecha_liquidacion_fin,
# NIE006 # NIE006
@@ -115,6 +189,7 @@ class Periodo:
# NIE008 # NIE008
FechaGen=self.fecha_generacion) FechaGen=self.fecha_generacion)
@dataclass @dataclass
class Proveedor: class Proveedor:
razon_social: str razon_social: str
@@ -141,11 +216,16 @@ class Proveedor:
cune_xpath = scopexml.xpath_from_root('/InformacionGeneral') cune_xpath = scopexml.xpath_from_root('/InformacionGeneral')
cune = fexml.get_element_attribute(cune_xpath, 'CUNE') cune = fexml.get_element_attribute(cune_xpath, 'CUNE')
ambiente = fexml.get_element_attribute(scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente') ambiente = fexml.get_element_attribute(
codigo_qr = f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={cune}" scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente')
codigo_qr = (
f"https://catalogo-vpfe.dian.gov.co/document/"
f"searchqr?documentkey={cune}")
if InformacionGeneral.AMBIENTE_PRUEBAS == ambiente: if InformacionGeneral.AMBIENTE_PRUEBAS == ambiente:
codigo_qr = f"https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey={cune}" codigo_qr = (
f"https://catalogo-vpfe-hab.dian.gov.co/document/"
f"searchqr?documentkey={cune}")
elif ambiente is None: elif ambiente is None:
raise RuntimeError('fail to get InformacionGeneral/@Ambiente') raise RuntimeError('fail to get InformacionGeneral/@Ambiente')
@@ -153,13 +233,15 @@ class Proveedor:
# NIE020 # NIE020
software_code = self._software_security_code(fexml, scopexml) 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): def _software_security_code(self, fexml, scopexml):
# 8.2 # 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: if numero is None:
raise RuntimeError('fallo obtener NumeroSequenciaXML/@Numero') raise RuntimeError('fallo obtener NumeroSequenciaXML/@Numero')
@@ -173,6 +255,7 @@ class Proveedor:
h.update(code.encode('utf-8')) h.update(code.encode('utf-8'))
return h.hexdigest() return h.hexdigest()
@dataclass @dataclass
class Metadata: class Metadata:
novedad: Novedad novedad: Novedad
@@ -181,18 +264,33 @@ class Metadata:
lugar_generacion: Lugar lugar_generacion: Lugar
proveedor: Proveedor 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: if novedad:
self.novedad.apply(novedad) self.novedad.apply(novedad)
self.secuencia.apply(numero_secuencia_xml) 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) 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) self.proveedor.post_apply(fexml, scopexml, proveedor_xml)
if novedad: if novedad:
self.novedad.post_apply(fexml, scopexml, proveedor_xml) self.novedad.post_apply(fexml, scopexml, proveedor_xml)
@dataclass @dataclass
class PeriodoNomina: class PeriodoNomina:
code: str code: str
@@ -203,6 +301,7 @@ class PeriodoNomina:
raise ValueError("code [%s] not found" % (self.code)) raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.PeriodoNomina[self.code]['name'] self.name = codelist.PeriodoNomina[self.code]['name']
@dataclass @dataclass
class TipoMoneda: class TipoMoneda:
code: str code: str
@@ -213,6 +312,7 @@ class TipoMoneda:
raise ValueError("code [%s] not found" % (self.code)) raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoMoneda[self.code]['name'] self.name = codelist.TipoMoneda[self.code]['name']
@dataclass @dataclass
class InformacionGeneral: class InformacionGeneral:
@dataclass @dataclass
@@ -328,17 +428,28 @@ class InformacionGeneral:
CUNE=cune_hash CUNE=cune_hash
) )
class DianXMLExtensionSigner(fe.DianXMLExtensionSigner): class DianXMLExtensionSigner(fe.DianXMLExtensionSigner):
def __init__(self, pkcs12_path, passphrase=None, localpolicy=True): 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): 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: 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.informacion_general_version = None
self.tag_document = tag_document self.tag_document = tag_document
@@ -346,21 +457,26 @@ class DIANNominaXML:
if namespace_ajuste: if namespace_ajuste:
self.fexml = fe.FeXML(tag_document, namespace_ajuste) self.fexml = fe.FeXML(tag_document, namespace_ajuste)
else: 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", "")
self.fexml.root.set("schemaLocation", schemaLocation) self.fexml.root.set("schemaLocation", schemaLocation)
# layout, la dian requiere que los elementos # layout, la dian requiere que los elementos
# esten ordenados segun el anexo tecnico # 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.fexml.placeholder_for('./TipoNota', optional=True)
self.root_fragment = self.fexml self.root_fragment = self.fexml
if xpath_ajuste is not None: if xpath_ajuste is not None:
self.root_fragment = self.fexml.fragment(xpath_ajuste) self.root_fragment = self.fexml.fragment(xpath_ajuste)
self.root_fragment.placeholder_for('./ReemplazandoPredecesor', optional=True) self.root_fragment.placeholder_for(
self.root_fragment.placeholder_for('./EliminandoPredecesor', optional=True) './ReemplazandoPredecesor', optional=True)
self.root_fragment.placeholder_for(
'./EliminandoPredecesor', optional=True)
if not namespace_ajuste: if not namespace_ajuste:
self.root_fragment.placeholder_for('./Novedad', optional=False) self.root_fragment.placeholder_for('./Novedad', optional=False)
self.root_fragment.placeholder_for('./Periodo') self.root_fragment.placeholder_for('./Periodo')
@@ -374,16 +490,20 @@ class DIANNominaXML:
self.root_fragment.placeholder_for('./Pago') self.root_fragment.placeholder_for('./Pago')
self.root_fragment.placeholder_for('./FechasPagos') self.root_fragment.placeholder_for('./FechasPagos')
self.root_fragment.placeholder_for('./Devengados/Basico') 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: if not namespace_ajuste:
self.novedad = self.root_fragment.fragment('./Novedad') self.novedad = self.root_fragment.fragment('./Novedad')
else: else:
self.novedad = None 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.periodo_xml = self.root_fragment.fragment('./Periodo')
self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos') self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos')
self.numero_secuencia_xml = self.root_fragment.fragment('./NumeroSecuenciaXML') self.numero_secuencia_xml = self.root_fragment.fragment(
self.lugar_generacion_xml = self.root_fragment.fragment('./LugarGeneracionXML') './NumeroSecuenciaXML')
self.lugar_generacion_xml = self.root_fragment.fragment(
'./LugarGeneracionXML')
self.proveedor_xml = self.root_fragment.fragment('./ProveedorXML') self.proveedor_xml = self.root_fragment.fragment('./ProveedorXML')
self.empleador = self.root_fragment.fragment('./Empleador') self.empleador = self.root_fragment.fragment('./Empleador')
self.trabajador = self.root_fragment.fragment('./Trabajador') self.trabajador = self.root_fragment.fragment('./Trabajador')
@@ -399,13 +519,19 @@ class DIANNominaXML:
raise ValueError('se espera tipo Metadata') raise ValueError('se espera tipo Metadata')
self.metadata = 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): def asignar_informacion_general(self, general):
if not isinstance(general, InformacionGeneral): if not isinstance(general, InformacionGeneral):
raise ValueError('se espera tipo InformacionGeneral') raise ValueError('se espera tipo InformacionGeneral')
self.informacion_general = general 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): def asignar_periodo(self, periodo):
if not isinstance(periodo, Periodo): if not isinstance(periodo, Periodo):
@@ -498,10 +624,13 @@ class DIANNominaXML:
def informacion_general(self): def informacion_general(self):
xpath = self.root_fragment.xpath_from_root('/InformacionGeneral') xpath = self.root_fragment.xpath_from_root('/InformacionGeneral')
return { return {
'cune': self.fexml.get_element_attribute(cune_xpath, 'CUNE'), 'cune': self.fexml.get_element_attribute(
'fecha_generacion': self.fexml.get_element_attribute(xpath, 'FechaGen'), xpath, 'CUNE'),
'numero': self.fexml.get_element_attribute(self.root_fragment('/NumeroSecuenciaXML', 'Numero')) 'fecha_generacion': self.fexml.get_element_attribute(
} xpath, 'FechaGen'),
'numero': self.fexml.get_element_attribute(
self.root_fragment(
'/NumeroSecuenciaXML', 'Numero'))}
def toFachoXML(self): def toFachoXML(self):
self._devengados_total() self._devengados_total()
@@ -512,26 +641,38 @@ class DIANNominaXML:
# TODO(bit4bit) acoplamiento temporal # TODO(bit4bit) acoplamiento temporal
# es importante el orden de ejecucion # 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: 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 return self.fexml
def _comprobante_total(self): def _comprobante_total(self):
devengados_total = self.root_fragment.get_element_text_or_attribute('./DevengadosTotal', '0.0') devengados_total = self.root_fragment.get_element_text_or_attribute(
deducciones_total = self.root_fragment.get_element_text_or_attribute('./DeduccionesTotal', '0.0') './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): def _deducciones_total(self):
xpaths = [ xpaths = [
self.root_fragment.xpath_from_root('/Deducciones/Salud/@Deduccion'), self.root_fragment.xpath_from_root(
self.root_fragment.xpath_from_root('/Deducciones/FondoPension/@Deduccion') '/Deducciones/Salud/@Deduccion'),
] self.root_fragment.xpath_from_root(
'/Deducciones/FondoPension/@Deduccion')]
deducciones = map(lambda valor: Amount(valor), deducciones = map(lambda valor: Amount(valor),
self._values_of_xpaths(xpaths)) self._values_of_xpaths(xpaths))
@@ -540,15 +681,19 @@ class DIANNominaXML:
for deduccion in deducciones: for deduccion in deducciones:
deducciones_total += deduccion 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): def _devengados_total(self):
xpaths = [ xpaths = [
self.root_fragment.xpath_from_root('/Devengados/Basico/@SueldoTrabajado'), self.root_fragment.xpath_from_root(
self.root_fragment.xpath_from_root('/Devengados/Transporte/@AuxilioTransporte'), '/Devengados/Basico/@SueldoTrabajado'),
self.root_fragment.xpath_from_root('/Devengados/Transporte/@ViaticoManuAlojS'), self.root_fragment.xpath_from_root(
self.root_fragment.xpath_from_root('/Devengados/Transporte/@ViaticoManuAlojNS') '/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), devengados = map(lambda valor: Amount(valor),
self._values_of_xpaths(xpaths)) self._values_of_xpaths(xpaths))
@@ -558,10 +703,13 @@ class DIANNominaXML:
# TODO(bit4bit) nque valor va redondeado? # TODO(bit4bit) nque valor va redondeado?
# NIE186 # NIE186
self.root_fragment.set_element('./Redondeo', str(round(0, 2))) 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(
'./DevengadosTotal', str(round(devengados_total, 2)))
def _values_of_xpaths(self, xpaths): 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 = [] xpaths_values = []
# toda esta carreta para hacer un aplano de lista # toda esta carreta para hacer un aplano de lista
for xpath_values in xpaths_values_of_values: for xpath_values in xpaths_values_of_values:
@@ -573,15 +721,22 @@ class DIANNominaXML:
return filter(lambda val: val is not None, xpaths_values) return filter(lambda val: val is not None, xpaths_values)
class DIANNominaIndividual(DIANNominaXML): class DIANNominaIndividual(DIANNominaXML):
def __init__(self): def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd" schema = (
"dian:gov:co:facturaelectronica:NominaIndividual"
" NominaIndividualElectronicaXSD.xsd"
)
super().__init__('NominaIndividual', schemaLocation=schema) 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 # TODO(bit4bit) confirmar que no tienen en comun con NominaIndividual
class DIANNominaIndividualDeAjuste(DIANNominaXML): class DIANNominaIndividualDeAjuste(DIANNominaXML):
class Reemplazar(DIANNominaXML): class Reemplazar(DIANNominaXML):
@@ -594,7 +749,8 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
def apply(self, fragment): def apply(self, fragment):
# NIAE214 # NIAE214
fragment.set_element('./TipoNota', '1') fragment.set_element('./TipoNota', '1')
fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None, fragment.set_element(
'./Reemplazar/ReemplazandoPredecesor', None,
# NIAE090 # NIAE090
NumeroPred=self.numero, NumeroPred=self.numero,
# NIAE191 # NIAE191
@@ -604,18 +760,28 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
) )
def __init__(self): 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): def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor): if not isinstance(predecesor, self.Predecesor):
raise ValueError("se espera tipo Predecesor") raise ValueError("se espera tipo Predecesor")
predecesor.apply(self.fexml) predecesor.apply(self.fexml)
class Eliminar(DIANNominaXML): class Eliminar(DIANNominaXML):
@dataclass @dataclass
@@ -636,10 +802,21 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
) )
def __init__(self): def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd" schema = (
super().__init__('NominaIndividualDeAjuste', './Eliminar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste') "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): def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor): if not isinstance(predecesor, self.Predecesor):
@@ -648,4 +825,3 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
def __init__(self): def __init__(self):
super().__init__('NominaIndividualDeAjuste') super().__init__('NominaIndividualDeAjuste')

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ from dataclasses import dataclass
from ..amount import Amount from ..amount import Amount
from .deduccion import Deduccion from .deduccion import Deduccion
@dataclass @dataclass
class DeduccionFondoPension(Deduccion): class DeduccionFondoPension(Deduccion):
porcentaje: Amount porcentaje: Amount

View File

@@ -3,6 +3,7 @@ from dataclasses import dataclass
from ..amount import Amount from ..amount import Amount
from .deduccion import Deduccion from .deduccion import Deduccion
@dataclass @dataclass
class DeduccionSalud(Deduccion): class DeduccionSalud(Deduccion):
porcentaje: Amount porcentaje: Amount
@@ -16,4 +17,3 @@ class DeduccionSalud(Deduccion):
# NIE163 # NIE163
Deduccion=self.deduccion Deduccion=self.deduccion
) )

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ class DevengadoHorasExtrasDiarias(Devengado):
for hora_extra in self.horas_extras: for hora_extra in self.horas_extras:
hora_extra.apply('./HED', hora_extra_xml) hora_extra.apply('./HED', hora_extra_xml)
@dataclass @dataclass
class DevengadoHorasExtrasNocturnas(Devengado): class DevengadoHorasExtrasNocturnas(Devengado):
horas_extras: list[DevengadoHoraExtra] horas_extras: list[DevengadoHoraExtra]
@@ -56,6 +57,7 @@ class DevengadoHorasRecargoNocturno(Devengado):
for hora_extra in self.horas_extras: for hora_extra in self.horas_extras:
hora_extra.apply('./HRN', hora_extra_xml) hora_extra.apply('./HRN', hora_extra_xml)
@dataclass @dataclass
class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado): class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra] horas_extras: list[DevengadoHoraExtra]
@@ -65,6 +67,7 @@ class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
for hora_extra in self.horas_extras: for hora_extra in self.horas_extras:
hora_extra.apply('./HEDDF', hora_extra_xml) hora_extra.apply('./HEDDF', hora_extra_xml)
@dataclass @dataclass
class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado): class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra] horas_extras: list[DevengadoHoraExtra]
@@ -74,6 +77,7 @@ class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
for hora_extra in self.horas_extras: for hora_extra in self.horas_extras:
hora_extra.apply('./HRDDF', hora_extra_xml) hora_extra.apply('./HRDDF', hora_extra_xml)
@dataclass @dataclass
class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado): class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra] horas_extras: list[DevengadoHoraExtra]
@@ -83,6 +87,7 @@ class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
for hora_extra in self.horas_extras: for hora_extra in self.horas_extras:
hora_extra.apply('./HENDF', hora_extra_xml) hora_extra.apply('./HENDF', hora_extra_xml)
@dataclass @dataclass
class DevengadoHorasRecargoNocturnoDominicalesYFestivos(Devengado): class DevengadoHorasRecargoNocturnoDominicalesYFestivos(Devengado):
horas_extras: list[DevengadoHoraExtra] horas_extras: list[DevengadoHoraExtra]

View File

@@ -3,6 +3,7 @@ from dataclasses import dataclass
from ..amount import Amount from ..amount import Amount
from .devengado import Devengado from .devengado import Devengado
@dataclass @dataclass
class DevengadoTransporte(Devengado): class DevengadoTransporte(Devengado):
auxilio_transporte: Amount = None auxilio_transporte: Amount = None
@@ -17,5 +18,6 @@ class DevengadoTransporte(Devengado):
# NIE072 # NIE072
ViaticoManuAlojS=self.viatico_manutencion, ViaticoManuAlojS=self.viatico_manutencion,
# NIE073 # NIE073
ViaticoManuAlojNS = self.viatico_manutencion_no_salarial ViaticoManuAlojNS=(
self.viatico_manutencion_no_salarial)
) )

View File

@@ -4,6 +4,7 @@ from ..pais import Pais
from ..departamento import Departamento from ..departamento import Departamento
from ..municipio import Municipio from ..municipio import Municipio
@dataclass @dataclass
class Empleador: class Empleador:
razon_social: str razon_social: str
@@ -31,5 +32,3 @@ class Empleador:
RazonSocial=self.razon_social RazonSocial=self.razon_social
) )

View File

@@ -3,6 +3,7 @@ import datetime
from facho import fe from facho import fe
class Habilitacion: class Habilitacion:
@dataclass @dataclass
@@ -16,18 +17,18 @@ class Habilitacion:
self.metadata = metadata self.metadata = metadata
def generar(self, zipname, fecha): def generar(self, zipname, fecha):
nominas = [] fe.DianZIP(open(zipname, 'w'))
dianzip = fe.DianZIP(open(zipname, 'w'))
fechabase = datetime.datetime.now() fechabase = datetime.datetime.now()
consecutivo = 0 consecutivo = 0
for _ in range(1, 11): for _ in range(1, 11):
consecutivo += 1 consecutivo += 1
fechabase += datetime.timedelta(days=1) fechabase += datetime.timedelta(days=1)
nomina = self._crear_nomina_individual() self._crear_nomina_individual()
# pag 96 # pag 96
nombre = "nie%010d%s%08x.xml" % (int(self.nit), fecha.strftime('%s'), consecutivo) "nie%010d%s%08x.xml" % (
int(self.nit), fecha.strftime('%s'), consecutivo)
def _crear_nomina_individual_reemplazar(self, nomina, fechabase): def _crear_nomina_individual_reemplazar(self, nomina, fechabase):
metadata = self.metadata metadata = self.metadata
@@ -36,10 +37,15 @@ class Habilitacion:
nomina_ajuste = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() nomina_ajuste = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
self._poblar_nomina(nomina_ajuste, metadata, fecha, prefijo='R') self._poblar_nomina(nomina_ajuste, metadata, fecha, prefijo='R')
informacion_general = nomina.informacion_general() nomina.informacion_general()
def _poblar_nomina(
def _poblar_nomina(self, nomina, metadata, fecha, prefijo='N', consecutivo='0001'): self,
nomina,
metadata,
fecha,
prefijo='N',
consecutivo='0001'):
nomina.asignar_fecha_pago(fecha) nomina.asignar_fecha_pago(fecha)
nomina.asignar_metadata(fe.nomina.Metadata( nomina.asignar_metadata(fe.nomina.Metadata(
@@ -152,4 +158,3 @@ class Habilitacion:
nomina = fe.nomina.DIANNominaIndividual() nomina = fe.nomina.DIANNominaIndividual()
self._poblar_nomina(nomina, metadata, fecha) self._poblar_nomina(nomina, metadata, fecha)

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ from dataclasses import dataclass
from .forma_pago import FormaPago from .forma_pago import FormaPago
from .metodo_pago import MetodoPago from .metodo_pago import MetodoPago
@dataclass @dataclass
class Pago: class Pago:
forma: FormaPago forma: FormaPago

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,11 @@ from dataclasses import dataclass, field
from ..amount import Amount from ..amount import Amount
from .tipo_contrato import * from .tipo_contrato import TipoContrato
from .tipo_documento import * from .tipo_documento import TipoDocumento
from .lugar_trabajo import * from .lugar_trabajo import LugarTrabajo
from .tipo_trabajador import * from .tipo_trabajador import TipoTrabajador
from .sub_tipo_trabajador import * from .sub_tipo_trabajador import SubTipoTrabajador
@dataclass @dataclass
@@ -29,10 +28,13 @@ class Trabajador:
codigo_trabajador: str = None codigo_trabajador: str = None
otros_nombres: str = None otros_nombres: str = None
sub_tipo: SubTipoTrabajador = field(default_factory=lambda: SubTipoTrabajador(code='00')) sub_tipo: SubTipoTrabajador = field(
default_factory=lambda: SubTipoTrabajador(code="00")
)
def apply(self, fragment): def apply(self, fragment):
fragment.set_attributes('./Trabajador', fragment.set_attributes(
"./Trabajador",
# NIE041 # NIE041
TipoTrabajador=self.tipo.code, TipoTrabajador=self.tipo.code,
# NIE042 # NIE042
@@ -53,13 +55,12 @@ class Trabajador:
OtrosNombres=self.otros_nombres, OtrosNombres=self.otros_nombres,
# NIE050 # NIE050
LugarTrabajoPais=self.lugar_trabajo.pais.code, LugarTrabajoPais=self.lugar_trabajo.pais.code,
# NIE051 # NIE051
LugarTrabajoDepartamentoEstado = self.lugar_trabajo.departamento.code, LugarTrabajoDepartamentoEstado=(
self.lugar_trabajo.departamento.code
),
# NIE052 # NIE052
LugarTrabajoMunicipioCiudad=self.lugar_trabajo.municipio.code, LugarTrabajoMunicipioCiudad=self.lugar_trabajo.municipio.code,
# NIE053 # NIE053
LugarTrabajoDireccion=self.lugar_trabajo.direccion, LugarTrabajoDireccion=self.lugar_trabajo.direccion,
# NIE056 # NIE056
@@ -69,5 +70,5 @@ class Trabajador:
# NIE062 # NIE062
Sueldo=str(self.sueldo), Sueldo=str(self.sueldo),
# NIE063 # NIE063
CodigoTrabajador = self.codigo_trabajador CodigoTrabajador=self.codigo_trabajador,
) )

View File

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

View File

@@ -2,12 +2,13 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist from facho.fe.data.dian import codelist
@dataclass @dataclass
class SubTipoTrabajador: class SubTipoTrabajador:
code: str code: str
name: str = '' name: str = ""
def __post_init__(self): def __post_init__(self):
if self.code not in codelist.SubTipoTrabajador: if self.code not in codelist.SubTipoTrabajador:
raise ValueError("code [%s] not found" % (self.code)) 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,14 +2,13 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist from facho.fe.data.dian import codelist
@dataclass @dataclass
class TipoContrato: class TipoContrato:
code: str code: str
name: str = '' name: str = ""
def __post_init__(self): def __post_init__(self):
if self.code not in codelist.TipoContrato: if self.code not in codelist.TipoContrato:
raise ValueError("code [%s] not found" % (self.code)) 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,12 +2,13 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist from facho.fe.data.dian import codelist
@dataclass @dataclass
class TipoDocumento: class TipoDocumento:
code: str code: str
name: str = '' name: str = ""
def __post_init__(self): def __post_init__(self):
if self.code not in codelist.TipoIdFiscal: if self.code not in codelist.TipoIdFiscal:
raise ValueError("code [%s] not found" % (self.code)) 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,12 +2,13 @@ from dataclasses import dataclass
from facho.fe.data.dian import codelist from facho.fe.data.dian import codelist
@dataclass @dataclass
class TipoTrabajador: class TipoTrabajador:
code: str code: str
name: str = '' name: str = ""
def __post_init__(self): def __post_init__(self):
if self.code not in codelist.TipoTrabajador: if self.code not in codelist.TipoTrabajador:
raise ValueError("code [%s] not found" % (self.code)) raise ValueError("code [%s] not found" % (self.code))
self.name = codelist.TipoTrabajador[self.code]['name'] self.name = codelist.TipoTrabajador[self.code]["name"]