From 20d8f4284f49959f41bb930578c758673aa2fc89 Mon Sep 17 00:00:00 2001 From: cosmos Date: Sun, 16 Mar 2025 17:02:56 -0500 Subject: [PATCH] Fix: Facho para habilitacion de nomina python 3.9 --- CONTRIBUTING.rst | 8 - Dockerfile | 35 +- Makefile.dev | 5 +- examples/generate-invoice-from-cli.py | 77 +- examples/use-as-lib.py | 130 +-- experimental/facho-signer/src/xmlusigned.xml | 2 +- facho/cli.py | 6 +- facho/facho.py | 94 +- facho/fe/__init__.py | 1 - facho/fe/client/wsse/signature.py | 5 +- .../codelist/TarifaImpuestoReteIVA-2.1.gc | 111 +- .../codelist/TarifaImpuestoReteRenta-2.1.gc | 479 -------- .../data/dian/codelist/TipoDocumento-2.1.gc | 172 ++- .../fe/data/dian/codelist/TipoImpuesto-2.1.gc | 331 +++--- .../dian/codelist/TipoOperacionNCDS-2.1.gc | 47 - .../TipoResponsabilidad-2.1.custom.gc | 912 +++++++++++++++ .../dian/codelist/TipoResponsabilidad-2.1.gc | 8 - facho/fe/data/dian/codelist/__init__.py | 5 - facho/fe/fe.py | 210 +--- facho/fe/form/__init__.py | 291 ++--- facho/fe/form_xml/__init__.py | 3 - facho/fe/form_xml/application_response.py | 177 --- facho/fe/form_xml/attached_document.py | 223 +--- facho/fe/form_xml/credit_note.py | 5 +- facho/fe/form_xml/debit_note.py | 32 +- facho/fe/form_xml/invoice.py | 918 ++++++--------- facho/fe/form_xml/support_document.py | 647 ----------- .../form_xml/support_document_credit_note.py | 25 - facho/fe/form_xml/utils.py | 20 +- facho/fe/nomina/__init__.py | 95 +- facho/fe/nomina/trabajador/__init__.py | 4 +- requirements_dev.txt | 17 - setup.cfg | 2 +- setup.py | 51 +- tests/cude.txt | 1 - tests/cufe.txt | 0 tests/fixtures.py | 68 +- tests/test_application_response.py | 21 - tests/test_attached_document.py | 120 +- tests/test_data.py | 15 +- tests/test_fe.py | 20 +- tests/test_fe_form.py | 232 ++-- tests/test_form.py | 224 ++-- tests/test_form_xml.py | 143 +-- tests/test_nomina.py | 1026 ++++++++++------- tests/test_nomina_ajuste.py | 236 ---- tests/test_query.py | 14 +- tox.ini | 10 +- 48 files changed, 2771 insertions(+), 4507 deletions(-) delete mode 100644 facho/fe/data/dian/codelist/TarifaImpuestoReteRenta-2.1.gc delete mode 100644 facho/fe/data/dian/codelist/TipoOperacionNCDS-2.1.gc delete mode 100644 facho/fe/form_xml/application_response.py delete mode 100644 facho/fe/form_xml/support_document.py delete mode 100644 facho/fe/form_xml/support_document_credit_note.py delete mode 100644 requirements_dev.txt delete mode 100644 tests/cude.txt delete mode 100644 tests/cufe.txt delete mode 100644 tests/test_application_response.py delete mode 100644 tests/test_nomina_ajuste.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 4200623..2190357 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -94,14 +94,6 @@ Ready to contribute? Here's how to set up `facho` for local development. 7. Submit a pull request through the GitHub website. -Using docker ------------- - -1. make -f Makefile.dev build -2. make -f Makefile.dev dev-shell -3. make -f Makefile.dev python3.8 setup.py develop -4. make -f Makefile.dev python3.8 setup.py test - Pull Request Guidelines ----------------------- diff --git a/Dockerfile b/Dockerfile index 7894872..ee5d265 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,17 @@ # DERIVADO DE https://alextereshenkov.github.io/run-python-tests-with-tox-in-docker.html -FROM ubuntu:24.04 +FROM ubuntu:18.04 RUN apt-get -qq update - -RUN apt install software-properties-common -y \ - && add-apt-repository ppa:deadsnakes/ppa - RUN apt-get install -y --no-install-recommends \ - python3.9 python3.9-distutils python3.9-dev \ - python3.10 python3.10-distutils python3.10-dev \ - python3.11 python3.11-distutils python3.11-dev \ - python3.12 python3-setuptools python3.12-dev \ + python3.7 python3.7-distutils python3.7-dev \ + python3.8 python3.8-distutils python3.8-dev \ wget \ ca-certificates RUN wget https://bootstrap.pypa.io/get-pip.py \ - && python3.9 get-pip.py pip==23.2.1 --break-system-packages \ - && python3.10 get-pip.py pip==23.2.1 --break-system-packages \ - && python3.11 get-pip.py pip==23.2.1 --break-system-packages \ - && python3.12 get-pip.py pip==23.2.1 --break-system-packages \ + && python3 get-pip.py pip==21.3 \ + && python3.7 get-pip.py pip==21.3 \ + && python3.8 get-pip.py pip==21.3 \ && rm get-pip.py RUN apt-get install -y --no-install-recommends \ @@ -27,14 +20,12 @@ RUN apt-get install -y --no-install-recommends \ build-essential \ zip -RUN python3.9 --version -RUN python3.10 --version -RUN python3.11 --version -RUN python3.12 --version +RUN python3.6 --version +RUN python3.7 --version +RUN python3.8 --version -RUN pip3.9 install setuptools setuptools-rust -RUN pip3.10 install setuptools setuptools-rust -RUN pip3.11 install setuptools setuptools-rust --break-system-packages -RUN pip3.12 install setuptools setuptools-rust --break-system-packages +RUN pip3.6 install setuptools setuptools-rust +RUN pip3.7 install setuptools setuptools-rust +RUN pip3.8 install setuptools setuptools-rust -RUN pip3 install tox pytest --break-system-packages +RUN pip3 install tox pytest diff --git a/Makefile.dev b/Makefile.dev index ae90ab9..f2aaeaf 100644 --- a/Makefile.dev +++ b/Makefile.dev @@ -11,11 +11,14 @@ dev-setup: docker build -t facho . +py-develop: + docker run -t -v $(PWD):/app -w /app facho sh -c 'python3.7 setup.py develop --user' + dev-shell: docker run --rm -ti -v "$(PWD):/app" -w /app --name facho-cli facho bash test: - docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.12 setup.py test' + docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.7 setup.py test' tox: docker run -it -v $(PWD)/:/app -w /app facho tox diff --git a/examples/generate-invoice-from-cli.py b/examples/generate-invoice-from-cli.py index a2d6351..edb6cdd 100644 --- a/examples/generate-invoice-from-cli.py +++ b/examples/generate-invoice-from-cli.py @@ -14,7 +14,7 @@ from facho.fe import fe from datetime import datetime, date # Datos del fomulario del SET de pruebas -INVOICE_AUTHORIZATION = '181360000001' # Número suministrado por la Dian en el momento de la creación del SET de Pruebas +INVOICE_AUTHORIZATION = '181360000001' #Número suministrado por la Dian en el momento de la creación del SET de Pruebas ID_SOFTWARE = '57bcb6d1-c591-5a90-b80a-cb030ec91440' #Id suministrado por la Dian en el momento de la creación del SET de Pruebas PIN = '19642' #Número creado por la empresa para poder crear el SET de pruebas CLAVE_TECNICA = 'fc9eac422eba16e21ffd8c5f94b3f30a6e38162d' ##Id suministrado por la Dian en el momento de la creación del SET de Pruebas @@ -36,7 +36,6 @@ def extensions(inv): 'SETP', 990000000, 995000000)#del SET de pruebas return [security_code, authorization_provider, cufe, software_provider, inv_authorization] - def invoice(): # factura de venta nacional inv = form.Invoice('01') @@ -50,17 +49,16 @@ def invoice(): inv.set_operation_type('10') inv.set_supplier(form.Party( legal_name = 'Nombre registrado de la empresa', - name='Nombre comercial o él mismo nombre registrado', - ident=form.PartyIdentification( - 'nit_empresa', 'digito_verificación', '31'), + name = 'Nombre comercial o él mismo nombre registrado', + ident = form.PartyIdentification('nit_empresa', 'digito_verificación', '31'), # obligaciones del contribuyente ver DIAN:FAK26 - responsability_code=form.Responsability(['ZZ', 'O-14', 'O-48']), + responsability_code = form.Responsability(['O-07', 'O-14', 'O-48']), # ver DIAN:FAJ28 - responsability_regime_code='48', + responsability_regime_code = '48', # tipo de organizacion juridica ver DIAN:6.2.3 - organization_code='1', - email="correoempresa@correoempresa.correo", - address=form.Address( + organization_code = '1', + email = "correoempresa@correoempresa.correo", + address = form.Address( '', '', form.City('05001', 'Medellín'), form.Country('CO', 'Colombia'), form.CountrySubentity('05', 'Antioquia')), @@ -78,43 +76,42 @@ def invoice(): '', '', form.City('05001', 'Medellín'), form.Country('CO', 'Colombia'), form.CountrySubentity('05', 'Antioquia')), - # tax_scheme = form.TaxScheme('01', 'IVA') + #tax_scheme = form.TaxScheme('01', 'IVA') )) # asignar metodo de pago inv.set_payment_mean(form.PaymentMean( # metodo de pago ver DIAN:3.4.1 - id='1', - # codigocorrespondientealmediodepagoverDIAN:3.4.2 - code='20', - # fechadevencimientodelafactura - due_at=datetime.now(), - # identificadornumerico - payment_id='2' + id = '1', + # codigo correspondiente al medio de pago ver DIAN:3.4.2 + code = '20', + # fecha de vencimiento de la factura + due_at = datetime.now(), + # identificador numerico + payment_id = '2' )) # adicionar una linea al documento - inv.add_invoice_line( - form.InvoiceLine( - quantity=form.Quantity(int(20.5), '94'), - # item general de codigo 999 - description='productO3', - sitem=form.StandardItem('test', 9999), - price=form.Price( - # precio base del item (sin iva) - amount=form.Amount(200.00), - # ver DIAN:6.3.5.1 - type_code='01', - type='x' - ), - tax=form.TaxTotal( - subtotals=[ - form.TaxSubTotal( - percent=19.00, - scheme=form.TaxScheme('01') - )] - ) - )) + inv.add_invoice_line(form.InvoiceLine( + quantity = form.Quantity(int(20.5), '94'), + # item general de codigo 999 + description = 'productO3', + item = form.StandardItem('test', 9999), + price = form.Price( + # precio base del item (sin iva) + amount = form.Amount(200.00), + # ver DIAN:6.3.5.1 + type_code = '01', + type = 'x' + ), + tax = form.TaxTotal( + subtotals = [ + form.TaxSubTotal( + percent = 19.00, + scheme=form.TaxScheme('01') + ) + ] + ) + )) return inv - def document_xml(): return form_xml.DIANInvoiceXML diff --git a/examples/use-as-lib.py b/examples/use-as-lib.py index 0215f62..96dbbaa 100644 --- a/examples/use-as-lib.py +++ b/examples/use-as-lib.py @@ -1,127 +1,109 @@ # importar libreria de modelos -from facho import fe, form_xml import facho.fe.form as form -import datetime - -PRIVATE_KEY_PATH = 'ruta a mi llave privada' -PRIVATE_PASSPHRASE = 'clave de la llave privada' +import facho.fe.form_xml +PRIVATE_KEY_PATH='ruta a mi llave privada' +PRIVATE_PASSPHRASE='clave de la llave privada' # consultar las extensiones necesarias def extensions(inv): - security_code = fe.DianXMLExtensionSoftwareSecurityCode( - 'id software', 'pin', inv.invoice_ident) + security_code = fe.DianXMLExtensionSoftwareSecurityCode('id software', 'pin', inv.invoice_ident) authorization_provider = fe.DianXMLExtensionAuthorizationProvider() - cufe = fe.DianXMLExtensionCUFE( - inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS, - 'clave tecnica') + cufe = fe.DianXMLExtensionCUFE(inv, fe.DianXMLExtensionCUFE.AMBIENTE_PRUEBAS, + 'clave tecnica') nit = form.PartyIdentification('nit', '5', '31') - software_provider = fe.DianXMLExtensionSoftwareProvider( - nit, nit.dv, 'id software') - inv_authorization = fe.DianXMLExtensionInvoiceAuthorization( - 'invoice autorization', - datetime(2019, 1, 19), - datetime(2030, 1, 19), - 'SETP', 990000001, 995000000) - return [ - security_code, - authorization_provider, - cufe, software_provider, - inv_authorization - ] - + software_provider = fe.DianXMLExtensionSoftwareProvider(nit, nit.dv, 'id software') + inv_authorization = fe.DianXMLExtensionInvoiceAuthorization('invoice autorization', + datetime(2019, 1, 19), + datetime(2030, 1, 19), + 'SETP', 990000001, 995000000) + return [security_code, authorization_provider, cufe, software_provider, inv_authorization] # generar documento desde modelo a ruta indicada def generate_document(invoice, filepath): xml = form_xml.DIANInvoiceXML(invoice) for extension in extensions(invoice): xml.add_extension(extension) - form_xml.utils.DIANWriteSigned( - xml, filepath, PRIVATE_KEY_PATH, PRIVATE_PASSPHRASE, True) - + form_xml.utils.DIANWriteSigned(xml, filepath, PRIVATE_KEY_PATH, PRIVATE_PASSPHRASE, True) # Modelars las facturas # ... # factura de venta nacional inv = form.NationalSalesInvoice() - # asignar periodo de facturacion inv.set_period(datetime.now(), datetime.now()) - # asignar fecha de emision de la factura - inv.set_issue(datetime.now()) # asignar prefijo y numero del documento - inv.set_ident('SETP990003033') # asignar tipo de operacion ver DIAN:6.1.5 inv.set_operation_type('10') - # asignar proveedor inv.set_supplier(form.Party( - legal_name='FACHOSOS', - name='FACHOSOS', - ident=form.PartyIdentification('900579212', '5', '31'), + legal_name = 'FACHO SOS', + name = 'FACHO SOS', + ident = form.PartyIdentification('900579212', '5', '31'), # obligaciones del contribuyente ver DIAN:FAK26 - responsability_code=form.Responsability(['ZZ', 'O-09', 'O-14', 'O-48']), + responsability_code = form.Responsability(['O-07', 'O-09', 'O-14', 'O-48']), # ver DIAN:FAJ28 - responsability_regime_code='48', + responsability_regime_code = '48', # tipo de organizacion juridica ver DIAN:6.2.3 - organization_code='1', - email="sdds@sd.com", - address=form.Address( - name='', - street='', - city=form.City('05001', 'Medellín'), - country=form.Country('CO', 'Colombia'), - countrysubentity=form.CountrySubentity('05', 'Antioquia')) + organization_code = '1', + email = "sdds@sd.com", + address = form.Address( + name = '', + street = '', + city = form.City('05001', 'Medellín'), + country = form.Country('CO', 'Colombia'), + countrysubentity = form.CountrySubentity('05', 'Antioquia')) )) - inv.set_customer(form.Party( - legal_name='facho-customer', - name='facho-customer', - ident=form.PartyIdentification('999999999', '', '13'), - responsability_code=form.Responsability(['R-99-PN']), - responsability_regime_code='49', - organization_code='2', - email="sdds@sd.com", - address=form.Address( - name='', - street='', - city=form.City('05001', 'Medellín'), - country=form.Country('CO', 'Colombia'), - countrysubentity=form.CountrySubentity('05', 'Antioquia')) + legal_name = 'facho-customer', + name = 'facho-customer', + ident = form.PartyIdentification('999999999', '', '13'), + responsability_code = form.Responsability(['R-99-PN']), + responsability_regime_code = '49', + organization_code = '2', + email = "sdds@sd.com", + address = form.Address( + name = '', + street = '', + city = form.City('05001', 'Medellín'), + country = form.Country('CO', 'Colombia'), + countrysubentity = form.CountrySubentity('05', 'Antioquia')) )) # asignar metodo de pago inv.set_payment_mean(form.PaymentMean( # metodo de pago ver DIAN:3.4.1 - id='1', + id = '1', # codigo correspondiente al medio de pago ver DIAN:3.4.2 - code='10', + code = '10', # fecha de vencimiento de la factura - due_at=datetime.now(), + due_at = datetime.now(), + # identificador numerico - payment_id='1' + payment_id = '1' )) # adicionar una linea al documento inv.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='producto facho', + quantity = form.Quantity(1, '94'), + description = 'producto facho', # item general de codigo 999 - item=form.StandardItem('test', 9999), - price=form.Price( + item = form.StandardItem('test', 9999), + price = form.Price( # precio base del tiem - amount=form.Amount(100.00), + amount = form.Amount(100.00), # ver DIAN:6.3.5.1 - type_code='01', - type='x' + type_code = '01', + type = 'x' ), - tax=form.TaxTotal( - subtotals=[ + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - percent=19.00, - )] + percent = 19.00, + ) + ] ) )) diff --git a/experimental/facho-signer/src/xmlusigned.xml b/experimental/facho-signer/src/xmlusigned.xml index 35e11a5..23ead39 100644 --- a/experimental/facho-signer/src/xmlusigned.xml +++ b/experimental/facho-signer/src/xmlusigned.xml @@ -76,7 +76,7 @@ NEUROTEC TECNOLOGIA S.A.S 900579212 - ZZ;O-09;O-14;O-48 + O-07;O-09;O-14;O-48 diff --git a/facho/cli.py b/facho/cli.py index 9ef1fec..8db2710 100644 --- a/facho/cli.py +++ b/facho/cli.py @@ -259,14 +259,14 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr spec.loader.exec_module(module) import facho.fe.form as form - from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned, DIANWrite, DIANSupportDocumentXML + from facho.fe.form_xml import DIANInvoiceXML, DIANWriteSigned,DIANWrite from facho import fe try: invoice_xml = module.document_xml() except AttributeError: - #invoice_xml = DIANInvoiceXML - invoice_xml = DIANSupportDocumentXML + invoice_xml = DIANInvoiceXML + print("Using document xml:", invoice_xml) invoice = module.invoice() invoice.calculate() diff --git a/facho/facho.py b/facho/facho.py index 0970c2c..0a1a243 100644 --- a/facho/facho.py +++ b/facho/facho.py @@ -1,11 +1,12 @@ # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. + from lxml import etree -from lxml.etree import Element, tostring +from lxml.etree import Element, SubElement, tostring import re from collections import defaultdict from copy import deepcopy - +from pprint import pprint class FachoValueInvalid(Exception): def __init__(self, xpath): @@ -31,10 +32,7 @@ class LXMLBuilder: def __init__(self, nsmap): self.nsmap = nsmap - self._re_node_expr = \ - re.compile( - r'^(?P((?P\w+):)?(?P[a-zA-Z0-9_-]+))' - r'(?P\[.+\])?') + self._re_node_expr = re.compile(r'^(?P((?P\w+):)?(?P[a-zA-Z0-9_-]+))(?P\[.+\])?') self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?') def match_expression(self, node_expr): @@ -123,7 +121,7 @@ class LXMLBuilder: elem.attrib[key] = value @classmethod - def remove_attributes(cls, elem, keys, exclude=[]): + def remove_attributes(cls, elem, keys, exclude = []): for key in keys: if key in exclude: continue @@ -145,8 +143,7 @@ class LXMLBuilder: self.remove_attributes(el, keys, exclude=['facho_optional']) is_optional = el.get('facho_optional', 'False') == 'True' - if is_optional and el.getchildren() == [] and el.keys() == [ - 'facho_optional']: + if is_optional and el.getchildren() == [] and el.keys() == ['facho_optional']: el.getparent().remove(el) return tostring(elem, **attrs).decode('utf-8') @@ -156,15 +153,14 @@ class FachoXML: """ Decora XML con funciones de consulta XPATH de un solo elemento """ - def __init__(self, root, builder=None, nsmap=None, fragment_prefix='', - fragment_root_element=None): + def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',fragment_root_element=None): if builder is None: self.builder = LXMLBuilder(nsmap) else: self.builder = builder self.nsmap = nsmap - + if isinstance(root, str): self.root = self.builder.build_element_from_string(root, nsmap) else: @@ -181,22 +177,16 @@ class FachoXML: xml = LXMLBuilder.from_string(document) return FachoXML(xml, nsmap=namespaces) - def root_namespace(self): - return etree.QName(self.root).namespace - - def root_localname(self): - return etree.QName(self.root).localname - def append_element(self, elem, new_elem): - # elem = self.find_or_create_element(xpath, append=append) - # self.builder.append(elem, new_elem) + #elem = self.find_or_create_element(xpath, append=append) + #self.builder.append(elem, new_elem) self.builder.append(elem, new_elem) def add_extension(self, extension): extension.build(self) - def fragment( - self, xpath, append=False, append_not_exists=False): + + def fragment(self, xpath, append=False, append_not_exists=False): nodes = xpath.split('/') nodes.pop() root_prefix = '/'.join(nodes) @@ -206,9 +196,7 @@ class FachoXML: if parent is None: parent = self.find_or_create_element(xpath, append=append) - return FachoXML( - parent, nsmap=self.nsmap, fragment_prefix=root_prefix, - fragment_root_element=self.root) + return FachoXML(parent, nsmap=self.nsmap, fragment_prefix=root_prefix, fragment_root_element=self.root) def register_alias_xpath(self, alias, xpath): self.xpath_for[alias] = xpath @@ -244,8 +232,7 @@ class FachoXML: """ xpath = self._path_xpath_for(xpath) node_paths = xpath.split('/') - # remove empty / - node_paths.pop(0) + node_paths.pop(0) #remove empty / root_tag = node_paths.pop(0) root_node = self.builder.build_from_expression(root_tag) @@ -253,10 +240,10 @@ class FachoXML: # restaurar ya que no es la raiz y asignar actual como raiz node_paths.insert(0, root_tag) root_node = self.root - + if not self.builder.same_tag(root_node.tag, self.root.tag): - raise ValueError('xpath %s must be absolute to /%s' % ( - xpath, self.root.tag)) + + raise ValueError('xpath %s must be absolute to /%s' % (xpath, self.root.tag)) # crea jerarquia segun xpath indicado parent = None @@ -266,8 +253,8 @@ class FachoXML: for node_path in node_paths: node_expr = self.builder.match_expression(node_path) node = self.builder.build_from_expression(node_path) - child = self.builder.find_relative( - current_elem, node_expr['path'], self.nsmap) + + child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap) parent = current_elem if child is not None: @@ -278,12 +265,11 @@ class FachoXML: node_expr = self.builder.match_expression(node_tag) node = self.builder.build_from_expression(node_tag) - child = self.builder.find_relative( - current_elem, node_expr['path'], self.nsmap) + child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap) parent = current_elem if child is not None: current_elem = child - + if parent == current_elem: self.builder.append(parent, node) return node @@ -300,10 +286,9 @@ class FachoXML: self.builder.append(parent, node) return node - if self.builder.is_attribute( - last_slibing, 'facho_placeholder', 'True'): + if self.builder.is_attribute(last_slibing, 'facho_placeholder', 'True'): self._remove_facho_attributes(last_slibing) - return last_slibing + return last_slibing self.builder.append_next(last_slibing, node) return node @@ -314,8 +299,7 @@ class FachoXML: self._remove_facho_attributes(current_elem) return current_elem - def set_element_validator( - self, xpath, validator=False): + def set_element_validator(self, xpath, validator = False): """ validador al asignar contenido a xpath indicado @@ -328,9 +312,8 @@ class FachoXML: self._validators[key] = lambda v, attrs: True else: self._validators[key] = validator - - def set_element( - self, xpath, content, **attrs): + + def set_element(self, xpath, content, **attrs): """ asigna contenido ubicado por ruta tipo XPATH. @param xpath ruta tipo XPATH @@ -372,8 +355,7 @@ class FachoXML: self.builder.set_attribute(elem, k, str(v)) return self - def get_element_attribute( - self, xpath, attribute, multiple=False): + def get_element_attribute(self, xpath, attribute, multiple=False): elem = self.get_element(xpath, multiple=multiple) if elem is None: @@ -410,16 +392,14 @@ class FachoXML: return None return format_(text) - def get_element_text_or_attribute( - self, xpath, default=None, multiple=False, raise_on_fail=False): + def get_element_text_or_attribute(self, xpath, default=None, multiple=False, raise_on_fail=False): parts = xpath.split('/') - is_attribute = parts[-1].startswith('@') + is_attribute = parts[-1].startswith('@') if is_attribute: attribute_name = parts.pop(-1).lstrip('@') element_path = "/".join(parts) try: - val = self.get_element_attribute( - element_path, attribute_name, multiple=multiple) + val = self.get_element_attribute(element_path, attribute_name, multiple=multiple) if val is None: return default return val @@ -452,8 +432,7 @@ class FachoXML: if isinstance(xpath, tuple): val = xpath[0] else: - val = self.get_element_text_or_attribute( - xpath, raise_on_fail=raise_on_fail) + val = self.get_element_text_or_attribute(xpath, raise_on_fail=raise_on_fail) vals.append(val) return vals @@ -475,8 +454,7 @@ class FachoXML: return True def _remove_facho_attributes(self, elem): - self.builder.remove_attributes( - elem, ['facho_optional', 'facho_placeholder']) + self.builder.remove_attributes(elem, ['facho_optional', 'facho_placeholder']) def tostring(self, **kw): return self.builder.tostring(self.root, **kw) @@ -488,17 +466,15 @@ class FachoXML: root = self.root if self.fragment_root_element is not None: root = self.fragment_root_element - + if isinstance(self.nsmap, dict): nsmap = dict(map(reversed, self.nsmap.items())) ns = nsmap[etree.QName(root).namespace] + ':' if self.fragment_root_element is not None: - new_xpath = '/' + ns + etree.QName(root).localname + '/' + \ - etree.QName(self.root).localname + '/' + xpath.lstrip('/') + new_xpath = '/' + ns + etree.QName(root).localname + '/' + etree.QName(self.root).localname + '/' + xpath.lstrip('/') else: - new_xpath = '/' + ns + etree.QName(root).localname + '/' + \ - xpath.lstrip('/') + new_xpath = '/' + ns + etree.QName(root).localname + '/' + xpath.lstrip('/') return new_xpath def __str__(self): diff --git a/facho/fe/__init__.py b/facho/fe/__init__.py index fdfaedc..11adce3 100644 --- a/facho/fe/__init__.py +++ b/facho/fe/__init__.py @@ -5,7 +5,6 @@ from .fe import DianXMLExtensionSigner from .fe import DianXMLExtensionSoftwareSecurityCode from .fe import DianXMLExtensionCUFE from .fe import DianXMLExtensionCUDE -from .fe import DianXMLExtensionCUDS from .fe import DianXMLExtensionInvoiceAuthorization from .fe import DianXMLExtensionSoftwareProvider from .fe import DianXMLExtensionAuthorizationProvider diff --git a/facho/fe/client/wsse/signature.py b/facho/fe/client/wsse/signature.py index 7295742..cc42a3d 100644 --- a/facho/fe/client/wsse/signature.py +++ b/facho/fe/client/wsse/signature.py @@ -234,10 +234,9 @@ def _append_timestamp(security, expires_dt=None): if expires_dt is None: expires_dt = timedelta(seconds=6000) - timestamp = datetime.now() etimestamp = utils.WSU.Timestamp({'{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id': utils.get_unique_id()}) - etimestamp.append(utils.WSU.Created(get_timestamp(timestamp=timestamp))) - etimestamp.append(utils.WSU.Expires(get_timestamp(timestamp=timestamp, delta=expires_dt))) + etimestamp.append(utils.WSU.Created(get_timestamp())) + etimestamp.append(utils.WSU.Expires(get_timestamp(delta=expires_dt))) security.insert(0, etimestamp) if etree.LXML_VERSION[:2] >= (3, 5): etree.cleanup_namespaces(security, diff --git a/facho/fe/data/dian/codelist/TarifaImpuestoReteIVA-2.1.gc b/facho/fe/data/dian/codelist/TarifaImpuestoReteIVA-2.1.gc index cd62b2d..2720b0e 100644 --- a/facho/fe/data/dian/codelist/TarifaImpuestoReteIVA-2.1.gc +++ b/facho/fe/data/dian/codelist/TarifaImpuestoReteIVA-2.1.gc @@ -1,61 +1,50 @@ - - - - - TarifaImpuestos - Tarifas por Impuesto - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos - urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TarifaImpuestos-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - Codigo Comun - - - - Name - Nombre - - - - Description - Descripcion - - - - CodeKey - - - - - - - 15.00 - - - ReteIVA - - - ReteIVA - - - - - 100.00 - - - ReteIVA - - - ReteIVA - - - - + + + + + TarifaImpuestos + Tarifas por Impuesto + 1 + urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos + urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos-2.1 + http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TarifaImpuestos-2.1.gc + + DIAN (Dirección de Impuestos y Aduanas Nacionales) + 195 + + + + + Code + Codigo Comun + + + + Name + Nombre + + + + Description + Descripcion + + + + CodeKey + + + + + + + 15.00 + + + ReteIVA + + + ReteIVA + + + + diff --git a/facho/fe/data/dian/codelist/TarifaImpuestoReteRenta-2.1.gc b/facho/fe/data/dian/codelist/TarifaImpuestoReteRenta-2.1.gc deleted file mode 100644 index 2ff8d6f..0000000 --- a/facho/fe/data/dian/codelist/TarifaImpuestoReteRenta-2.1.gc +++ /dev/null @@ -1,479 +0,0 @@ - - - - - TarifaImpuestoReteFuente - Tarifas por Impuesto - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos - urn:dian:names:especificacion:ubl:listacodigos:gc:TarifaImpuestos-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TarifaImpuestos-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - Codigo Comun - - - - Name - Nombre - - - - Description - Descripcion - - - - CodeKey - - - - - - - 2.50 - - - ReteFuente - - - Compras generales (declarantes) - - - - - 3.50 - - - ReteFuente - - - Compras generales (no declarantes) - - - - - 1.50 - - - ReteFuente - - - Compras con tarjeta débito o crédito - - - - - 1.50 - - - ReteFuente - - - Compras de bienes o productos agrícolas o pecuarios sin procesamiento industrial - - - - - 2.50 - - - ReteFuente - - - Compras de bienes o productos agrícolas o pecuarios con procesamiento industrial (declarantes) - - - - - 3.50 - - - ReteFuente - - - Compras de bienes o productos agrícolas o pecuarios con procesamiento industrial declarantes (no declarantes) - - - - - 0.50 - - - ReteFuente - - - Compras de café pergamino o cereza - - - - - 0.10 - - - ReteFuente - - - Compras de combustibles derivados del petróleo - - - - - 1.00 - - - ReteFuente - - - Enajenación de activos fijos de personas naturales (notarías y tránsito son agentes retenedores) - - - - - 1.00 - - - ReteFuente - - - Compras de vehículos - - - - - 1.00 - - - ReteFuente - - - Compras de bienes raíces cuya destinación y uso sea vivienda de habitación (por las primeras 20.000 UVT, es decir hasta $637.780.000) - - - - - 2.50 - - - ReteFuente - - - Compras  de bienes raíces cuya destinación y uso sea vivienda de habitación (exceso de las primeras 20.000 UVT, es decir superior a $637.780.000) - - - - - 2.50 - - - ReteFuente - - - Compras  de bienes raíces cuya destinación y uso sea distinto a vivienda de habitación - - - - - 4.00 - - - ReteFuente - - - Servicios generales (declarantes) - - - - - 6.00 - - - ReteFuente - - - Servicios generales (no declarantes) - - - - - 4.00 - - - ReteFuente - - - Por emolumentos eclesiásticos (declarantes) - - - - - 3.50 - - - ReteFuente - - - Por emolumentos eclesiásticos (no declarantes) - - - - - 1.00 - - - ReteFuente - - - Servicios de transporte de carga - - - - - 3.50 - - - ReteFuente - - - Servicios de  transporte nacional de pasajeros por vía terrestre (declarantes) - - - - - 3.50 - - - ReteFuente - - - Servicios de  transporte nacional de pasajeros por vía terrestre (no declarantes) - - - - - 1.00 - - - ReteFuente - - - Servicios de  transporte nacional de pasajeros por vía aérea o marítima - - - - - 1.00 - - - ReteFuente - - - Servicios prestados por empresas de servicios temporales (sobre AIU) - - - - - 2.00 - - - ReteFuente - - - Servicios prestados por empresas de vigilancia y aseo (sobre AIU) - - - - - 2.00 - - - ReteFuente - - - Servicios integrales de salud prestados por IPS - - - - - 3.50 - - - ReteFuente - - - Servicios de hoteles y restaurantes (declarantes) - - - - - 3.50 - - - ReteFuente - - - Servicios de hoteles y restaurantes (no declarantes) - - - - - 4.00 - - - ReteFuente - - - Arrendamiento de bienes muebles - - - - - 3.50 - - - ReteFuente - - - Arrendamiento de bienes inmuebles (declarantes) - - - - - 3.50 - - - ReteFuente - - - Arrendamiento de bienes inmuebles (no declarantes) - - - - - 2.50 - - - ReteFuente - - - Otros ingresos tributarios (declarantes) - - - - - 3.50 - - - ReteFuente - - - Otros ingresos tributarios (no declarantes) - - - - - 11.00 - - - ReteFuente - - - Honorarios y comisiones (personas jurídicas) - - - - - 11.00 - - - ReteFuente - - - Honorarios y comisiones personas naturales que suscriban contrato o cuya sumatoria de los pagos o abonos en cuenta superen las 3.300 UVT ($105.135.000) - - - - - 10.00 - - - ReteFuente - - - Honorarios y comisiones (no declarantes) - - - - - 3.50 - - - ReteFuente - - - Servicios de licenciamiento o derecho de uso de software - - - - - 7.00 - - - ReteFuente - - - Intereses o rendimientos financieros - - - - - 4.00 - - - ReteFuente - - - Rendimientos financieros provenientes de títulos de renta fija - - - - - 20.00 - - - ReteFuente - - - Loterías, rifas, apuestas y similares - - - - - 3.00 - - - ReteFuente - - - Retención en colocación independiente de juegos de suerte y azar - - - - - 2.00 - - - ReteFuente - - - Contratos de construcción  y urbanización - - - - diff --git a/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc b/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc index ee0b9ef..80c7b96 100644 --- a/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc +++ b/facho/fe/data/dian/codelist/TipoDocumento-2.1.gc @@ -1,100 +1,74 @@ - - - - - TipoDocumento - Tipo de Documento - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoDocumento-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - Codigo Comun - - - - Name - Nombre - - - - CodeKey - - - - - - - 01 - - - Factura electrónica de Venta - - - Tipos de factura - - - - - 02 - - - Factura electrónica de venta con propósito de exportación - - - Tipos de factura - - - - - 03 - - - Factura de talonario o papel con numeración de contingencia. - - - Tipos de factura - - - - - 04 - - - Factura electrónica de Venta por Contingencia DIAN - - - Tipos de factura - - - - - 91 - - - Nota Crédito - - - Exclusivo en referencias a documentos (elementos DocumentReference) - - - - - 92 - - - Nota Débito - - - Exclusivo en referencias a documentos (elementos DocumentReference) - - - + + + + + TipoDocumento + Tipo de Documento + 1 + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoDocumento-2.1 + http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoDocumento-2.1.gc + + DIAN (Dirección de Impuestos y Aduanas Nacionales) + 195 + + + + + Code + Codigo Comun + + + + Name + Nombre + + + + CodeKey + + + + + + + 01 + + + Factura de Venta Nacional + + + + + 02 + + + Factura de Exportación + + + + + 03 + + + Factura de Contingencia + + + + + 91 + + + Nota Crédito + + + + + 92 + + + Nota Débito + + + diff --git a/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc b/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc index c70c793..4d1bf8b 100644 --- a/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc +++ b/facho/fe/data/dian/codelist/TipoImpuesto-2.1.gc @@ -1,171 +1,162 @@ - - - - - TipoImpuesto - Tipo de Tributos - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoImpuesto-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - Codigo Comun - - - - Name - Nombre - - - - CodeKey - - - - - - - 01 - - - IVA - - - - - 02 - - - IC - - - - - 03 - - - ICA - - - - - 04 - - - INC - - - - - 05 - - - ReteIVA - - - - - 06 - - - ReteRenta - - - - - 07 - - - ReteICA - - - - - 08 - - - ReteCREE - - - - - 20 - - - FtoHorticultura - - - - - 21 - - - Timbre - - - - - 22 - - - Bolsas - - - - - 23 - - - INCarbono - - - - - 24 - - - INCombustibles - - - - - 25 - - - Sobretasa Combustibles - - - - - 26 - - - Sordicom - - - - - 30 - - - Impuesto al Consumo de Datos - - - - - - ZZ - - - Nombre de la figura tributaria - - - + + + + + TipoImpuesto + Tipo de Tributos + 1 + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto + urn:dian:names:especificacion:ubl:listacodigos:gc:TipoImpuesto-2.1 + http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoImpuesto-2.1.gc + + DIAN (Dirección de Impuestos y Aduanas Nacionales) + 195 + + + + + Code + Codigo Comun + + + + Name + Nombre + + + + CodeKey + + + + + + + 01 + + + IVA + + + + + 02 + + + IC + + + + + 03 + + + ICA + + + + + 04 + + + INC + + + + + 05 + + + ReteIVA + + + + + 06 + + + ReteFuente + + + + + 07 + + + ReteICA + + + + + 08 + + + ReteCREE + + + + + 20 + + + FtoHorticultura + + + + + 21 + + + Timbre + + + + + 22 + + + Bolsas + + + + + 23 + + + INCarbono + + + + + 24 + + + INCombustibles + + + + + 25 + + + Sobretasa Combustibles + + + + + 26 + + + Sordicom + + + + + ZZ + + + Nombre de la figura tributaria + + + diff --git a/facho/fe/data/dian/codelist/TipoOperacionNCDS-2.1.gc b/facho/fe/data/dian/codelist/TipoOperacionNCDS-2.1.gc deleted file mode 100644 index 33002a4..0000000 --- a/facho/fe/data/dian/codelist/TipoOperacionNCDS-2.1.gc +++ /dev/null @@ -1,47 +0,0 @@ - - - - TipoOperacion - Tipo de operacion - 1 - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoOperacion - urn:dian:names:especificacion:ubl:listacodigos:gc:TipoOperacion-2.1 - http://dian.gov.co/ubl/os-ubl-2.0/cl/gc/default/TipoOperacion-2.1.gc - - DIAN (Dirección de Impuestos y Aduanas Nacionales) - 195 - - - - - Code - - - - Nombre - - - - CodeKey - - - - - - - 10 - - - Residente - - - - - 11 - - - No Residente - - - - diff --git a/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.custom.gc b/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.custom.gc index 9c1ddd6..74ee5d8 100644 --- a/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.custom.gc +++ b/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.custom.gc @@ -30,6 +30,46 @@ + + + O-99 + + + Otro tipo de obligado + + + + + O-06 + + + Ingresos y patrimonio + + + + + O-07 + + + Retención en la fuente a título de renta + + + + + O-08 + + + Retención timbre nacional + + + + + O-09 + + + Retención en la fuente en el impuesto sobre las ventas + + O-13 @@ -38,6 +78,14 @@ Gran contribuyente + + + O-14 + + + Informante de exógena + + O-15 @@ -46,6 +94,38 @@ Autorretenedor + + + O-16 + + + Obligación de facturar por ingresos de bienes y/o servicios excluidos + + + + + O-17 + + + Profesionales de compra y venta de divisas + + + + + O-19 + + + Productor y/o exportador de bienes exentos + + + + + O-22 + + + Obligado a cumplir deberes formales a nombre de terceros + + O-23 @@ -54,6 +134,62 @@ Agente de retención en el impuesto sobre las ventas + + + O-32 + + + Impuesto Nacional a la Gasolina y al ACPM + + + + + O-33 + + + Impuesto Nacional al consumo + + + + + O-34 + + + Régimen simplificado impuesto nacional consumo rest y bares + + + + + O-36 + + + Establecimiento Permanente + + + + + O-37 + + + Obligado a Facturar Electrónicamente Modelo 2242 + + + + + O-38 + + + Facturación Electrónica Voluntaria Modelo 2242 + + + + + O-39 + + + Proveedor de Servicios Tecnológicos PST Modelo 2242 + + O-47 @@ -78,6 +214,782 @@ No responsable de IVA + + + O-52 + + + Facturador electrónico + + + + + O-99 + + + Otro tipo de obligado + + + + + R-00-PN + + + Clientes del Exterior + + + + + R-12-PN + + + Factor PN + + + + + R-16-PN + + + Mandatario + + + + + R-25-PN + + + Agente Interventor + + + + + R-99-PN + + + No responsable + + + + + R-06-PJ + + + Apoderado especial + + + + + R-07-PJ + + + Apoderado general + + + + + R-12-PJ + + + Factor + + + + + R-16-PJ + + + Mandatario + + + + + R-99-PJ + + + Otro tipo de responsable + + + + + A-01 + + + Agente de carga internacional + + + + + A-02 + + + Agente marítimo + + + + + A-03 + + + Almacén general de depósito + + + + + A-04 + + + Comercializadora internacional (C.I.) + + + + + A-05 + + + Comerciante de la zona aduanera especial de Inírida, Puerto Carreño, Cumaribo y Primavera + + + + + A-06 + + + Comerciantes de la zona de régimen aduanero especial de Leticia + + + + + A-07 + + + Comerciantes de la zona de régimen aduanero especial de Maicao, Uribia y Manaure + + + + + A-08 + + + Comerciantes de la zona de régimen aduanero especial de Urabá, Tumaco y Guapí + + + + + A-09 + + + Comerciantes del puerto libre de San Andrés, Providencia y Santa Catalina + + + + + A-10 + + + Depósito público de apoyo logístico internacional + + + + + A-11 + + + Depósito privado para procesamiento industrial + + + + + A-12 + + + Depósito privado de transformación o ensamble + + + + + A-13 + + + Depósito franco + + + + + A-14 + + + Depósito privado aeronáutico + + + + + A-15 + + + Depósito privado para distribución internacional + + + + + A-16 + + + Depósito privado de provisiones de a bordo para consumo y para llevar + + + + + A-17 + + + Depósito privado para envíos urgentes + + + + + A-18 + + + Depósito privado + + + + + A-19 + + + Depósito público + + + + + A-20 + + + Depósito público para distribución internacional + + + + + A-21 + + + Exportador de café + + + + + A-22 + + + Exportador + + + + + A-23 + + + Importador + + + + + A-24 + + + Intermediario de tráfico postal y envíos urgentes + + + + + A-25 + + + Operador de transporte multimodal + + + + + A-26 + + + Sociedad de intermediación aduanera + + + + + A-27 + + + Titular de puertos y muelles de servicio público o privado + + + + + A-28 + + + Transportador 263nfor régimen de importación y/o exportación + + + + + A-29 + + + Transportista nacional para operaciones del régimen de tránsito aduanero + + + + + A-30 + + + Usuario comercial zona franca + + + + + A-32 + + + Usuario industrial de bienes zona franca + + + + + A-34 + + + Usuario industrial de servicios zona franca + + + + + A-36 + + + Usuario operador de zona franca + + + + + A-37 + + + Usuario aduanero permanente + + + + + A-38 + + + Usuario altamente exportador + + + + + A-39 + + + Usuario de zonas económicas especiales de exportación + + + + + A-40 + + + Deposito privado de instalaciones industriales + + + + + A-41 + + + Beneficiarios de programas especiales de exportación PEX + + + + + A-42 + + + Depósitos privados para mercancías en tránsito San Andrés + + + + + A-43 + + + Observadores de las operaciones de importación + + + + + A-44 + + + Usuarios sistemas especiales Importación exportación + + + + + A-46 + + + Transportador 263nformac régimen de importación y/o exportación + + + + + A-47 + + + Transportador terrestre régimen de importación y/o exportación + + + + + A-48 + + + Aeropuerto de servicio publico o privado + + + + + A-49 + + + Transportador fluvial régimen de importación + + + + + A-50 + + + Usuario industrial zona franca especial + + + + + A-53 + + + Agencias de aduanas 1 + + + + + A-54 + + + Usuario Operador Zona Franca Especial + + + + + A-55 + + + Agencias de aduanas 2 + + + + + A-56 + + + Agencias de aduanas 3 + + + + + A-57 + + + Agencias de aduanas 4 + + + + + A-58 + + + Transportador aéreo nacional + + + + + A-60 + + + Transportador aéreo, marítimo o fluvial modalidad Cabotaje + + + + + A-61 + + + Importador de alimentos de consumo humano y animal + + + + + A-62 + + + Importador Ocasional + + + + + A-63 + + + Importador de maquinaría y sus partes Decreto 2261 de 2012 + + + + + A-64 + + + Beneficiario Programa de Fomento Industria Automotriz-PROFIA + + + + + A-99 + + + Otro tipo de agente aduanero + + + + + E-01 + + + Agencia + + + + + E-02 + + + Establecimiento de comercio + + + + + E-03 + + + Centro de explotación agrícola + + + + + E-04 + + + Centro de explotación animal + + + + + E-05 + + + Centro de explotación minera + + + + + E-06 + + + Centro de explotación de transformación + + + + + E-07 + + + Centro de explotación de servicios + + + + + E-08 + + + Oficina + + + + + E-09 + + + Sede + + + + + E-10 + + + Sucursal + + + + + E-11 + + + Consultorio + + + + + E-12 + + + Administraciones + + + + + E-13 + + + Seccionales + + + + + E-14 + + + Regionales + + + + + E-15 + + + Intendencias + + + + + E-16 + + + Local o negocio + + + + + E-17 + + + Punto de venta + + + + + E-18 + + + Fábrica + + + + + E-19 + + + Taller + + + + + E-20 + + + Cantera + + + + + E-21 + + + Pozo de Petróleo y Gas + + + + + E-22 + + + Otro lug de tipo de extrac explotación de recursos naturales + + + + + E-99 + + + Otro tipo de establecimiento + + + + + O-13 + + + Gran contribuyente + + + + + O-15 + + + Autorretenedor + + + + + O-23 + + + Agente de retención IVA + + + + + O-47 + + + Régimen simple de tributación + + R-99-PN diff --git a/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.gc b/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.gc index 36bd0d3..9ba4739 100644 --- a/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.gc +++ b/facho/fe/data/dian/codelist/TipoResponsabilidad-2.1.gc @@ -62,13 +62,5 @@ Régimen simple de tributación - - - ZZ - - - No aplica - - diff --git a/facho/fe/data/dian/codelist/__init__.py b/facho/fe/data/dian/codelist/__init__.py index 837009d..feeaa45 100644 --- a/facho/fe/data/dian/codelist/__init__.py +++ b/facho/fe/data/dian/codelist/__init__.py @@ -82,16 +82,11 @@ TipoAmbiente = CodeList(path_for_codelist('TipoAmbiente-2.1.gc'), 'code', 'name' TipoDocumento = CodeList(path_for_codelist('TipoDocumento-2.1.gc'), 'code', 'name') TipoImpuesto = CodeList(path_for_codelist('TipoImpuesto-2.1.gc'), 'code', 'name')\ .update(CodeList(path_for_codelist('TipoImpuesto-2.1.custom.gc'), 'code', 'name')) -TarifaImpuesto = CodeList(path_for_codelist('TarifaImpuestoINC-2.1.gc'), 'code', 'name')\ - .update(CodeList(path_for_codelist('TarifaImpuestoIVA-2.1.gc'), 'code', 'name'))\ - .update(CodeList(path_for_codelist('TarifaImpuestoReteIVA-2.1.gc'), 'code', 'name'))\ - .update(CodeList(path_for_codelist('TarifaImpuestoReteRenta-2.1.gc'), 'code', 'name')) CodigoPrecioReferencia = CodeList(path_for_codelist('CodigoPrecioReferencia-2.1.gc'), 'code', 'name') MediosPago = CodeList(path_for_codelist('MediosPago-2.1.gc'), 'code', 'name') FormasPago = CodeList(path_for_codelist('FormasPago-2.1.gc'), 'code', 'name') RegimenFiscal = CodeList(path_for_codelist('RegimenFiscal-2.1.custom.gc'), 'code', 'name') TipoOperacionNC = CodeList(path_for_codelist('TipoOperacionNC-2.1.gc'), 'code', 'name') -TipoOperacionNCDS = CodeList(path_for_codelist('TipoOperacionNCDS-2.1.gc'), 'code', 'name') TipoOperacionND = CodeList(path_for_codelist('TipoOperacionND-2.1 - copia.gc'), 'code', 'name') TipoOperacionF = CodeList(path_for_codelist('TipoOperacionF-2.1.gc'), 'code', 'name')\ .update(CodeList(path_for_codelist('TipoOperacionF-2.1.custom.gc'), 'code', 'name')) diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 40005fb..39de2b8 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -1,5 +1,6 @@ # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. + from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder import uuid import xmlsig @@ -7,16 +8,13 @@ import xades from datetime import datetime import OpenSSL import zipfile -# import warnings +import warnings import hashlib from contextlib import contextmanager from .data.dian import codelist from . import form from collections import defaultdict -# from pathlib import Path -from dateutil import tz - -from cryptography.hazmat.primitives.serialization import pkcs12 +from pathlib import Path AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code'] AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code'] @@ -32,51 +30,32 @@ SCHEME_AGENCY_ATTRS = { POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf' POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.' -Bogota = tz.gettz('America/Bogota') -# NAMESPACES = { -# 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', -# 'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual', -# 'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste', -# 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', -# 'xs': 'http://www.w3.org/2001/XMLSchema-instance', -# 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', -# 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', -# 'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1', -# 'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001', -# 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003', -# 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', -# 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2', -# 'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', -# 'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2', -# 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', -# 'xades': 'http://uri.etsi.org/01903/v1.3.2#', -# 'xades141': 'http://uri.etsi.org/01903/v1.4.1#', -# 'ds': 'http://www.w3.org/2000/09/xmldsig#', -# 'sig': 'http://www.w3.org/2000/09/xmldsig#', -# } - NAMESPACES = { - 'apr': 'urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2', 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', + 'no': 'dian:gov:co:facturaelectronica:NominaIndividual', 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', + 'xs': 'http://www.w3.org/2001/XMLSchema-instance', 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', + 'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1', + 'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001', + 'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003', 'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', 'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2', 'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', - 'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2', + 'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', - 'ds': 'http://www.w3.org/2000/09/xmldsig#', 'xades': 'http://uri.etsi.org/01903/v1.3.2#', + 'xades141': 'http://uri.etsi.org/01903/v1.4.1#', + 'ds': 'http://www.w3.org/2000/09/xmldsig#', + 'sig': 'http://www.w3.org/2000/09/xmldsig#', } - def fe_from_string(document: str) -> FachoXML: return FeXML.from_string(document) - -# from contextlib import contextmanager +from contextlib import contextmanager @contextmanager def mock_xades_policy(): from mock import patch @@ -94,39 +73,29 @@ def mock_xades_policy(): mock.return_value = UrllibPolicyMock() yield - + class FeXML(FachoXML): def __init__(self, root, namespace): - # raise Exception(namespace) + super().__init__("{%s}%s" % (namespace, root), nsmap=NAMESPACES) @classmethod def from_string(cls, document: str) -> 'FeXML': return super().from_string(document, namespaces=NAMESPACES) - + def tostring(self, **kw): # MACHETE(bit4bit) la DIAN espera que la etiqueta raiz no este en un namespace - urn_oasis = { - 'AttachedDocument': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', - 'Invoice': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2', - 'CreditNote': 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2', - 'ApplicationResponse': 'urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2' - } - - root_namespace = self.root_namespace() - root_localname = self.root_localname() - xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace] - return super().tostring(**kw)\ - .replace(xmlns_name + ':', '')\ - .replace('xmlns:'+xmlns_name, 'xmlns')\ - .replace(root_namespace, urn_oasis[root_localname]) + .replace("fe:", "")\ + .replace("xmlns:no", "xmlns")\ + .replace("change", "xsi:schemaLocation") class DianXMLExtensionCUDFE(FachoXMLExtension): - def __init__(self, invoice, tipo_ambiente=AMBIENTE_PRUEBAS): + + def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS): self.tipo_ambiente = tipo_ambiente self.invoice = invoice @@ -151,25 +120,12 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): fachoxml.set_element('./cbc:UUID', cufe, schemeID=self.tipo_ambiente, schemeName=self.schemeName()) - - if self.schemeName() == "CUDS-SHA384": - if fachoxml.tag_document() == 'Invoice': - fachoxml.set_element('./cbc:ProfileID', - 'DIAN 2.1: documento soporte en adquisiciones efectuadas a no obligados a facturar.') - else: - fachoxml.set_element('./cbc:ProfileID', - 'DIAN 2.1: Nota de ajuste al documento soporte en adquisiciones efectuadas a sujetos no obligados a expedir factura o documento equivalente') - else: - fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') - - # #DIAN 1.8.-2021: FAD03 - # fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') - fachoxml.set_element( - './cbc:ProfileExecutionID', self._tipo_ambiente_int()) + #DIAN 1.8.-2021: FAD03 + fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') + fachoxml.set_element('./cbc:ProfileExecutionID', self._tipo_ambiente_int()) #DIAN 1.7.-2020: FAB36 - fachoxml.set_element( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', - self._get_qrcode(cufe)) + fachoxml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', + self._get_qrcode(cufe)) def issue_time(self, datetime_): return datetime_.strftime('%H:%M:%S-05:00') @@ -185,8 +141,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue) # PAG 601 build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount - build_vars['ValorTotalPagar' - ] = invoice.invoice_legal_monetary_total.payable_amount + build_vars['ValorTotalPagar'] = invoice.invoice_legal_monetary_total.payable_amount ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0)) build_vars['CodImpuesto1'] = '01' build_vars['CodImpuesto2'] = '04' @@ -216,8 +171,7 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): class DianXMLExtensionCUFE(DianXMLExtensionCUDFE): - def __init__( - self, invoice, clave_tecnica='', tipo_ambiente=AMBIENTE_PRUEBAS): + def __init__(self, invoice, clave_tecnica = '', tipo_ambiente = AMBIENTE_PRUEBAS): self.tipo_ambiente = tipo_ambiente self.clave_tecnica = clave_tecnica self.invoice = invoice @@ -253,7 +207,6 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE): '%d' % build_vars['TipoAmb'], ] - class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS): self.tipo_ambiente = tipo_ambiente @@ -291,41 +244,6 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): '%d' % build_vars['TipoAmb'], ] - -class DianXMLExtensionCUDS(DianXMLExtensionCUDFE): - def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS): - self.tipo_ambiente = tipo_ambiente - self.software_pin = software_pin - self.invoice = invoice - - def schemeName(self): - return 'CUDS-SHA384' - - def buildVars(self): - build_vars = super().buildVars() - build_vars['Software-PIN'] = str(self.software_pin) - return build_vars - - def formatVars(self): - build_vars = self.buildVars() - CodImpuesto1 = build_vars['CodImpuesto1'] - CodImpuesto2 = build_vars['CodImpuesto2'] - CodImpuesto3 = build_vars['CodImpuesto3'] - return [ - '%s' % build_vars['NumFac'], - '%s' % build_vars['FecFac'], - '%s' % build_vars['HoraFac'], - form.Amount(build_vars['ValorBruto']).truncate_as_string(2), - CodImpuesto1, - form.Amount(build_vars['ValorImpuestoPara'].get(CodImpuesto1, 0.0)).truncate_as_string(2), - form.Amount(build_vars['ValorTotalPagar']).truncate_as_string(2), - '%s' % build_vars['NitOFE'], - '%s' % build_vars['NumAdq'], - '%s' % build_vars['Software-PIN'], - '%d' % build_vars['TipoAmb'], - ] - - class DianXMLExtensionSoftwareProvider(FachoXMLExtension): # RESOLUCION 0004: pagina 108 @@ -335,8 +253,7 @@ class DianXMLExtensionSoftwareProvider(FachoXMLExtension): self.id_software = id_software def build(self, fexml): - software_provider = fexml.fragment( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') + software_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') provider_id_attrs = SCHEME_AGENCY_ATTRS.copy() provider_id_attrs.update({'schemeID': self.dv}) #DIAN 1.7.-2020: FAB23 @@ -366,6 +283,7 @@ class DianXMLExtensionSoftwareSecurityCode(FachoXMLExtension): class DianXMLExtensionSigner: + def __init__(self, pkcs12_path, passphrase=None, localpolicy=True): self._pkcs12_data = open(pkcs12_path, 'rb').read() self._passphrase = None @@ -376,18 +294,17 @@ class DianXMLExtensionSigner: @classmethod def from_bytes(cls, data, passphrase=None, localpolicy=True): self = cls.__new__(cls) + self._pkcs12_data = data self._passphrase = None self._localpolicy = localpolicy if passphrase: self._passphrase = passphrase.encode('utf-8') - + return self def _element_extension_content(self, fachoxml): - return fachoxml.builder.xpath( - fachoxml.root, - './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') + return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') def sign_xml_string(self, document): xml = LXMLBuilder.from_string(document) @@ -409,6 +326,7 @@ class DianXMLExtensionSigner: ) xml.append(signature) + ref = xmlsig.template.add_reference( signature, xmlsig.constants.TransformSha256, uri="", name="xmldsig-%s-ref0" % (id_uuid) ) @@ -416,16 +334,14 @@ class DianXMLExtensionSigner: id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid) xmlsig.template.add_reference( - signature, xmlsig.constants.TransformSha256, uri="#%s" % ( - id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid), + signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid), ) ki = xmlsig.template.ensure_key_info(signature, name=id_keyinfo) data = xmlsig.template.add_x509_data(ki) xmlsig.template.x509_data_add_certificate(data) xmlsig.template.add_key_value(ki) - qualifying = xades.template.create_qualifying_properties( - signature, 'XadesObjects', 'xades') + qualifying = xades.template.create_qualifying_properties(signature, 'XadesObjects', 'xades') xades.utils.ensure_id(qualifying) id_props = "xmldsig-%s-signedprops" % (id_uuid) @@ -433,12 +349,10 @@ class DianXMLExtensionSigner: signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props), uri_type="http://uri.etsi.org/01903#SignedProperties" ) - xmlsig.template.add_transform( - props_ref, xmlsig.constants.TransformInclC14N) + xmlsig.template.add_transform(props_ref, xmlsig.constants.TransformInclC14N) # TODO assert with http://www.sic.gov.co/hora-legal-colombiana - props = xades.template.create_signed_properties( - qualifying, name=id_props, datetime=datetime.now(tz=Bogota)) + props = xades.template.create_signed_properties(qualifying, name=id_props, datetime=datetime.now()) xades.template.add_claimed_role(props, "supplier") policy = xades.policy.GenericPolicyId( @@ -446,13 +360,9 @@ class DianXMLExtensionSigner: POLICY_NAME, xmlsig.constants.TransformSha256) ctx = xades.XAdESContext(policy) - ctx.load_pkcs12(pkcs12.load_key_and_certificates( - self._pkcs12_data, - self._passphrase)) + ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(self._pkcs12_data, + self._passphrase)) - # ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12( - # self._pkcs12_data, - # self._passphrase)) if self._localpolicy: with mock_xades_policy(): ctx.sign(signature) @@ -460,7 +370,7 @@ class DianXMLExtensionSigner: else: ctx.sign(signature) ctx.verify(signature) - # xmlsig take parent root + #xmlsig take parent root xml.remove(signature) return signature @@ -469,28 +379,29 @@ class DianXMLExtensionSigner: extcontent = self._element_extension_content(fachoxml) fachoxml.append_element(extcontent, signature) - + class DianXMLExtensionAuthorizationProvider(FachoXMLExtension): # RESOLUCION 0004: pagina 176 def build(self, fexml): attrs = {'schemeID': '4', 'schemeName': '31'} attrs.update(SCHEME_AGENCY_ATTRS) + authorization_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider') authorization_provider.set_element('./sts:AuthorizationProviderID', '800197268', **attrs) + class DianXMLExtensionInvoiceSource(FachoXMLExtension): # CAB13 def build(self, fexml): dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode' - fexml.set_element( - dian_path, 'CO', - listAgencyID="6", - listAgencyName="United Nations Economic Commission for Europe", - listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") + fexml.set_element(dian_path, 'CO', + listAgencyID="6", + listAgencyName="United Nations Economic Commission for Europe", + listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension): @@ -520,15 +431,16 @@ class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension): invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To', self.to) - fexml.set_element( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode', - 'CO', - # DIAN 1.7.-2020: FAB15 - listAgencyID="6", - # DIAN 1.7.-2020: FAB16 - listAgencyName="United Nations Economic Commission for Europe", - # DIAN 1.7.-2020: FAB17 - listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") + fexml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode', + 'CO', + #DIAN 1.7.-2020: FAB15 + listAgencyID="6", + #DIAN 1.7.-2020: FAB16 + listAgencyName="United Nations Economic Commission for Europe", + #DIAN 1.7.-2020: FAB17 + listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1" + ) + class DianZIP: @@ -537,8 +449,7 @@ class DianZIP: MAX_FILES = 50 def __init__(self, file_like): - self.zipfile = zipfile.ZipFile( - file_like, mode='w', compression=zipfile.ZIP_DEFLATED) + self.zipfile = zipfile.ZipFile(file_like, mode='w', compression=zipfile.ZIP_DEFLATED) self.num_files = 0 def add_xml(self, name, xml_data): @@ -559,6 +470,7 @@ class DianZIP: def __enter__(self): """ Facilita el uso de esta manera: + f = open('xxx', 'rb') with DianZIP(f) as zip: zip.add_invoice_xml('name', 'data xml') @@ -581,7 +493,7 @@ class DianXMLExtensionSignerVerifier: def verify_string(self, document): # Obtener FachoXML xml = LXMLBuilder.from_string(document) - fachoxml = FachoXML(xml, nsmap=NAMESPACES) + fachoxml = FachoXML(xml,nsmap=NAMESPACES) # Obtener Signature signature = fachoxml.builder.xpath(fachoxml.root, '//ds:Signature') diff --git a/facho/fe/form/__init__.py b/facho/fe/form/__init__.py index 026eef8..0aa6318 100644 --- a/facho/fe/form/__init__.py +++ b/facho/fe/form/__init__.py @@ -1,26 +1,24 @@ # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -# import hashlib -# from functools import reduce -# import copy - +import hashlib +from functools import reduce +import copy import dataclasses -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime, date -# from collections import defaultdict +from collections import defaultdict import decimal from decimal import Decimal import typing + from ..data.dian import codelist DECIMAL_PRECISION = 6 - class AmountCurrencyError(TypeError): pass - @dataclass class Currency: code: str @@ -31,7 +29,6 @@ class Currency: def __str__(self): return self.code - class Collection: def __init__(self, array): @@ -48,7 +45,6 @@ class Collection: def sum(self): return sum(self.array) - class AmountCollection(Collection): def sum(self): @@ -57,13 +53,10 @@ class AmountCollection(Collection): total += v return total - class Amount: - def __init__( - self, amount: typing.Union[int, float, str, "Amount"], - currency: Currency = Currency('COP')): + def __init__(self, amount: int or float or str or Amount, currency: Currency = Currency('COP')): - # DIAN 1.7.-2020: 1.2.3.1 + #DIAN 1.7.-2020: 1.2.3.1 if isinstance(amount, Amount): if amount < Amount(0.0): raise ValueError('amount must be positive >= 0') @@ -74,16 +67,14 @@ class Amount: if float(amount) < 0: raise ValueError('amount must be positive >= 0') - self.amount = Decimal( - amount, decimal.Context( - prec=DECIMAL_PRECISION, - # DIAN 1.7.-2020: 1.2.1.1 - rounding=decimal.ROUND_HALF_EVEN)) + self.amount = Decimal(amount, decimal.Context(prec=DECIMAL_PRECISION, + #DIAN 1.7.-2020: 1.2.1.1 + rounding=decimal.ROUND_HALF_EVEN )) self.currency = currency def fromNumber(self, val): return Amount(val, currency=self.currency) - + def round(self, prec): return Amount(round(self.amount, prec), currency=self.currency) @@ -101,8 +92,7 @@ class Amount: def __eq__(self, other): if not self.is_same_currency(other): raise AmountCurrencyError() - return round(self.amount, DECIMAL_PRECISION) == round( - other.amount, DECIMAL_PRECISION) + return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION) def _cast(self, val): if type(val) in [int, float]: @@ -110,7 +100,7 @@ class Amount: if isinstance(val, Amount): return val raise TypeError("cant cast to amount") - + def __add__(self, rother): other = self._cast(rother) if not self.is_same_currency(other): @@ -134,14 +124,14 @@ class Amount: def truncate_as_string(self, prec): parts = str(self.float()).split('.', 1) - return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec, '0')) + return '%s.%s' % (parts[0], parts[1][0:prec].ljust(prec,'0')) def float(self): return float(round(self.amount, DECIMAL_PRECISION)) - + class Quantity: - + def __init__(self, val, code): if type(val) not in [float, int]: raise ValueError('val expected int or float') @@ -163,7 +153,6 @@ class Quantity: def __repr__(self): return str(self) - @dataclass class Item: scheme_name: str @@ -174,10 +163,10 @@ class Item: class StandardItem(Item): - def __init__(self, id_: str, description: str = '', name: str = ''): + def __init__(self, id_: str, description: str = ''): super().__init__(id=id_, description=description, - scheme_name=name, + scheme_name='', scheme_id='999', scheme_agency_id='') @@ -188,9 +177,9 @@ class UNSPSCItem(Item): description=description, scheme_name='UNSPSC', scheme_id='001', - scheme_agency_id='10') - + scheme_agency_id='10') + @dataclass class Country: code: str @@ -201,7 +190,6 @@ class Country: raise ValueError("code [%s] not found" % (self.code)) self.name = codelist.Paises[self.code]['name'] - @dataclass class CountrySubentity: code: str @@ -212,7 +200,6 @@ class CountrySubentity: raise ValueError("code [%s] not found" % (self.code)) self.name = codelist.Departamento[self.code]['name'] - @dataclass class City: code: str @@ -223,22 +210,13 @@ class City: raise ValueError("code [%s] not found" % (self.code)) self.name = codelist.Municipio[self.code]['name'] - -@dataclass -class PostalZone: - code: str = '' - - @dataclass class Address: name: str street: str = '' - city: City = field(default_factory=lambda: City('05001')) - country: Country = field(default_factory=lambda: Country('CO')) - countrysubentity: CountrySubentity = field( - default_factory=lambda: CountrySubentity('05')) - postalzone: PostalZone = field(default_factory=lambda: PostalZone('')) - + city: City = City('05001') + country: Country = Country('CO') + countrysubentity: CountrySubentity = CountrySubentity('05') @dataclass class PartyIdentification: @@ -259,7 +237,6 @@ class PartyIdentification: if self.type_fiscal not in codelist.TipoIdFiscal: raise ValueError("type_fiscal [%s] not found" % (self.type_fiscal)) - @dataclass class Responsability: codes: list @@ -284,12 +261,12 @@ class TaxScheme: code: str name: str = '' + def __post_init__(self): if self.code not in codelist.TipoImpuesto: raise ValueError("code not found") self.name = codelist.TipoImpuesto[self.code]['name'] - @dataclass class Party: name: str @@ -297,10 +274,10 @@ class Party: responsability_code: typing.List[Responsability] responsability_regime_code: str organization_code: str - tax_scheme: TaxScheme = field(default_factory=lambda: TaxScheme('01')) + tax_scheme: TaxScheme = TaxScheme('01') phone: str = '' - address: Address = field(default_factory=lambda: Address('')) + address: Address = Address('') email: str = '' legal_name: str = '' legal_company_ident: str = '' @@ -328,7 +305,7 @@ class TaxScheme: class TaxSubTotal: percent: float scheme: typing.Optional[TaxScheme] = None - tax_amount: Amount = field(default_factory=lambda: Amount(0.0)) + tax_amount: Amount = Amount(0.0) def calculate(self, invline): if self.percent is not None: @@ -338,11 +315,12 @@ class TaxSubTotal: @dataclass class TaxTotal: subtotals: list - tax_amount: Amount = field(default_factory=lambda: Amount(0.0)) - taxable_amount: Amount = field(default_factory=lambda: Amount(0.0)) + tax_amount: Amount = Amount(0.0) + taxable_amount: Amount = Amount(0.0) def calculate(self, invline): self.taxable_amount = invline.total_amount + for subtax in self.subtotals: subtax.calculate(invline) self.tax_amount += subtax.tax_amount @@ -355,40 +333,6 @@ class TaxTotalOmit(TaxTotal): def calculate(self, invline): pass - -@dataclass -class WithholdingTaxSubTotal: - percent: float - scheme: typing.Optional[TaxScheme] = None - tax_amount: Amount = field(default_factory=lambda: Amount(0.0)) - - def calculate(self, invline): - if self.percent is not None: - self.tax_amount = invline.total_amount * Amount(self.percent / 100) - - -@dataclass -class WithholdingTaxTotal: - subtotals: list - tax_amount: Amount = field(default_factory=lambda: Amount(0.0)) - taxable_amount: Amount = field(default_factory=lambda: Amount(0.0)) - - def calculate(self, invline): - self.taxable_amount = invline.total_amount - - for subtax in self.subtotals: - subtax.calculate(invline) - self.tax_amount += subtax.tax_amount - - -class WithholdingTaxTotalOmit(WithholdingTaxTotal): - def __init__(self): - super().__init__([]) - - def calculate(self, invline): - pass - - @dataclass class Price: amount: Amount @@ -404,7 +348,6 @@ class Price: self.amount *= self.quantity - @dataclass class PaymentMean: DEBIT = '01' @@ -422,24 +365,8 @@ class PaymentMean: @dataclass class PrePaidPayment: - # DIAN 1.7.-2020: FBD03 - paid_amount: Amount = field(default_factory=lambda: Amount(0.0)) - - -@dataclass -class BillingResponse: - id: str - code: str - description: str - - -class SupportDocumentCreditNoteResponse(BillingResponse): - """ - ReferenceID: Identifica la sección del Documento - Soporte original a la cual se aplica la corrección. - ResponseCode: Código de descripción de la corrección. - Description: Descripción de la naturaleza de la corrección. - """ + #DIAN 1.7.-2020: FBD03 + paid_amount: Amount = Amount(0.0) @dataclass @@ -448,7 +375,6 @@ class BillingReference: uuid: str date: date - class CreditNoteDocumentReference(BillingReference): """ ident: Prefijo + Numero de la factura relacionada @@ -464,7 +390,6 @@ class DebitNoteDocumentReference(BillingReference): date: fecha de emision de la factura relacionada """ - class InvoiceDocumentReference(BillingReference): """ ident: Prefijo + Numero de la nota credito relacionada @@ -472,7 +397,6 @@ class InvoiceDocumentReference(BillingReference): date: fecha de emision de la nota credito relacionada """ - @dataclass class AllowanceChargeReason: code: str @@ -485,26 +409,22 @@ class AllowanceChargeReason: @dataclass class AllowanceCharge: - # DIAN 1.7.-2020: FAQ03 + #DIAN 1.7.-2020: FAQ03 charge_indicator: bool = True - amount: Amount = field(default_factory=lambda: Amount(0.0)) + amount: Amount = Amount(0.0) reason: AllowanceChargeReason = None - # Valor Base para calcular el descuento o el cargo - base_amount: typing.Optional[Amount] = field( - default_factory=lambda: Amount(0.0)) - + #Valor Base para calcular el descuento o el cargo + base_amount: typing.Optional[Amount] = Amount(0.0) + # Porcentaje: Porcentaje que aplicar. - multiplier_factor_numeric: Amount = field( - default_factory=lambda: Amount(1.0)) - + multiplier_factor_numeric: Amount = Amount(1.0) + def isCharge(self): - charge_indicator = self.charge_indicator is True - return charge_indicator + return self.charge_indicator == True def isDiscount(self): - charge_indicator = self.charge_indicator is False - return charge_indicator + return self.charge_indicator == False def asCharge(self): self.charge_indicator = True @@ -518,13 +438,11 @@ class AllowanceCharge: def set_base_amount(self, amount): self.base_amount = amount - class AllowanceChargeAsDiscount(AllowanceCharge): def __init__(self, amount: Amount = Amount(0.0)): self.charge_indicator = False self.amount = amount - @dataclass class InvoiceLine: # RESOLUCION 0004: pagina 155 @@ -537,9 +455,8 @@ class InvoiceLine: # la factura y el percent es unico por type_code # de subtotal tax: typing.Optional[TaxTotal] - withholding: typing.Optional[WithholdingTaxTotal] - allowance_charge: typing.List[AllowanceCharge] = dataclasses.field( - default_factory=list) + + allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(default_factory=list) def add_allowance_charge(self, charge): if not isinstance(charge, AllowanceCharge): @@ -550,7 +467,7 @@ class InvoiceLine: @property def total_amount_without_charge(self): return (self.quantity * self.price.amount) - + @property def total_amount(self): charge = AmountCollection(self.allowance_charge)\ @@ -582,17 +499,8 @@ class InvoiceLine: def taxable_amount(self): return self.tax.taxable_amount - @property - def withholding_amount(self): - return self.withholding.tax_amount - - @property - def withholding_taxable_amount(self): - return self.withholding.taxable_amount - def calculate(self): self.tax.calculate(self) - self.withholding.calculate(self) def __post_init__(self): if not isinstance(self.quantity, Quantity): @@ -601,22 +509,18 @@ class InvoiceLine: if self.tax is None: self.tax = TaxTotalOmit() - if self.withholding is None: - self.withholding = WithholdingTaxTotalOmit() - - @dataclass class LegalMonetaryTotal: - line_extension_amount: Amount = field(default_factory=lambda: Amount(0.0)) - tax_exclusive_amount: Amount = field(default_factory=lambda: Amount(0.0)) - tax_inclusive_amount: Amount = field(default_factory=lambda: Amount(0.0)) - charge_total_amount: Amount = field(default_factory=lambda: Amount(0.0)) - allowance_total_amount: Amount = field(default_factory=lambda: Amount(0.0)) - payable_amount: Amount = field(default_factory=lambda: Amount(0.0)) - prepaid_amount: Amount = field(default_factory=lambda: Amount(0.0)) + line_extension_amount: Amount = Amount(0.0) + tax_exclusive_amount: Amount = Amount(0.0) + tax_inclusive_amount: Amount = Amount(0.0) + charge_total_amount: Amount = Amount(0.0) + allowance_total_amount: Amount = Amount(0.0) + payable_amount: Amount = Amount(0.0) + prepaid_amount: Amount = Amount(0.0) def calculate(self): - # DIAN 1.7.-2020: FAU14 + #DIAN 1.7.-2020: FAU14 self.payable_amount = \ self.tax_inclusive_amount \ + self.allowance_total_amount \ @@ -624,29 +528,22 @@ class LegalMonetaryTotal: - self.prepaid_amount + class NationalSalesInvoiceDocumentType(str): def __str__(self): # 6.1.3 return '01' - class CreditNoteDocumentType(str): def __str__(self): # 6.1.3 return '91' - class DebitNoteDocumentType(str): def __str__(self): # 6.1.3 return '92' - -class CreditNoteSupportDocumentType(str): - def __str__(self): - return '95' - - class Invoice: def __init__(self, type_code: str): if str(type_code) not in codelist.TipoDocumento: @@ -666,7 +563,6 @@ class Invoice: self.invoice_allowance_charge = [] self.invoice_prepaid_payment = [] self.invoice_billing_reference = None - self.invoice_discrepancy_response = None self.invoice_type_code = str(type_code) self.invoice_ident_prefix = None @@ -692,8 +588,7 @@ class Invoice: if len(prefix) <= 4: self.invoice_ident_prefix = prefix else: - raise ValueError( - 'ident prefix failed to get, expected 0 to 4 chars') + raise ValueError('ident prefix failed to get, expected 0 to 4 chars') def set_ident(self, ident: str): """ @@ -724,7 +619,7 @@ class Invoice: def _get_codelist_tipo_operacion(self): return codelist.TipoOperacionF - + def set_operation_type(self, operation): if operation not in self._get_codelist_tipo_operacion(): raise ValueError("operation not found") @@ -743,9 +638,6 @@ class Invoice: def set_billing_reference(self, billing_reference: BillingReference): self.invoice_billing_reference = billing_reference - def set_discrepancy_response(self, billing_response: BillingResponse): - self.invoice_discrepancy_response = billing_response - def accept(self, visitor): visitor.visit_payment_mean(self.invoice_payment_mean) visitor.visit_customer(self.invoice_customer) @@ -757,34 +649,29 @@ class Invoice: def _calculate_legal_monetary_total(self): for invline in self.invoice_lines: - self.invoice_legal_monetary_total.line_extension_amount +=\ - invline.total_amount - self.invoice_legal_monetary_total.tax_exclusive_amount +=\ - invline.total_tax_exclusive_amount - # DIAN 1.7.-2020: FAU6 - self.invoice_legal_monetary_total.tax_inclusive_amount +=\ - invline.total_tax_inclusive_amount + self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount + self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_tax_exclusive_amount + #DIAN 1.7.-2020: FAU6 + self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount - # DIAN 1.7.-2020: FAU08 - self.invoice_legal_monetary_total.allowance_total_amount =\ - AmountCollection(self.invoice_allowance_charge)\ + #DIAN 1.7.-2020: FAU08 + self.invoice_legal_monetary_total.allowance_total_amount = AmountCollection(self.invoice_allowance_charge)\ .filter(lambda charge: charge.isDiscount())\ .map(lambda charge: charge.amount)\ .sum() - # DIAN 1.7.-2020: FAU10 - self.invoice_legal_monetary_total.charge_total_amount =\ - AmountCollection(self.invoice_allowance_charge)\ + #DIAN 1.7.-2020: FAU10 + self.invoice_legal_monetary_total.charge_total_amount = AmountCollection(self.invoice_allowance_charge)\ .filter(lambda charge: charge.isCharge())\ .map(lambda charge: charge.amount)\ .sum() - # DIAN 1.7.-2020: FAU12 - self.invoice_legal_monetary_total.prepaid_amount = AmountCollection( - self.invoice_prepaid_payment).map( - lambda paid: paid.paid_amount).sum() + #DIAN 1.7.-2020: FAU12 + self.invoice_legal_monetary_total.prepaid_amount = AmountCollection(self.invoice_prepaid_payment)\ + .map(lambda paid: paid.paid_amount)\ + .sum() - # DIAN 1.7.-2020: FAU14 + #DIAN 1.7.-2020: FAU14 self.invoice_legal_monetary_total.calculate() def _refresh_charges_base_amount(self): @@ -792,21 +679,18 @@ class Invoice: for invline in self.invoice_lines: if invline.allowance_charge: # TODO actualmente solo uno de los cargos es permitido - raise ValueError( - 'allowance charge in invoice exclude invoice line') - + raise ValueError('allowance charge in invoice exclude invoice line') + # cargos a nivel de factura for charge in self.invoice_allowance_charge: - charge.set_base_amount( - self.invoice_legal_monetary_total.line_extension_amount) - + charge.set_base_amount(self.invoice_legal_monetary_total.line_extension_amount) + def calculate(self): for invline in self.invoice_lines: invline.calculate() self._calculate_legal_monetary_total() self._refresh_charges_base_amount() - class NationalSalesInvoice(Invoice): def __init__(self): super().__init__(NationalSalesInvoiceDocumentType()) @@ -822,7 +706,7 @@ class CreditNote(Invoice): def _get_codelist_tipo_operacion(self): return codelist.TipoOperacionNC - + def _check_ident_prefix(self, prefix): if len(prefix) != 6: raise ValueError('prefix must be 6 length') @@ -851,30 +735,3 @@ class DebitNote(Invoice): if not self.invoice_ident_prefix: self.invoice_ident_prefix = self.invoice_ident[0:6] - -class SupportDocument(Invoice): - pass - - -class SupportDocumentCreditNote(SupportDocument): - def __init__( - self, invoice_document_reference: BillingReference, - invoice_discrepancy_response: BillingResponse): - super().__init__(CreditNoteSupportDocumentType()) - - if not isinstance(invoice_document_reference, BillingReference): - raise TypeError('invoice_document_reference invalid type') - self.invoice_billing_reference = invoice_document_reference - self.invoice_discrepancy_response = invoice_discrepancy_response - - def _get_codelist_tipo_operacion(self): - return codelist.TipoOperacionNCDS - - def _check_ident_prefix(self, prefix): - if len(prefix) != 6: - raise ValueError('prefix must be 6 length') - - def _set_ident_prefix_automatic(self): - if not self.invoice_ident_prefix: - self.invoice_ident_prefix = self.invoice_ident[0:6] - pass diff --git a/facho/fe/form_xml/__init__.py b/facho/fe/form_xml/__init__.py index 4b302c0..b7530fa 100644 --- a/facho/fe/form_xml/__init__.py +++ b/facho/fe/form_xml/__init__.py @@ -2,7 +2,4 @@ from .invoice import * from .credit_note import * from .debit_note import * from .utils import * -from .support_document import * -from .support_document_credit_note import * from .attached_document import * -from .application_response import * diff --git a/facho/fe/form_xml/application_response.py b/facho/fe/form_xml/application_response.py deleted file mode 100644 index 17fee92..0000000 --- a/facho/fe/form_xml/application_response.py +++ /dev/null @@ -1,177 +0,0 @@ -from .. import fe - -__all__ = ['ApplicationResponse'] - - -class ApplicationResponse: - - def __init__(self, invoice, tag_document='ApplicationResponse'): - self.schema =\ - 'urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2' - self.tag_document = tag_document - self.invoice = invoice - self.fexml = fe.FeXML( - self.tag_document, self.schema) - self.application_response = self.application_response() - - def application_response(self): - # DIAN 1.9.-2023: AE02 - self.fexml.set_element( - './cbc:UBLVersionID', 'UBL 2.1') - - # DIAN 1.9.-2023: AE03 - self.fexml.set_element( - './cbc:CustomizationID', 'Documentos adjuntos') - - # DIAN 1.9.-2023: AE04 - self.fexml.set_element( - './cbc:ProfileID', 'DIAN 2.1') - - # DIAN 1.9.-2023: AE04a - self.fexml.set_element( - './cbc:ProfileExecutionID', '1') - - self.fexml.set_element( - './cbc:ID', '1') - - self.fexml.set_element( - './cbc:UUID', '1', schemeName="CUDE-SHA384") - - self.fexml.set_element( - './cbc:IssueDate', - self.invoice.invoice_issue.strftime('%Y-%m-%d')) - - # DIAN 1.9.-2023: AE06 - self.fexml.set_element( - './cbc:IssueTime', self.invoice.invoice_issue.strftime( - '%H:%M:%S-05:00')) - - self.set_sender_party() - self.set_receiver_party() - self.set_document_response() - - def set_sender_party(self): - # DIAN 1.9.-2023: AE09 - self.fexml.placeholder_for( - './cac:SenderParty') - # DIAN 1.9.-2023: AE10 - self.fexml.placeholder_for( - './cac:SenderParty/cac:PartyTaxScheme') - # DIAN 1.9.-2023: AE11 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cbc:RegistrationName', - self.invoice.invoice_supplier.name) - # DIAN 1.9.-2023: AE12 - # DIAN 1.9.-2023: AE13 - # DIAN 1.9.-2023: AE14 - # DIAN 1.9.-2023: AE15 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cbc:CompanyID', - self.invoice.invoice_supplier.ident, - schemeAgencyID='195', - schemeID=self.invoice.invoice_supplier.ident.dv, - schemeName=self.invoice.invoice_supplier.ident.type_fiscal) - - # DIAN 1.9.-2023: AE16 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cbc:TaxLevelCode', - self.invoice.invoice_supplier.responsability_code) - - # DIAN 1.9.-2023: AE18 - self.fexml.placeholder_for( - './cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme') - - # DIAN 1.9.-2023: AE19 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - self.invoice.invoice_supplier.tax_scheme.code) - - # DIAN 1.9.-2023: AE20 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - self.invoice.invoice_supplier.tax_scheme.name) - - def set_receiver_party(self): - # DIAN 1.9.-2023: AE21 - self.fexml.placeholder_for( - './cac:ReceiverParty') - # DIAN 1.9.-2023: AE22 - self.fexml.placeholder_for( - './cac:ReceiverParty/cac:PartyTaxScheme') - # DIAN 1.9.-2023: AE23 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cbc:RegistrationName', - self.invoice.invoice_customer.name) - # DIAN 1.9.-2023: AE24 - # DIAN 1.9.-2023: AE25 - # DIAN 1.9.-2023: AE26 - # DIAN 1.9.-2023: AE27 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cbc:CompanyID', - self.invoice.invoice_customer.ident, - schemeAgencyID='195', - schemeID=self.invoice.invoice_customer.ident.dv, - schemeName=self.invoice.invoice_customer.ident.type_fiscal) - # DIAN 1.9.-2023: AE28 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cbc:TaxLevelCode', - self.invoice.invoice_customer.responsability_code) - # DIAN 1.9.-2023: AE30 - self.fexml.placeholder_for( - './cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme') - # DIAN 1.9.-2023: AE31 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - self.invoice.invoice_customer.tax_scheme.code) - # DIAN 1.9.-2023: AE32 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - self.invoice.invoice_customer.tax_scheme.name) - - def set_document_response(self): - self.fexml.placeholder_for( - './cac:DocumentResponse') - self.fexml.placeholder_for( - './cac:DocumentResponse/cac:Response') - self.fexml.set_element( - './cac:DocumentResponse/cac:Response/cbc:ResponseCode', - '02') - self.fexml.set_element( - './cac:DocumentResponse/cac:Response/cbc:Description', - 'Documento validado por la DIAN') - self.set_documnent_reference() - - def set_documnent_reference(self): - self.fexml.placeholder_for( - './cac:DocumentResponse/cac:DocumentReference') - self.fexml.set_element( - './cac:DocumentResponse/cac:DocumentReference/cbc:ID', - 'FESS19566058') - self.fexml.set_element( - './cac:DocumentResponse/cac:DocumentReference/cbc:UUID', - 'f51ee529aabd19d10e39444f2f593b94d56d5885fbf433faf718d53a7e968f64bf54a6ee43c6a2df842771b54a6aae1a', - schemeName="CUFE-SHA384") - self.set_response_lines() - - def set_response_lines(self): - lines = [{ - 'LineID': '1', - 'ResponseCode': '0000', - 'Description': '0', - }] - - for line in lines: - self.fexml.set_element( - './cac:DocumentResponse/cac:LineResponse/cac:LineReference/cbc:LineID', line[ - 'LineID']) - self.fexml.set_element( - './cac:DocumentResponse/cac:LineResponse/cac:Response/cbc:ResponseCode', line[ - 'ResponseCode']) - self.fexml.set_element( - './cac:DocumentResponse/cac:LineResponse/cac:Response/cbc:Description', line[ - 'Description']) - - - - def toFachoXML(self): - return self.fexml diff --git a/facho/fe/form_xml/attached_document.py b/facho/fe/form_xml/attached_document.py index a26ebe3..ae3df9b 100644 --- a/facho/fe/form_xml/attached_document.py +++ b/facho/fe/form_xml/attached_document.py @@ -1,227 +1,14 @@ from .. import fe -from .application_response import ApplicationResponse __all__ = ['AttachedDocument'] - class AttachedDocument(): - def __init__(self, invoice, DIANInvoiceXML, id): - self.schema =\ - 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2' - self.id = id - self.invoice = invoice - self.DIANInvoiceXML = DIANInvoiceXML - self.attached_document_invoice = self.attached_document_invoice() - - def attached_document_invoice(self): - self.fexml = fe.FeXML( - 'AttachedDocument', self.schema) - - # DIAN 1.9.-2023: AE02 - self.fexml.set_element( - './cbc:UBLVersionID', 'UBL 2.1') - - # DIAN 1.9.-2023: AE03 - self.fexml.set_element( - './cbc:CustomizationID', 'Documentos adjuntos') - - # DIAN 1.9.-2023: AE04 - self.fexml.set_element( - './cbc:ProfileID', 'Factura Electrónica de Venta') - - # DIAN 1.9.-2023: AE04a - self.fexml.set_element( - './cbc:ProfileExecutionID', '1') - - # DIAN 1.9.-2023: AE04b - self.fexml.set_element( - './cbc:ID', self.id) - - # DIAN 1.9.-2023: AE05 - self.fexml.set_element( - './cbc:IssueDate', - self.invoice.invoice_issue.strftime('%Y-%m-%d')) - - # DIAN 1.9.-2023: AE06 - self.fexml.set_element( - './cbc:IssueTime', self.invoice.invoice_issue.strftime( - '%H:%M:%S-05:00')) - - # DIAN 1.9.-2023: AE08 - self.fexml.set_element( - './cbc:DocumentType', 'Contenedor de Factura Electrónica') - - # DIAN 1.9.-2023: AE08a - self.fexml.set_element( - './cbc:ParentDocumentID', self.invoice.invoice_ident) - - # DIAN 1.9.-2023: AE09 - self.set_sender_party() - - # DIAN 1.9.-2023: AE20 - self.set_receiver_party() - # DIAN 1.9.-2023: AE33 - self.set_attachment() - self.set_parent_document_line_reference() - - def set_sender_party(self): - # DIAN 1.9.-2023: AE09 - self.fexml.placeholder_for( - './cac:SenderParty') - # DIAN 1.9.-2023: AE10 - self.fexml.placeholder_for( - './cac:SenderParty/cac:PartyTaxScheme') - # DIAN 1.9.-2023: AE11 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cbc:RegistrationName', - self.invoice.invoice_supplier.name) - # DIAN 1.9.-2023: AE12 - # DIAN 1.9.-2023: AE13 - # DIAN 1.9.-2023: AE14 - # DIAN 1.9.-2023: AE15 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cbc:CompanyID', - self.invoice.invoice_supplier.ident, - schemeAgencyID='195', - schemeID=self.invoice.invoice_supplier.ident.dv, - schemeName=self.invoice.invoice_supplier.ident.type_fiscal) - - # DIAN 1.9.-2023: AE16 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cbc:TaxLevelCode', - self.invoice.invoice_supplier.responsability_code) - - # DIAN 1.9.-2023: AE18 - self.fexml.placeholder_for( - './cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme') - - # DIAN 1.9.-2023: AE19 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - self.invoice.invoice_supplier.tax_scheme.code) - - # DIAN 1.9.-2023: AE20 - self.fexml.set_element( - './cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - self.invoice.invoice_supplier.tax_scheme.name) - - def set_receiver_party(self): - # DIAN 1.9.-2023: AE21 - self.fexml.placeholder_for( - './cac:ReceiverParty') - # DIAN 1.9.-2023: AE22 - self.fexml.placeholder_for( - './cac:ReceiverParty/cac:PartyTaxScheme') - # DIAN 1.9.-2023: AE23 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cbc:RegistrationName', - self.invoice.invoice_customer.name) - # DIAN 1.9.-2023: AE24 - # DIAN 1.9.-2023: AE25 - # DIAN 1.9.-2023: AE26 - # DIAN 1.9.-2023: AE27 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cbc:CompanyID', - self.invoice.invoice_customer.ident, - schemeAgencyID='195', - schemeID=self.invoice.invoice_customer.ident.dv, - schemeName=self.invoice.invoice_customer.ident.type_fiscal) - # DIAN 1.9.-2023: AE28 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cbc:TaxLevelCode', - self.invoice.invoice_customer.responsability_code) - # DIAN 1.9.-2023: AE30 - self.fexml.placeholder_for( - './cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme') - # DIAN 1.9.-2023: AE31 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - self.invoice.invoice_customer.tax_scheme.code) - # DIAN 1.9.-2023: AE32 - self.fexml.set_element( - './cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - self.invoice.invoice_customer.tax_scheme.name) - - def set_attachment(self): - # DIAN 1.9.-2023: AE33 - self.fexml.placeholder_for( - './cac:Attachment') - # DIAN 1.9.-2023: AE34 - self.fexml.placeholder_for( - './cac:Attachment/cac:ExternalReference') - # DIAN 1.9.-2023: AE35 - self.fexml.set_element( - './cac:Attachment/cac:ExternalReference/cbc:MimeCode', - 'text/xml') - # DIAN 1.9.-2023: AE36 - self.fexml.set_element( - './cac:Attachment/cac:ExternalReference/cbc:EncodingCode', - 'UTF-8') - # DIAN 1.9.-2023: AE37 - self.fexml.set_element( - './cac:Attachment/cac:ExternalReference/cbc:Description', - self._build_attachment(self.DIANInvoiceXML) - ) - - def set_parent_document_line_reference(self): - self.fexml.placeholder_for( - './cac:ParentDocumentLineReference') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cbc:LineID', 1) - self.fexml.placeholder_for( - './cac:ParentDocumentLineReference/cac:DocumentReference') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cbc:ID', - '1234') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cbc:UUID', - '1234', - schemeName="CUFE-SHA384") - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cbc:IssueDate', - '2024-11-28') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cbc:DocumentType', - 'ApplicationResponse') - self.fexml.placeholder_for( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:MimeCode', - 'text/xml') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:EncodingCode', - 'UTF-8') - - application_response = ApplicationResponse( - self.invoice).toFachoXML() - - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:Description', - self._build_attachment(application_response)) - self.fexml.placeholder_for( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidatorID', - 'Unidad Especial Dirección de Impuestos y Aduanas Nacionales') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidationResultCode', - '02') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidationDate', - '2024-11-28') - self.fexml.set_element( - './cac:ParentDocumentLineReference/cac:DocumentReference/cac:ResultOfVerification/cbc:ValidationTime', - '10:35:11-05:00') - - def _build_attachment(self, DIANInvoiceXML): - document = ( - '' - ) + DIANInvoiceXML.tostring() - attachment = "".format( - document) - - return attachment + def __init__(self, id): + schema = 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2' + self.fexml = fe.FeXML('AttachedDocument', schema) + self.fexml.set_element('./cbc:ID', id) def toFachoXML(self): return self.fexml + diff --git a/facho/fe/form_xml/credit_note.py b/facho/fe/form_xml/credit_note.py index c6ad5bb..89820c0 100644 --- a/facho/fe/form_xml/credit_note.py +++ b/facho/fe/form_xml/credit_note.py @@ -1,10 +1,9 @@ -# from .. import fe -# from ..form import * +from .. import fe +from ..form import * from .invoice import DIANInvoiceXML __all__ = ['DIANCreditNoteXML'] - class DIANCreditNoteXML(DIANInvoiceXML): """ DianInvoiceXML mapea objeto form.Invoice a XML segun diff --git a/facho/fe/form_xml/debit_note.py b/facho/fe/form_xml/debit_note.py index c589124..1ba42b6 100644 --- a/facho/fe/form_xml/debit_note.py +++ b/facho/fe/form_xml/debit_note.py @@ -1,10 +1,9 @@ -# from .. import fe -# from ..form import * +from .. import fe +from ..form import * from .invoice import DIANInvoiceXML __all__ = ['DIANDebitNoteXML'] - class DIANDebitNoteXML(DIANInvoiceXML): """ DianInvoiceXML mapea objeto form.Invoice a XML segun @@ -20,24 +19,19 @@ class DIANDebitNoteXML(DIANInvoiceXML): def tag_document_concilied(fexml): return 'Debited' - # DIAN 1.7.-2020: DAU03 + #DIAN 1.7.-2020: DAU03 def set_legal_monetary(fexml, invoice): - fexml.set_element_amount( - './cac:RequestedMonetaryTotal/cbc:LineExtensionAmount', - invoice.invoice_legal_monetary_total.line_extension_amount) + fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:LineExtensionAmount', + invoice.invoice_legal_monetary_total.line_extension_amount) - fexml.set_element_amount( - './cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount', - invoice.invoice_legal_monetary_total.tax_exclusive_amount) + fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxExclusiveAmount', + invoice.invoice_legal_monetary_total.tax_exclusive_amount) - fexml.set_element_amount( - './cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount', - invoice.invoice_legal_monetary_total.tax_inclusive_amount) + fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:TaxInclusiveAmount', + invoice.invoice_legal_monetary_total.tax_inclusive_amount) - fexml.set_element_amount( - './cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount', - invoice.invoice_legal_monetary_total.charge_total_amount) + fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:ChargeTotalAmount', + invoice.invoice_legal_monetary_total.charge_total_amount) - fexml.set_element_amount( - './cac:RequestedMonetaryTotal/cbc:PayableAmount', - invoice.invoice_legal_monetary_total.payable_amount) + fexml.set_element_amount('./cac:RequestedMonetaryTotal/cbc:PayableAmount', + invoice.invoice_legal_monetary_total.payable_amount) diff --git a/facho/fe/form_xml/invoice.py b/facho/fe/form_xml/invoice.py index cbf541c..42b277a 100644 --- a/facho/fe/form_xml/invoice.py +++ b/facho/fe/form_xml/invoice.py @@ -1,7 +1,5 @@ from .. import fe from ..form import * -from collections import defaultdict -from .attached_document import AttachedDocument __all__ = ['DIANInvoiceXML'] @@ -11,478 +9,355 @@ class DIANInvoiceXML(fe.FeXML): lo indicado para la facturacion electronica. """ - 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') self.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts: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 ublextension = self.fragment('./ext:UBLExtensions/ext:UBLExtension', append=True) extcontent = ublextension.find_or_create_element('/ext:UBLExtension/ext:ExtensionContent') - self.attach_invoice = self.attach_invoice(invoice) - - # self.attach_document = self.attached_document_invoice(attach_invoice) - - def attach_invoice(fexml, invoice): - """adiciona etiquetas a FEXML y retorna FEXML - en caso de fallar validacion retorna None""" - - fexml.placeholder_for('./ext:UBLExtensions') - fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1') - fexml.set_element( - './cbc:CustomizationID', invoice.invoice_operation_type) - fexml.placeholder_for('./cbc:ProfileID') - fexml.placeholder_for('./cbc:ProfileExecutionID') - fexml.set_element('./cbc:ID', invoice.invoice_ident) - fexml.placeholder_for('./cbc:UUID') - fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) - # DIAN 1.7.-2020: FAD10 - fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) - fexml.set_element( - './cbc:%sTypeCode' % (fexml.tag_document()), - invoice.invoice_type_code, - listAgencyID='195', - listAgencyName='No matching global declaration available for the validation root', - listURI='http://www.dian.gov.co') - fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') - fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) - if fexml.tag_document() == 'Invoice': - fexml.set_element('./cac:%sPeriod/cbc:StartDate' % ( - fexml.tag_document()), - invoice.invoice_period_start.strftime( - '%Y-%m-%d')) - fexml.set_element('./cac:%sPeriod/cbc:EndDate' % ( - fexml.tag_document()), - invoice.invoice_period_end.strftime('%Y-%m-%d')) - fexml.set_billing_reference(invoice) - fexml.customize(invoice) - fexml.set_supplier(invoice) - fexml.set_customer(invoice) - fexml.set_payment_mean(invoice) - fexml.set_invoice_totals(invoice) - fexml.set_legal_monetary(invoice) - fexml.set_invoice_lines(invoice) - fexml.set_allowance_charge(invoice) - - return fexml - - def attached_document_invoice(fexml, invoice): - attach_invoice = fexml.attach_invoice(invoice) - attached_document = AttachedDocument( - invoice, '123', attach_invoice) - - return attached_document + self.attach_invoice(invoice) def set_supplier(fexml, invoice): fexml.placeholder_for('./cac:AccountingSupplierParty') - # DIAN 1.7.-2020: CAJ02 - # DIAN 1.7.-2020: FAJ02 - fexml.set_element( - './cac:AccountingSupplierParty/cbc:AdditionalAccountID', - invoice.invoice_supplier.organization_code) + #DIAN 1.7.-2020: CAJ02 + #DIAN 1.7.-2020: FAJ02 + fexml.set_element('./cac:AccountingSupplierParty/cbc:AdditionalAccountID', + invoice.invoice_supplier.organization_code) - # DIAN 1.7.-2020: CAJ06 - # DIAN 1.7.-2020: FAJ06 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name', - invoice.invoice_supplier.name) + #DIAN 1.7.-2020: CAJ06 + #DIAN 1.7.-2020: FAJ06 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name', + invoice.invoice_supplier.name) - # DIAN 1.7.-2020: CAJ07, CAJ08 - # DIAN 1.7.-2020: FAJ07 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') + #DIAN 1.7.-2020: CAJ07, CAJ08 + #DIAN 1.7.-2020: FAJ07 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') - # DIAN 1.7.-2020: FAJ08 - # DIAN 1.7.-2020: CAJ09 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', - invoice.invoice_supplier.address.city.code) - # DIAN 1.7.-2020: FAJ09 - # DIAN 1.7.-2020: CAJ10 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', - invoice.invoice_supplier.address.city.name) - # DIAN 1.7.-2020: FAJ11 - # DIAN 1.7.-2020: CAJ11 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', - invoice.invoice_supplier.address.countrysubentity.name) + #DIAN 1.7.-2020: FAJ08 + #DIAN 1.7.-2020: CAJ09 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', + invoice.invoice_supplier.address.city.code) + #DIAN 1.7.-2020: FAJ09 + #DIAN 1.7.-2020: CAJ10 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', + invoice.invoice_supplier.address.city.name) + #DIAN 1.7.-2020: FAJ11 + #DIAN 1.7.-2020: CAJ11 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', + invoice.invoice_supplier.address.countrysubentity.name) - # DIAN 1.7.-2020: FAJ12 - # DIAN 1.7.-2020: CAJ12 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', - invoice.invoice_supplier.address.countrysubentity.code) - # DIAN 1.7.-2020: FAJ14 - # DIAN 1.7.-2020: CAJ13, CAJ14 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', - invoice.invoice_supplier.address.street) + #DIAN 1.7.-2020: FAJ12 + #DIAN 1.7.-2020: CAJ12 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', + invoice.invoice_supplier.address.countrysubentity.code) + #DIAN 1.7.-2020: FAJ14 + #DIAN 1.7.-2020: CAJ13, CAJ14 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', + invoice.invoice_supplier.address.street) - # DIAN 1.7.-2020: FAJ16 - # DIAN 1.7.-2020: CAJ16, CAJ16 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', - invoice.invoice_supplier.address.country.code) + #DIAN 1.7.-2020: FAJ16 + #DIAN 1.7.-2020: CAJ16, CAJ16 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', + invoice.invoice_supplier.address.country.code) - # DIAN 1.7.-2020: FAJ17 - # DIAN 1.7.-2020: CAJ17 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', - invoice.invoice_supplier.address.country.name, - # DIAN 1.7.-2020: FAJ18 - languageID='es') + #DIAN 1.7.-2020: FAJ17 + #DIAN 1.7.-2020: CAJ17 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', + invoice.invoice_supplier.address.country.name, + #DIAN 1.7.-2020: FAJ18 + languageID = 'es') supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() - supplier_company_id_attrs.update({ - 'schemeID': invoice.invoice_supplier.ident.dv, - 'schemeName': invoice.invoice_supplier.ident.type_fiscal - }) + supplier_company_id_attrs.update({'schemeID': invoice.invoice_supplier.ident.dv, + 'schemeName': invoice.invoice_supplier.ident.type_fiscal}) - # DIAN 1.7.-2020: FAJ19 - # DIAN 1.7.-2020: CAJ19 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme') + #DIAN 1.7.-2020: FAJ19 + #DIAN 1.7.-2020: CAJ19 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme') - # DIAN 1.7.-2020: FAJ20 - # DIAN 1.7.-2020: CAJ20 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', - invoice.invoice_supplier.legal_name) + #DIAN 1.7.-2020: FAJ20 + #DIAN 1.7.-2020: CAJ20 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', + invoice.invoice_supplier.legal_name) - # DIAN 1.7.-2020: FAJ21 - # DIAN 1.7.-2020: CAJ21 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', - invoice.invoice_supplier.ident, - # DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25 - **supplier_company_id_attrs) - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', - # DIAN 1.7.-2020: FAJ26 - # DIAN 1.7.-2020: CAJ26 - invoice.invoice_supplier.responsability_code, - # DIAN 1.7.-2020: FAJ27 - # DIAN 1.7.-2020: CAJ27 - listName=invoice.invoice_supplier.responsability_regime_code) - # DIAN 1.7.-2020: FAJ28 - # DIAN 1.7.-2020: CAJ28 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') + #DIAN 1.7.-2020: FAJ21 + #DIAN 1.7.-2020: CAJ21 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', + invoice.invoice_supplier.ident, + #DIAN 1.7.-2020: FAJ22,FAJ23,FAJ24,FAJ25 + **supplier_company_id_attrs) + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', + #DIAN 1.7.-2020: FAJ26 + #DIAN 1.7.-2020: CAJ26 + invoice.invoice_supplier.responsability_code, + #DIAN 1.7.-2020: FAJ27 + #DIAN 1.7.-2020: CAJ27 + listName=invoice.invoice_supplier.responsability_regime_code) + #DIAN 1.7.-2020: FAJ28 + #DIAN 1.7.-2020: CAJ28 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') - # DIAN 1.7.-2020: FAJ29 - # DIAN 1.7.-2020: CAJ29 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', - invoice.invoice_supplier.address.city.code) + #DIAN 1.7.-2020: FAJ29 + #DIAN 1.7.-2020: CAJ29 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', + invoice.invoice_supplier.address.city.code) - # DIAN 1.7.-2020: FAJ30 - # DIAN 1.7.-2020: CAJ30 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', invoice.invoice_supplier.address.city.name) + #DIAN 1.7.-2020: FAJ30 + #DIAN 1.7.-2020: CAJ30 + 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: CAJ31 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', - invoice.invoice_supplier.address.countrysubentity.name) + #DIAN 1.7.-2020: FAJ31 + #DIAN 1.7.-2020: CAJ31 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', + invoice.invoice_supplier.address.countrysubentity.name) - # DIAN 1.7.-2020: FAJ32 - # DIAN 1.7.-2020: CAJ32 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', - invoice.invoice_supplier.address.countrysubentity.code) + #DIAN 1.7.-2020: FAJ32 + #DIAN 1.7.-2020: CAJ32 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', + invoice.invoice_supplier.address.countrysubentity.code) - # DIAN 1.7.-2020: FAJ33,FAJ34 - # DIAN 1.7.-2020: CAJ33,CAJ34 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', - invoice.invoice_supplier.address.street) + #DIAN 1.7.-2020: FAJ33,FAJ34 + #DIAN 1.7.-2020: CAJ33,CAJ34 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', + invoice.invoice_supplier.address.street) - # DIAN 1.7.-2020: FAJ35,FAJ36 - # DIAN 1.7.-2020: CAJ35,CAJ36 + #DIAN 1.7.-2020: FAJ35,FAJ36 + #DIAN 1.7.-2020: CAJ35,CAJ36 fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', invoice.invoice_supplier.address.country.code) - # DIAN 1.7.-2020: FAJ37,FAJ38 - # DIAN 1.7.-2020: CAJ37,CAJ38 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', - invoice.invoice_supplier.address.country.name, - languageID='es') + #DIAN 1.7.-2020: FAJ37,FAJ38 + #DIAN 1.7.-2020: CAJ37,CAJ38 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:Name', + invoice.invoice_supplier.address.country.name, + languageID='es') - # DIAN 1.7.-2020: FAJ39 - # DIAN 1.7.-2020: CAJ39 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') + #DIAN 1.7.-2020: FAJ39 + #DIAN 1.7.-2020: CAJ39 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') - # DIAN 1.7.-2020: CAJ40 - # DIAN 1.7.-2020: FAJ40 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - invoice.invoice_customer.tax_scheme.code) + #DIAN 1.7.-2020: CAJ40 + #DIAN 1.7.-2020: FAJ40 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', + invoice.invoice_customer.tax_scheme.code) - # DIAN 1.7.-2020: CAJ41 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - invoice.invoice_customer.tax_scheme.name) - # DIAN 1.7.-2020: FAJ42 - # DIAN 1.7.-2020: CAJ42 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity') + #DIAN 1.7.-2020: CAJ41 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', + invoice.invoice_customer.tax_scheme.name) - # DIAN 1.7.-2020: FAJ43 - # DIAN 1.7.-2020: CAJ43 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', - invoice.invoice_supplier.legal_name) + #DIAN 1.7.-2020: FAJ42 + #DIAN 1.7.-2020: CAJ42 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity') - # DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48 - # DIAN 1.7.-2020: CAJ44,CAJ45,CAJ46,CAJ47,CAJ48 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', - invoice.invoice_supplier.ident, - **supplier_company_id_attrs) + #DIAN 1.7.-2020: FAJ43 + #DIAN 1.7.-2020: CAJ43 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', + invoice.invoice_supplier.legal_name) - # DIAN 1.7.-2020: FAJ49 - # DIAN 1.7.-2020: CAJ49 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme') + #DIAN 1.7.-2020: FAJ44,FAJ45,FAJ46,FAJ47,FAJ48 + #DIAN 1.7.-2020: CAJ44,CAJ45,CAJ46,CAJ47,CAJ48 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', + invoice.invoice_supplier.ident, + **supplier_company_id_attrs) - # DIAN 1.7.-2020: FAJ50 - # DIAN 1.7.-2020: CAJ50 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID', - invoice.invoice_ident_prefix) + #DIAN 1.7.-2020: FAJ49 + #DIAN 1.7.-2020: CAJ49 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme') - # DIAN 1.7.-2020: CAJ67 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:Contact') + #DIAN 1.7.-2020: FAJ50 + #DIAN 1.7.-2020: CAJ50 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PartyLegalEntity/cac:CorporateRegistrationScheme/cbc:ID', + invoice.invoice_ident_prefix) + + #DIAN 1.7.-2020: CAJ67 + fexml.placeholder_for('./cac:AccountingSupplierParty/cac:Party/cac:Contact') + + #DIAN 1.7.-2020: FAJ71 + #DIAN 1.7.-2020: CAJ71 + fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail', + invoice.invoice_supplier.email) - # DIAN 1.7.-2020: FAJ71 - # DIAN 1.7.-2020: CAJ71 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:Contact/cbc:ElectronicMail', - invoice.invoice_supplier.email) def set_customer(fexml, invoice): fexml.placeholder_for('./cac:AccountingCustomerParty') - fexml.set_element( - './cac:AccountingCustomerParty/cbc:AdditionalAccountID', - invoice.invoice_customer.organization_code) - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID', - invoice.invoice_customer.ident) - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name', - invoice.invoice_customer.name) + fexml.set_element('./cac:AccountingCustomerParty/cbc:AdditionalAccountID', + invoice.invoice_customer.organization_code) + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID', + invoice.invoice_customer.ident) + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc: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() - # DIAN 1.7.-2020: FAK25 - # DIAN 1.7.-2020: CAK25 - customer_company_id_attrs.update({ - 'schemeID': invoice.invoice_customer.ident.dv, - 'schemeName': invoice.invoice_customer.ident.type_fiscal}) + #DIAN 1.7.-2020: FAK25 + #DIAN 1.7.-2020: CAK25 + customer_company_id_attrs.update({'schemeID': invoice.invoice_customer.ident.dv, + 'schemeName': invoice.invoice_customer.ident.type_fiscal}) - # DIAN 1.7.-2020: FAK07 - # DIAN 1.7.-2020: CAK07 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address') + #DIAN 1.7.-2020: FAK07 + #DIAN 1.7.-2020: CAK07 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address') - # DIAN 1.7.-2020: FAK08 - # DIAN 1.7.-2020: CAK08 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', - invoice.invoice_customer.address.city.code) - # DIAN 1.7.-2020: FAK09 - # DIAN 1.7.-2020: CAK09 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', invoice.invoice_customer.address.city.name) + #DIAN 1.7.-2020: FAK08 + #DIAN 1.7.-2020: CAK08 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', + invoice.invoice_customer.address.city.code) + #DIAN 1.7.-2020: FAK09 + #DIAN 1.7.-2020: CAK09 + 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: CAK11 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', - invoice.invoice_customer.address.countrysubentity.name) + #DIAN 1.7.-2020: FAK11 + #DIAN 1.7.-2020: CAK11 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', + invoice.invoice_customer.address.countrysubentity.name) - # DIAN 1.7.-2020: FAK12 - # DIAN 1.7.-2020: CAK12 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', - invoice.invoice_customer.address.countrysubentity.code) + #DIAN 1.7.-2020: FAK12 + #DIAN 1.7.-2020: CAK12 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', + invoice.invoice_customer.address.countrysubentity.code) - # DIAN 1.7.-2020: CAK13, CAK14 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', - invoice.invoice_customer.address.street) + #DIAN 1.7.-2020: CAK13, CAK14 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', + invoice.invoice_customer.address.street) - # DIAN 1.7.-2020: CAK16 - # DIAN 1.7.-2020: FAK16 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', - invoice.invoice_customer.address.country.code) + #DIAN 1.7.-2020: CAK16 + #DIAN 1.7.-2020: FAK16 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', + invoice.invoice_customer.address.country.code) - # DIAN 1.7.-2020: FAK17 - # DIAN 1.7.-2020: CAK17 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', - invoice.invoice_customer.address.country.name, - # DIAN 1.7.-2020: FAK18 - # DIAN 1.7.-2020: CAK18 - languageID='es') + #DIAN 1.7.-2020: FAK17 + #DIAN 1.7.-2020: CAK17 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', + invoice.invoice_customer.address.country.name, + #DIAN 1.7.-2020: FAK18 + #DIAN 1.7.-2020: CAK18 + languageID='es') - # DIAN 1.7.-2020: FAK17,FAK19 - # DIAN 1.7.-2020: CAK19 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme') + #DIAN 1.7.-2020: FAK17,FAK19 + #DIAN 1.7.-2020: CAK19 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme') - # DIAN 1.7.-2020: FAK17,FAK20 - # DIAN 1.7.-2020: CAK20 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', - invoice.invoice_customer.legal_name) + #DIAN 1.7.-2020: FAK17,FAK20 + #DIAN 1.7.-2020: CAK20 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', + invoice.invoice_customer.legal_name) - # DIAN 1.7.-2020: CAK21 - # DIAN 1.7.-2020: FAK21 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', - invoice.invoice_customer.ident, - # DIAN 1.7.-2020: CAK22, CAK23, CAK24, CAK25 - **customer_company_id_attrs) + #DIAN 1.7.-2020: CAK21 + #DIAN 1.7.-2020: FAK21 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', + invoice.invoice_customer.ident, + #DIAN 1.7.-2020: CAK22, CAK23, CAK24, CAK25 + **customer_company_id_attrs) - # DIAN 1.7.-2020: CAK26 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', - # DIAN 1.7.-2020: FAK26 - invoice.invoice_customer.responsability_code, - # DIAN 1.7.-2020: FAK27 - # DIAN 1.7.-2020: CAK27 - listName=invoice.invoice_customer.responsability_regime_code) + #DIAN 1.7.-2020: CAK26 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', + #DIAN 1.7.-2020: FAK26 + invoice.invoice_customer.responsability_code, + #DIAN 1.7.-2020: FAK27 + #DIAN 1.7.-2020: CAK27 + listName=invoice.invoice_customer.responsability_regime_code) - # DIAN 1.7.-2020: FAK28 - # DIAN 1.7.-2020: CAK28 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') + #DIAN 1.7.-2020: FAK28 + #DIAN 1.7.-2020: CAK28 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress') - # DIAN 1.7.-2020: FAK29 - # DIAN 1.7.-2020: CAK29 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', - invoice.invoice_customer.address.city.code) + #DIAN 1.7.-2020: FAK29 + #DIAN 1.7.-2020: CAK29 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:ID', + invoice.invoice_customer.address.city.code) - # DIAN 1.7.-2020: FAK30 - # DIAN 1.7.-2020: CAK30 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', - invoice.invoice_customer.address.city.name) + #DIAN 1.7.-2020: FAK30 + #DIAN 1.7.-2020: CAK30 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CityName', + invoice.invoice_customer.address.city.name) - # DIAN 1.7.-2020: FAK31 - # DIAN 1.7.-2020: CAK31 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', - invoice.invoice_customer.address.countrysubentity.name) + #DIAN 1.7.-2020: FAK31 + #DIAN 1.7.-2020: CAK31 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentity', + invoice.invoice_customer.address.countrysubentity.name) - # DIAN 1.7.-2020: FAK32 - # DIAN 1.7.-2020: CAK32 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', - invoice.invoice_customer.address.countrysubentity.code) + #DIAN 1.7.-2020: FAK32 + #DIAN 1.7.-2020: CAK32 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cbc:CountrySubentityCode', + invoice.invoice_customer.address.countrysubentity.code) - # DIAN 1.7.-2020: FAK33 - # DIAN 1.7.-2020: CAK33 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine') + #DIAN 1.7.-2020: FAK33 + #DIAN 1.7.-2020: CAK33 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine') - # DIAN 1.7.-2020: FAK34 - # DIAN 1.7.-2020: CAK34 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', - invoice.invoice_customer.address.street) + #DIAN 1.7.-2020: FAK34 + #DIAN 1.7.-2020: CAK34 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:AddressLine/cbc:Line', + invoice.invoice_customer.address.street) - # DIAN 1.7.-2020: CAK35 - # DIAN 1.7.-2020: FAK35 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country') + #DIAN 1.7.-2020: CAK35 + #DIAN 1.7.-2020: FAK35 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country') - # DIAN 1.7.-2020: CAK36 - # DIAN 1.7.-2020: FAK36 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', - invoice.invoice_customer.address.country.code) + #DIAN 1.7.-2020: CAK36 + #DIAN 1.7.-2020: FAK36 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', + invoice.invoice_customer.address.country.code) - # DIAN 1.7.-2020: CAK37 - # 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) + #DIAN 1.7.-2020: CAK37 + #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) - # DIAN 1.7.-2020: FAK38 - # DIAN 1.7.-2020: CAK38 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', - invoice.invoice_customer.address.country.code, - languageID='es') + #DIAN 1.7.-2020: FAK38 + #DIAN 1.7.-2020: CAK38 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:RegistrationAddress/cac:Country/cbc:IdentificationCode', + invoice.invoice_customer.address.country.code, + languageID='es') - # DIAN 1.7.-2020: CAK39 - # DIAN 1.7.-2020: FAK39 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') + #DIAN 1.7.-2020: CAK39 + #DIAN 1.7.-2020: FAK39 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') - # DIAN 1.7.-2020: CAK40 Machete Construir Validación - # DIAN 1.7.-2020: FAK40 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - invoice.invoice_customer.tax_scheme.code) + #DIAN 1.7.-2020: CAK40 Machete Construir Validación + #DIAN 1.7.-2020: FAK40 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', + invoice.invoice_customer.tax_scheme.code) - # DIAN 1.7.-2020: FAK41 - # DIAN 1.7.-2020: CAK41 Machete Construir Validación - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - invoice.invoice_customer.tax_scheme.name) + #DIAN 1.7.-2020: FAK41 + #DIAN 1.7.-2020: CAK41 Machete Construir Validación + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', + invoice.invoice_customer.tax_scheme.name) + #DIAN 1.7.-2020: FAK42 + #DIAN 1.7.-2020: CAK42 + fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity') - # DIAN 1.7.-2020: FAK42 - # DIAN 1.7.-2020: CAK42 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity') + #DIAN 1.7.-2020: FAK43 + #DIAN 1.7.-2020: CAK43 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', + invoice.invoice_customer.legal_name) - # DIAN 1.7.-2020: FAK43 - # DIAN 1.7.-2020: CAK43 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', - invoice.invoice_customer.legal_name) + #DIAN 1.7.-2020: CAK44 + #DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', + invoice.invoice_customer.ident, + **customer_company_id_attrs) - # DIAN 1.7.-2020: CAK44 - # DIAN 1.7.-2020: FAK44,FAK45,FAK46,FAK47,FAK48 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyLegalEntity/cbc:CompanyID', - invoice.invoice_customer.ident, - **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: CAK51, CAK55 + fexml.set_element('./cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail', + invoice.invoice_customer.email) - # DIAN 1.7.-2020: FAK55 - # DIAN 1.7.-2020: CAK51, CAK55 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:Contact/cbc:ElectronicMail', - invoice.invoice_customer.email) def set_payment_mean(fexml, invoice): payment_mean = invoice.invoice_payment_mean - fexml.set_element( - './cac:PaymentMeans/cbc:ID', payment_mean.id) - fexml.set_element( - './cac:PaymentMeans/cbc:PaymentMeansCode', 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) + fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id) + fexml.set_element('./cac:PaymentMeans/cbc:PaymentMeansCode', 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): if not isinstance(amount, Amount): @@ -546,7 +421,6 @@ class DIANInvoiceXML(fe.FeXML): def set_invoice_totals(fexml, invoice): tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) - withholding_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) percent_for = defaultdict(lambda: None) #requeridos para CUFE @@ -559,35 +433,23 @@ class DIANInvoiceXML(fe.FeXML): #tax_amount_for['03']['taxable_amount'] += 0.0 total_tax_amount = Amount(0.0) - total_withholding_amount = Amount(0.0) for invoice_line in invoice.invoice_lines: for subtotal in invoice_line.tax.subtotals: if subtotal.scheme is not None: tax_amount_for[subtotal.scheme.code]['tax_amount'] += subtotal.tax_amount tax_amount_for[subtotal.scheme.code]['taxable_amount'] += invoice_line.taxable_amount - tax_amount_for[subtotal.scheme.code]['name'] = subtotal.scheme.name # MACHETE ojo InvoiceLine.tax pasar a Invoice percent_for[subtotal.scheme.code] = subtotal.percent total_tax_amount += subtotal.tax_amount - for subtotal_withholding in invoice_line.withholding.subtotals: - if subtotal_withholding.scheme is not None: - withholding_amount_for[subtotal_withholding.scheme.code]['tax_amount'] += subtotal_withholding.tax_amount - withholding_amount_for[subtotal_withholding.scheme.code]['taxable_amount'] += invoice_line.withholding_taxable_amount - - # MACHETE ojo InvoiceLine.tax pasar a Invoice - - percent_for[subtotal_withholding.scheme.code] = subtotal_withholding.percent - - total_withholding_amount += subtotal_withholding.tax_amount - if total_tax_amount != Amount(0.0): fexml.placeholder_for('./cac:TaxTotal') fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', total_tax_amount) + for index, item in enumerate(tax_amount_for.items()): cod_impuesto, amount_of = item @@ -624,52 +486,8 @@ class DIANInvoiceXML(fe.FeXML): line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', cod_impuesto) line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', - amount_of['name']) - - for index, item in enumerate(withholding_amount_for.items()): - cod_impuesto, amount_of = item - next_append = index > 0 - - # DIAN 1.7.-2020: FAS01 - line = fexml.fragment( - './cac:WithholdingTaxTotal', append=next_append) - # DIAN 1.7.-2020: FAU06 - tax_amount = amount_of['tax_amount'] - fexml.set_element_amount_for( - line, - '/cac:WithholdingTaxTotal/cbc:TaxAmount', - tax_amount) - - # DIAN 1.7.-2020: FAS05 - fexml.set_element_amount_for( - line, - '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - amount_of['taxable_amount']) - - # DIAN 1.7.-2020: FAU06 - fexml.set_element_amount_for( - line, - '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', - amount_of['tax_amount']) - - # DIAN 1.7.-2020: FAS07 - if percent_for[cod_impuesto]: - line.set_element( - '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:Percent', - percent_for[cod_impuesto]) - - if percent_for[cod_impuesto]: - line.set_element( - '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', - percent_for[cod_impuesto]) - - line.set_element( - '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', - cod_impuesto) - line.set_element( - '/cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', - 'ReteRenta') - + 'IVA') + # abstract method def tag_document(fexml): return 'Invoice' @@ -679,141 +497,115 @@ class DIANInvoiceXML(fe.FeXML): return 'Invoiced' def set_invoice_line_tax(fexml, line, invoice_line): - fexml.set_element_amount_for( - line, - './cac:TaxTotal/cbc:TaxAmount', - invoice_line.tax_amount) - - # DIAN 1.7.-2020: FAX05 - fexml.set_element_amount_for( - line, - './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - invoice_line.taxable_amount) + fexml.set_element_amount_for(line, + './cac:TaxTotal/cbc:TaxAmount', + invoice_line.tax_amount) + #DIAN 1.7.-2020: FAX05 + fexml.set_element_amount_for(line, + './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', + invoice_line.taxable_amount) for subtotal in invoice_line.tax.subtotals: - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', - subtotal.tax_amount, - currencyID='COP') + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', subtotal.tax_amount, currencyID='COP') if subtotal.percent is not None: - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2)) + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2)) if subtotal.scheme is not None: - # DIAN 1.7.-2020: FAX15 - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) - - def set_invoice_line_withholding(fexml, line, invoice_line): - fexml.set_element_amount_for( - line, - './cac:WithholdingTaxTotal/cbc:TaxAmount', - invoice_line.withholding_amount) - - # DIAN 1.7.-2020: FAX05 - fexml.set_element_amount_for( - line, - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - invoice_line.withholding_taxable_amount) - - for subtotal in invoice_line.withholding.subtotals: - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', - subtotal.tax_amount, - currencyID='COP') - - if subtotal.percent is not None: - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', '%0.2f' % round(subtotal.percent, 2)) - - if subtotal.scheme is not None: - # DIAN 1.7.-2020: FAX15 - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) - + #DIAN 1.7.-2020: FAX15 + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', subtotal.scheme.code) + line.set_element('./cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', subtotal.scheme.name) + def set_invoice_lines(fexml, invoice): next_append = False for index, invoice_line in enumerate(invoice.invoice_lines): - line = fexml.fragment( - './cac:%sLine' % (fexml.tag_document()), - append=next_append) + line = fexml.fragment('./cac:%sLine' % (fexml.tag_document()), append=next_append) next_append = True line.set_element('./cbc:ID', index + 1) - line.set_element( - './cbc:%sQuantity' % ( - fexml.tag_document_concilied()), - invoice_line.quantity, - unitCode='NAR') - fexml.set_element_amount_for( - line, - './cbc:LineExtensionAmount', - invoice_line.total_amount) + line.set_element('./cbc:%sQuantity' % (fexml.tag_document_concilied()), invoice_line.quantity, unitCode = 'NAR') + fexml.set_element_amount_for(line, + './cbc:LineExtensionAmount', + invoice_line.total_amount) - if not isinstance( - invoice_line.tax, TaxTotalOmit): + if not isinstance(invoice_line.tax, TaxTotalOmit): fexml.set_invoice_line_tax(line, invoice_line) - if not isinstance( - invoice_line.withholding, WithholdingTaxTotalOmit): - fexml.set_invoice_line_withholding(line, invoice_line) + line.set_element('./cac:Item/cbc:Description', invoice_line.item.description) - line.set_element( - './cac:Item/cbc:Description', invoice_line.item.description) + line.set_element('./cac:Item/cac:StandardItemIdentification/cbc:ID', + invoice_line.item.id, + schemeID=invoice_line.item.scheme_id, + schemeName=invoice_line.item.scheme_name, + schemeAgencyID=invoice_line.item.scheme_agency_id) - line.set_element( - './cac:Item/cac:StandardItemIdentification/cbc:ID', - invoice_line.item.id, - schemeID=invoice_line.item.scheme_id, - schemeName=invoice_line.item.scheme_name, - schemeAgencyID=invoice_line.item.scheme_agency_id) - - line.set_element( - './cac:Price/cbc:PriceAmount', - invoice_line.price.amount, - currencyID=invoice_line.price.amount.currency.code) - - # DIAN 1.7.-2020: FBB04 - line.set_element( - './cac:Price/cbc:BaseQuantity', - invoice_line.price.quantity, - unitCode=invoice_line.quantity.code) + line.set_element('./cac:Price/cbc:PriceAmount', invoice_line.price.amount, currencyID=invoice_line.price.amount.currency.code) + #DIAN 1.7.-2020: FBB04 + line.set_element('./cac:Price/cbc:BaseQuantity', + invoice_line.price.quantity, + unitCode=invoice_line.quantity.code) for idx, charge in enumerate(invoice_line.allowance_charge): next_append_charge = idx > 0 - fexml.append_allowance_charge( - line, index + 1, charge, append=next_append_charge) - + fexml.append_allowance_charge(line, index + 1, charge, append=next_append_charge) + def set_allowance_charge(fexml, invoice): for idx, charge in enumerate(invoice.invoice_allowance_charge): next_append = idx > 0 - fexml.append_allowance_charge( - fexml, idx + 1, charge, append=next_append) + fexml.append_allowance_charge(fexml, idx + 1, charge, append=next_append) def append_allowance_charge(fexml, parent, idx, charge, append=False): - line = parent.fragment('./cac:AllowanceCharge', append=append) - # DIAN 1.7.-2020: FAQ02 - line.set_element('./cbc:ID', idx) - # DIAN 1.7.-2020: FAQ03 - line.set_element('./cbc:ChargeIndicator', str( - charge.charge_indicator).lower()) - if charge.reason: - line.set_element( - './cbc:AllowanceChargeReasonCode', charge.reason.code) - line.set_element( - './cbc:allowanceChargeReason', charge.reason.reason) - line.set_element( - './cbc:MultiplierFactorNumeric', str( - round(charge.multiplier_factor_numeric, 2))) - fexml.set_element_amount_for( - line, './cbc:Amount', charge.amount) - fexml.set_element_amount_for( - line, './cbc:BaseAmount', charge.base_amount) + line = parent.fragment('./cac:AllowanceCharge', append=append) + #DIAN 1.7.-2020: FAQ02 + line.set_element('./cbc:ID', idx) + #DIAN 1.7.-2020: FAQ03 + line.set_element('./cbc:ChargeIndicator', str(charge.charge_indicator).lower()) + if charge.reason: + line.set_element('./cbc:AllowanceChargeReasonCode', charge.reason.code) + line.set_element('./cbc:allowanceChargeReason', charge.reason.reason) + line.set_element('./cbc:MultiplierFactorNumeric', str(round(charge.multiplier_factor_numeric, 2))) + fexml.set_element_amount_for(line, './cbc:Amount', charge.amount) + fexml.set_element_amount_for(line, './cbc:BaseAmount', charge.base_amount) + + def attach_invoice(fexml, invoice): + """adiciona etiquetas a FEXML y retorna FEXML + en caso de fallar validacion retorna None""" + + fexml.placeholder_for('./ext:UBLExtensions') + fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1') + fexml.set_element('./cbc:CustomizationID', invoice.invoice_operation_type) + fexml.placeholder_for('./cbc:ProfileID') + fexml.placeholder_for('./cbc:ProfileExecutionID') + fexml.set_element('./cbc:ID', invoice.invoice_ident) + fexml.placeholder_for('./cbc:UUID') + fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') + fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) + #DIAN 1.7.-2020: FAD10 + fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) + fexml.set_element('./cbc:%sTypeCode' % (fexml.tag_document()), + invoice.invoice_type_code, + listAgencyID='195', + listAgencyName='No matching global declaration available for the validation root', + listURI='http://www.dian.gov.co') + fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) + fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (fexml.tag_document()), + invoice.invoice_period_start.strftime('%Y-%m-%d')) + + fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (fexml.tag_document()), + invoice.invoice_period_end.strftime('%Y-%m-%d')) + + fexml.customize(invoice) + + fexml.set_supplier(invoice) + fexml.set_customer(invoice) + fexml.set_legal_monetary(invoice) + fexml.set_invoice_totals(invoice) + fexml.set_invoice_lines(invoice) + fexml.set_payment_mean(invoice) + fexml.set_allowance_charge(invoice) + fexml.set_billing_reference(invoice) + + return fexml def customize(fexml, invoice): """adiciona etiquetas a FEXML y retorna FEXML diff --git a/facho/fe/form_xml/support_document.py b/facho/fe/form_xml/support_document.py deleted file mode 100644 index c425e22..0000000 --- a/facho/fe/form_xml/support_document.py +++ /dev/null @@ -1,647 +0,0 @@ -from .. import fe -from ..form import ( - Amount, DebitNoteDocumentReference, CreditNoteDocumentReference, - InvoiceDocumentReference, TaxTotalOmit, WithholdingTaxTotalOmit -) - -from collections import defaultdict -from datetime import datetime -# from .attached_document import * - -__all__ = ['DIANSupportDocumentXML'] - - -class DIANSupportDocumentXML(fe.FeXML): - """ - DianSupportDocumentXML mapea objeto form.Invoice a XML segun - lo indicado para él Documento soporte en adquisiciones efectuadas con sujetos no obligados a expedir factura de venta o documento equivalente. - """ - - def __init__(self, invoice, tag_document='Invoice'): - super().__init__(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1') - - # DIAN 1.1.-2021: DSAB03 - # DIAN 1.1.-2021: NSAB03 - self.placeholder_for( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceControl') - - # DIAN 1.1.-2021: DSAB13 - # DIAN 1.1.-2021: NSAB13 - self.placeholder_for( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource') - - # DIAN 1.1.-2021: DSAB18 - # DIAN 1.1.-2021: NSAB18 - self.placeholder_for( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') - - # DIAN 1.1.-2021: DSAB27 - # DIAN 1.1.-2021: NSAB27 - self.placeholder_for( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareSecurityCode') - - # DIAN 1.1.-2021: DSAB30 DSAB31 - # DIAN 1.1.-2021: NSAB30 NSAB31 - self.placeholder_for( - './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider/sts:AuthorizationProviderID') - - # ZE02 se requiere existencia para firmar - # DIAN 1.1.-2021: DSAA02 DSAB01 - # DIAN 1.1.-2021: NSAA02 NSAB01 - ublextension = self.fragment( - './ext:UBLExtensions/ext:UBLExtension', append=True) - # DIAN 1.1.-2021: DSAB02 - # DIAN 1.1.-2021: NSAB02 - extcontent = ublextension.find_or_create_element( - '/ext:UBLExtension/ext:ExtensionContent') - self.attach_invoice(invoice) - - def set_supplier(fexml, invoice): - # DIAN 1.1.-2021: DSAJ01 - # DIAN 1.1.-2021: NSAB01 - fexml.placeholder_for('./cac:AccountingSupplierParty') - - # DIAN 1.1.-2021: DSAJ02 - # DIAN 1.1.-2021: NSAJ02 - fexml.set_element( - './cac:AccountingSupplierParty/cbc:AdditionalAccountID', - invoice.invoice_supplier.organization_code) - - # DIAN 1.1.-2021: DSAJ07 DSAJ08 - # DIAN 1.1.-2021: NSAJ07 NSAJ08 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address') - - # DIAN 1.1.-2021: DSAJ09 - # DIAN 1.1.-2021: NSAJ09 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:ID', - invoice.invoice_supplier.address.city.code) - - # DIAN 1.1.-2021: DSAJ10 - # DIAN 1.1.-2021: NSAJ10 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CityName', - invoice.invoice_supplier.address.city.name) - - # DIAN 1.1.-2021: DSAJ73 - # DIAN 1.1.-2021: NSAJ73 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:PostalZone', - invoice.invoice_supplier.address.postalzone.code) - - # DIAN 1.1.-2021: DSAJ11 - # DIAN 1.1.-2021: NSAJ11 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', - invoice.invoice_supplier.address.countrysubentity.name) - - # DIAN 1.1.-2021: DSAJ12 - # DIAN 1.1.-2021: NSAJ12 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentityCode', - invoice.invoice_supplier.address.countrysubentity.code) - # DIAN 1.1.-2021: NSAJ13 NSAJ14 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:AddressLine/cbc:Line', - invoice.invoice_supplier.address.street) - - # DIAN 1.1.-2021: DSAJ15 DSAJ16 - # DIAN 1.1.-2021: NSAJ15 NSAJ16 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:IdentificationCode', - invoice.invoice_supplier.address.country.code) - - # DIAN 1.1.-2021: DSAJ17 - # DIAN 1.1.-2021: NSAJ17 - fexml.set_element('./cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cac:Country/cbc:Name', - invoice.invoice_supplier.address.country.name, - # DIAN 1.1.-2021: DSAJ18 - # # DIAN 1.1.-2021: NSAJ18 - languageID='es') - - supplier_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() - supplier_company_id_attrs.update( - { - 'schemeID': invoice.invoice_supplier.ident.dv, - 'schemeName': invoice.invoice_supplier.ident.type_fiscal}) - - # DIAN 1.1.-2021: DSAJ19 - # DIAN 1.1.-2021: NSAJ19 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme') - - # DIAN 1.1.-2021: DSAJ20 - # DIAN 1.1.-2021: NSAJ20 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', - invoice.invoice_supplier.legal_name) - - # DIAN 1.1.-2021: DSAJ21 - # DIAN 1.1.-2021: NSAJ21 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', - invoice.invoice_supplier.ident, - # DIAN 1.1.-2021: DSAJ22 DSAJ23 DSAJ24 DSAJ25 - # DIAN 1.1.-2021: NSAJ22 NSAJ23 NSAJ24 NSAJ25 - **supplier_company_id_attrs) - - # DIAN 1.1.-2021: DSAJ26 - # DIAN 1.1.-2021: NSAJ26 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', - invoice.invoice_supplier.responsability_code, - listName=invoice.invoice_supplier.responsability_regime_code) - - # DIAN 1.1.-2021: DSAJ39 - # DIAN 1.1.-2021: NSAJ39 - fexml.placeholder_for( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') - - # DIAN 1.1.-2021: DSAJ40 - # DIAN 1.1.-2021: NSAJ40 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - invoice.invoice_customer.tax_scheme.code) - - # DIAN 1.1.-2021: DSAJ41 - # DIAN 1.1.-2021: NSAJ41 - fexml.set_element( - './cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - invoice.invoice_customer.tax_scheme.name) - - def set_customer(fexml, invoice): - # DIAN 1.1.-2021: DSAK01 - # DIAN 1.1.-2021: NSAK01 - fexml.placeholder_for('./cac:AccountingCustomerParty') - - # DIAN 1.1.-2021: DSAK02 - # DIAN 1.1.-2021: NSAK02 - fexml.set_element( - './cac:AccountingCustomerParty/cbc:AdditionalAccountID', - invoice.invoice_customer.organization_code) - - # DIAN 1.1.-2021: DSAK03 - # DIAN 1.1.-2021: NSAK03 - fexml.placeholder_for('./cac:AccountingCustomerParty/cac:Party') - - # DIAN 1.1.-2021: DSAK19 - # DIAN 1.1.-2021: NSAK19 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme') - - # DIAN 1.1.-2021: DSAK20 - # DIAN 1.1.-2021: NSAK20 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:RegistrationName', - invoice.invoice_customer.legal_name) - - customer_company_id_attrs = fe.SCHEME_AGENCY_ATTRS.copy() - customer_company_id_attrs.update( - { - 'schemeID': invoice.invoice_customer.ident.dv, - 'schemeName': invoice.invoice_customer.ident.type_fiscal}) - - # DIAN 1.1.-2021: DSAK21 - # DIAN 1.1.-2021: NSAK21 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID', - invoice.invoice_customer.ident, - # DIAN 1.1.-2021: DSAK22 DSAK23 DSAK24 DSAK25 - # DIAN 1.1.-2021: NSAK22 NSAK23 NSAK24 NSAK25 - **customer_company_id_attrs) - - # DIAN 1.1.-2021: DSAK26 - # DIAN 1.1.-2021: NSAK26 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:TaxLevelCode', - invoice.invoice_customer.responsability_code) - - # DIAN 1.1.-2021: DSAK39 - # DIAN 1.1.-2021: NSAK39 - fexml.placeholder_for( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme') - - # DIAN 1.1.-2021: DSAK40 - # DIAN 1.1.-2021: NSAK40 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID', - invoice.invoice_customer.tax_scheme.code) - - # DIAN 1.1.-2021: DSAK41 - # DIAN 1.1.-2021: NSAK41 - fexml.set_element( - './cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name', - invoice.invoice_customer.tax_scheme.name) - - def set_payment_mean(fexml, invoice): - payment_mean = invoice.invoice_payment_mean - - # DIAN 1.1.-2021: DSAN01 DSAN02 - # DIAN 1.1.-2021: NSAN02 NSAN02 - fexml.set_element('./cac:PaymentMeans/cbc:ID', payment_mean.id) - - # DIAN 1.1.-2021: DSAN03 - # DIAN 1.1.-2021: NSAN03 - fexml.set_element( - './cac:PaymentMeans/cbc:PaymentMeansCode', - payment_mean.code) - - # DIAN 1.1.-2021: DSAN04 - # DIAN 1.1.-2021: NSAN04 - fexml.set_element( - './cac:PaymentMeans/cbc:PaymentDueDate', - payment_mean.due_at.strftime('%Y-%m-%d')) - - # DIAN 1.1.-2021: DSAN05 - # DIAN 1.1.-2021: NSAN05 - fexml.set_element( - './cac:PaymentMeans/cbc:PaymentID', - payment_mean.payment_id) - - def set_element_amount_for(fexml, xml, xpath, amount): - if not isinstance(amount, Amount): - raise TypeError("amount not is Amount") - - xml.set_element(xpath, amount, currencyID=amount.currency.code) - - def set_element_amount(fexml, xpath, amount): - if not isinstance(amount, Amount): - raise TypeError("amount not is Amount") - - fexml.set_element(xpath, amount, currencyID=amount.currency.code) - - def set_legal_monetary(fexml, invoice): - # DIAN 1.1.-2021: DSAU01 DSAU02 DSAU03 - # DIAN 1.1.-2021: NSAU01 NSAU02 NSAU03 - fexml.set_element_amount( - './cac:LegalMonetaryTotal/cbc:LineExtensionAmount', - invoice.invoice_legal_monetary_total.line_extension_amount) - - # DIAN 1.1.-2021: DSAU04 DSAU05 - # DIAN 1.1.-2021: NSAU04 NSAU05 - fexml.set_element_amount( - './cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', - invoice.invoice_legal_monetary_total.tax_exclusive_amount) - - # DIAN 1.1.-2021: DSAU06 DSAU07 - # DIAN 1.1.-2021: NSAU06 DSAU07 - fexml.set_element_amount( - './cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', - invoice.invoice_legal_monetary_total.tax_inclusive_amount) - - # DIAN 1.1.-2021: DSAU10 DSAU11 - # DIAN 1.1.-2021: NSAU10 DSAU11 - fexml.set_element_amount( - './cac:LegalMonetaryTotal/cbc:ChargeTotalAmount', - invoice.invoice_legal_monetary_total.charge_total_amount) - - # DIAN 1.1.-2021: DSAU14 DSAU15 - # DIAN 1.1.-2021: NSAU14 DSAU15 - fexml.set_element_amount( - './cac:LegalMonetaryTotal/cbc:PayableAmount', - invoice.invoice_legal_monetary_total.payable_amount) - - def _set_invoice_document_reference(fexml, reference): - fexml._do_set_billing_reference( - reference, 'cac:InvoiceDocumentReference') - - def _set_credit_note_document_reference(fexml, reference): - fexml._do_set_billing_reference( - reference, 'cac:CreditNoteDocumentReference') - - def _set_debit_note_document_reference(fexml, reference): - fexml._do_set_billing_reference( - reference, 'cac:DebitNoteDocumentReference') - - def _do_set_billing_reference(fexml, reference, tag_document): - - if tag_document == 'Invoice': - schemeName = 'CUFE-SHA384' - else: - schemeName = 'CUDS-SHA384' - - fexml.set_element('./cac:BillingReference/%s/cbc:ID' % (tag_document), - reference.ident) - fexml.set_element( - './cac:BillingReference/cac:InvoiceDocumentReference/cbc:UUID', - reference.uuid, - schemeName=schemeName) - fexml.set_element( - './cac:BillingReference/cac:InvoiceDocumentReference/cbc:IssueDate', - reference.date.strftime("%Y-%m-%d")) - - def set_billing_reference(fexml, invoice): - reference = invoice.invoice_billing_reference - if reference is None: - return - - if isinstance(reference, DebitNoteDocumentReference): - return fexml._set_debit_note_document_reference(reference) - if isinstance(reference, CreditNoteDocumentReference): - return fexml._set_credit_note_document_reference(reference) - - if isinstance(reference, InvoiceDocumentReference): - return fexml._set_invoice_document_reference(reference) - - def set_discrepancy_response(fexml, invoice): - reference = invoice.invoice_discrepancy_response - if reference is None: - return - if isinstance(reference, DebitNoteDocumentReference): - return fexml._set_debit_note_document_reference(reference) - if isinstance(reference, CreditNoteDocumentReference): - return fexml._set_credit_note_document_reference(reference) - - if isinstance(reference, InvoiceDocumentReference): - return fexml._set_invoice_document_reference(reference) - - fexml.set_element('./cac:DiscrepancyResponse/cbc:ReferenceID', - reference.id) - fexml.set_element('./cac:DiscrepancyResponse/cbc:ResponseCode', - reference.code) - fexml.set_element('./cac:DiscrepancyResponse/cbc:Description', - reference.description) - - def set_invoice_totals(fexml, invoice): - tax_amount_for = defaultdict(lambda: defaultdict(lambda: Amount(0.0))) - percent_for = defaultdict(lambda: None) - - total_tax_amount = Amount(0.0) - - for invoice_line in invoice.invoice_lines: - for subtotal in invoice_line.tax.subtotals: - if subtotal.scheme is not None: - tax_amount_for[ - subtotal.scheme.code][ - 'tax_amount'] += subtotal.tax_amount - tax_amount_for[subtotal.scheme.code][ - 'taxable_amount'] += invoice_line.taxable_amount - - # MACHETE ojo InvoiceLine.tax pasar a Invoice - percent_for[subtotal.scheme.code] = subtotal.percent - - total_tax_amount += subtotal.tax_amount - - if total_tax_amount != Amount(0.0): - fexml.placeholder_for('./cac:TaxTotal') - fexml.set_element_amount('./cac:TaxTotal/cbc:TaxAmount', - total_tax_amount) - - for index, item in enumerate(tax_amount_for.items()): - cod_impuesto, amount_of = item - next_append = index > 0 - - # DIAN 1.7.-2020: FAS01 - line = fexml.fragment('./cac:TaxTotal', append=next_append) - # DIAN 1.7.-2020: FAU06 - tax_amount = amount_of['tax_amount'] - fexml.set_element_amount_for(line, - '/cac:TaxTotal/cbc:TaxAmount', - tax_amount) - - # DIAN 1.7.-2020: FAS05 - fexml.set_element_amount_for( - line, - '/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - amount_of['taxable_amount']) - - # DIAN 1.7.-2020: FAU06 - fexml.set_element_amount_for( - line, - '/cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', - amount_of['tax_amount']) - - # DIAN 1.7.-2020: FAS07 - if percent_for[cod_impuesto]: - line.set_element('/cac:TaxTotal/cac:TaxSubtotal/cbc:Percent', - percent_for[cod_impuesto]) - - if percent_for[cod_impuesto]: - line.set_element( - '/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', - percent_for[cod_impuesto]) - - line.set_element( - '/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', - cod_impuesto) - line.set_element( - '/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', 'IVA') - - # abstract method - - def tag_document(fexml): - return 'Invoice' - - # abstract method - def tag_document_concilied(fexml): - return 'Invoiced' - - def set_invoice_line_withholding(fexml, line, invoice_line): - fexml.set_element_amount_for(line, - './cac:WithholdingTaxTotal/cbc:TaxAmount', - invoice_line.withholding_amount) - # DIAN 1.7.-2020: FAX05 - fexml.set_element_amount_for( - line, - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - invoice_line.withholding_taxable_amount) - - for subtotal in invoice_line.withholding.subtotals: - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cbc:TaxAmount', - subtotal.tax_amount, - currencyID='COP') - - if subtotal.percent is not None: - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', - '%0.2f' % - round( - subtotal.percent, - 2)) - - if subtotal.scheme is not None: - # DIAN 1.7.-2020: FAX15 - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', - subtotal.scheme.code) - line.set_element( - './cac:WithholdingTaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', - subtotal.scheme.name) - - def set_invoice_line_tax(fexml, line, invoice_line): - fexml.set_element_amount_for(line, - './cac:TaxTotal/cbc:TaxAmount', - invoice_line.tax_amount) - - # DIAN 1.7.-2020: FAX05 - fexml.set_element_amount_for( - line, - './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount', - invoice_line.taxable_amount) - for subtotal in invoice_line.tax.subtotals: - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cbc:TaxAmount', - subtotal.tax_amount, - currencyID='COP') - - if subtotal.percent is not None: - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent', - '%0.2f' % - round( - subtotal.percent, - 2)) - - if subtotal.scheme is not None: - # DIAN 1.7.-2020: FAX15 - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:ID', - subtotal.scheme.code) - line.set_element( - './cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', - subtotal.scheme.name) - - def set_invoice_lines(fexml, invoice): - next_append = False - for index, invoice_line in enumerate(invoice.invoice_lines): - line = fexml.fragment( - './cac:%sLine' % - (fexml.tag_document()), - append=next_append) - next_append = True - - line.set_element('./cbc:ID', index + 1) - line.set_element( - './cbc:%sQuantity' % - (fexml.tag_document_concilied()), - invoice_line.quantity, - unitCode='NAR') - fexml.set_element_amount_for(line, - './cbc:LineExtensionAmount', - invoice_line.total_amount) - - period = line.fragment('./cac:InvoicePeriod') - period.set_element('./cbc:StartDate', - datetime.now().strftime('%Y-%m-%d')) - period.set_element( - './cbc:DescriptionCode', '1') - period.set_element('./cbc:Description', - 'Por operación') - - if not isinstance(invoice_line.tax, TaxTotalOmit): - fexml.set_invoice_line_tax(line, invoice_line) - - if not isinstance( - invoice_line.withholding, - WithholdingTaxTotalOmit): - fexml.set_invoice_line_withholding(line, invoice_line) - - line.set_element( - './cac:Item/cbc:Description', - invoice_line.item.description) - - line.set_element( - './cac:Item/cac:StandardItemIdentification/cbc:ID', - invoice_line.item.id, - schemeID=invoice_line.item.scheme_id, - schemeName=invoice_line.item.scheme_name, - schemeAgencyID=invoice_line.item.scheme_agency_id) - - line.set_element( - './cac:Price/cbc:PriceAmount', - invoice_line.price.amount, - currencyID=invoice_line.price.amount.currency.code) - # DIAN 1.7.-2020: FBB04 - line.set_element('./cac:Price/cbc:BaseQuantity', - invoice_line.quantity, - unitCode=invoice_line.quantity.code) - - for idx, charge in enumerate(invoice_line.allowance_charge): - next_append_charge = idx > 0 - fexml.append_allowance_charge( - line, index + 1, charge, append=next_append_charge) - - def set_allowance_charge(fexml, invoice): - for idx, charge in enumerate(invoice.invoice_allowance_charge): - next_append = idx > 0 - fexml.append_allowance_charge( - fexml, idx + 1, charge, append=next_append) - - def append_allowance_charge(fexml, parent, idx, charge, append=False): - line = parent.fragment('./cac:AllowanceCharge', append=append) - # DIAN 1.7.-2020: FAQ02 - line.set_element('./cbc:ID', idx) - # DIAN 1.7.-2020: FAQ03 - line.set_element('./cbc:ChargeIndicator', - str(charge.charge_indicator).lower()) - if charge.reason: - line.set_element( - './cbc:AllowanceChargeReasonCode', - charge.reason.code) - line.set_element( - './cbc:allowanceChargeReason', - charge.reason.reason) - line.set_element('./cbc:MultiplierFactorNumeric', - str(round(charge.multiplier_factor_numeric, 2))) - fexml.set_element_amount_for(line, './cbc:Amount', charge.amount) - fexml.set_element_amount_for( - line, './cbc:BaseAmount', charge.base_amount) - - def attach_invoice(fexml, invoice): - """adiciona etiquetas a FEXML y retorna FEXML - en caso de fallar validacion retorna None""" - - fexml.placeholder_for('./ext:UBLExtensions') - fexml.set_element('./cbc:UBLVersionID', 'UBL 2.1') - fexml.set_element( - './cbc:CustomizationID', - invoice.invoice_operation_type) - fexml.placeholder_for('./cbc:ProfileID') - fexml.placeholder_for('./cbc:ProfileExecutionID') - fexml.set_element('./cbc:ID', invoice.invoice_ident) - fexml.placeholder_for('./cbc:UUID') - fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') - fexml.set_element( - './cbc:IssueDate', - invoice.invoice_issue.strftime('%Y-%m-%d')) - # DIAN 1.7.-2020: FAD10 - fexml.set_element( - './cbc:IssueTime', - invoice.invoice_issue.strftime('%H:%M:%S-05:00')) - fexml.set_element( - './cbc:%sTypeCode' % - (fexml.tag_document()), - invoice.invoice_type_code, - listAgencyID='195', - listAgencyName='No matching global declaration available for the validation root', - listURI='http://www.dian.gov.co') - fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) - fexml.set_element( - './cac:%sPeriod/cbc:StartDate' % - (fexml.tag_document()), - invoice.invoice_period_start.strftime('%Y-%m-%d')) - - fexml.set_element( - './cac:%sPeriod/cbc:EndDate' % - (fexml.tag_document()), - invoice.invoice_period_end.strftime('%Y-%m-%d')) - - fexml.customize(invoice) - - fexml.set_supplier(invoice) - fexml.set_customer(invoice) - fexml.set_legal_monetary(invoice) - fexml.set_invoice_totals(invoice) - fexml.set_invoice_lines(invoice) - fexml.set_payment_mean(invoice) - fexml.set_allowance_charge(invoice) - fexml.set_discrepancy_response(invoice) - fexml.set_billing_reference(invoice) - - return fexml - - def customize(fexml, invoice): - """adiciona etiquetas a FEXML y retorna FEXML - en caso de fallar validacion retorna None""" diff --git a/facho/fe/form_xml/support_document_credit_note.py b/facho/fe/form_xml/support_document_credit_note.py deleted file mode 100644 index 0145e71..0000000 --- a/facho/fe/form_xml/support_document_credit_note.py +++ /dev/null @@ -1,25 +0,0 @@ -# from .. import fe -# from ..form import * -from .support_document import DIANSupportDocumentXML - -__all__ = ['DIANSupportDocumentCreditNoteXML'] - - -class DIANSupportDocumentCreditNoteXML(DIANSupportDocumentXML): - """ - DianInvoiceXML mapea objeto form.Invoice a XML segun - lo indicado para la facturacion electronica. - """ - - def __init__(self, invoice): - super( - DIANSupportDocumentCreditNoteXML, - self).__init__( - invoice, - 'CreditNote') - - def tag_document(fexml): - return 'CreditNote' - - def tag_document_concilied(fexml): - return 'Credited' diff --git a/facho/fe/form_xml/utils.py b/facho/fe/form_xml/utils.py index 2d084c7..c05c3d4 100644 --- a/facho/fe/form_xml/utils.py +++ b/facho/fe/form_xml/utils.py @@ -2,30 +2,18 @@ from .. import fe __all__ = ['DIANWrite', 'DIANWriteSigned'] - def DIANWrite(xml, filename): document = xml.tostring(xml_declaration=True, encoding='UTF-8') with open(filename, 'w') as f: f.write(document) - -def DIANWriteSigned( - xml, - filename, - private_key, - passphrase, - use_cache_policy=False, - dian_signer=None): - document = xml.tostring( - xml_declaration=True, - encoding='UTF-8').encode('utf-8') + +def DIANWriteSigned(xml, filename, private_key, passphrase, use_cache_policy=False, dian_signer=None): + document = xml.tostring(xml_declaration=True, encoding='UTF-8').encode('utf-8') if dian_signer is None: dian_signer = fe.DianXMLExtensionSigner - signer = dian_signer( - private_key, - passphrase=passphrase, - localpolicy=use_cache_policy) + signer = dian_signer(private_key, passphrase=passphrase, localpolicy=use_cache_policy) with open(filename, 'w') as f: f.write(signer.sign_xml_string(document)) diff --git a/facho/fe/nomina/__init__.py b/facho/fe/nomina/__init__.py index a1f523a..5baaf57 100644 --- a/facho/fe/nomina/__init__.py +++ b/facho/fe/nomina/__init__.py @@ -53,20 +53,14 @@ class FechaPago(Fecha): @dataclass class Novedad: - # cune de nomina a relacionar - # NIE204 - cune: str - # NIE199 - activa: bool = False + value: False def apply(self, fragment): - if self.cune != "": - fragment.set_attributes('./Novedad', - CUNENov=self.cune, - ) - + fragment.set_attributes('./Novedad', + CUNENov=self.value, + ) def post_apply(self, fexml, scopexml, fragment): - scopexml.set_element('./Novedad', self.activa) + scopexml.set_element('./Novedad', "false") @dataclass @@ -144,12 +138,13 @@ class Proveedor: ambiente = fexml.get_element_attribute(scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente') codigo_qr = f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={cune}" - if InformacionGeneral.AMBIENTE_PRUEBAS == ambiente: + if InformacionGeneral.AMBIENTE_PRUEBAS.same(ambiente): codigo_qr = f"https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey={cune}" elif ambiente is None: raise RuntimeError('fail to get InformacionGeneral/@Ambiente') scopexml.set_element('./CodigoQR', codigo_qr) + scopexml.set_element('./Novedad', "false") # NIE020 software_code = self._software_security_code(fexml, scopexml) @@ -182,16 +177,14 @@ class Metadata: proveedor: Proveedor def apply(self, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml): - if novedad: - self.novedad.apply(novedad) + self.novedad.apply(novedad) self.secuencia.apply(numero_secuencia_xml) self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML') self.proveedor.apply(proveedor_xml) def post_apply(self, fexml, scopexml, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml): self.proveedor.post_apply(fexml, scopexml, proveedor_xml) - if novedad: - self.novedad.post_apply(fexml, scopexml, proveedor_xml) + self.novedad.post_apply(fexml, scopexml, proveedor_xml) @dataclass class PeriodoNomina: @@ -219,8 +212,9 @@ class InformacionGeneral: class TIPO_AMBIENTE: valor: str - def __eq__(self, other): - return self.valor == str(other) + @classmethod + def same(cls, value): + return cls.valor == str(value) # TABLA 5.1.1 @dataclass @@ -234,28 +228,6 @@ class InformacionGeneral: class AMBIENTE_PRUEBAS(TIPO_AMBIENTE): valor: str = '2' - def __str__(self): - self.valor - - # TABLA 5.5.7 - @dataclass - class TIPO_XML: - valor: str - - def __eq__(self, other): - return self.valor == str(other) - - @dataclass - class TIPO_XML_NORMAL(TIPO_XML): - valor: str = '102' - - def __str__(self): - self.valor - - @dataclass - class TIPO_XML_AJUSTES(TIPO_XML): - valor: str = '103' - def __str__(self): self.valor @@ -264,7 +236,6 @@ class InformacionGeneral: periodo_nomina: PeriodoNomina tipo_moneda: TipoMoneda tipo_ambiente: TIPO_AMBIENTE - tipo_xml: TIPO_XML software_pin: str def __post_init__(self): @@ -279,7 +250,7 @@ class InformacionGeneral: # NIE202 # TABLA 5.5.2 # TODO(bit4bit) solo NominaIndividual - TipoXML = self.tipo_xml.valor, + TipoXML = '102', # NIE024 CUNE = None, # NIE025 @@ -338,18 +309,15 @@ class DianXMLExtensionSigner(fe.DianXMLExtensionSigner): 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): self.informacion_general_version = None self.tag_document = tag_document + self.fexml = fe.FeXML(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1') - if namespace_ajuste: - self.fexml = fe.FeXML(tag_document, namespace_ajuste) - else: - self.fexml = fe.FeXML(tag_document, 'dian:gov:co:facturaelectronica:NominaIndividual') - - self.fexml.root.set("SchemaLocation", "") - self.fexml.root.set("schemaLocation", schemaLocation) + if schemaLocation is not None: + self.fexml.root.set("SchemaLocation", "") + self.fexml.root.set("change", schemaLocation) # layout, la dian requiere que los elementos # esten ordenados segun el anexo tecnico @@ -361,8 +329,7 @@ class DIANNominaXML: self.root_fragment = self.fexml.fragment(xpath_ajuste) self.root_fragment.placeholder_for('./ReemplazandoPredecesor', optional=True) self.root_fragment.placeholder_for('./EliminandoPredecesor', optional=True) - if not namespace_ajuste: - self.root_fragment.placeholder_for('./Novedad', optional=False) + self.root_fragment.placeholder_for('./Novedad', optional=False) self.root_fragment.placeholder_for('./Periodo') self.root_fragment.placeholder_for('./NumeroSecuenciaXML') self.root_fragment.placeholder_for('./LugarGeneracionXML') @@ -375,10 +342,8 @@ class DIANNominaXML: self.root_fragment.placeholder_for('./FechasPagos') self.root_fragment.placeholder_for('./Devengados/Basico') self.root_fragment.placeholder_for('./Devengados/Transporte', optional=True) - if not namespace_ajuste: - self.novedad = self.root_fragment.fragment('./Novedad') - else: - self.novedad = None + + self.novedad = self.root_fragment.fragment('./Novedad') self.informacion_general_xml = self.root_fragment.fragment('./InformacionGeneral') self.periodo_xml = self.root_fragment.fragment('./Periodo') self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos') @@ -398,7 +363,6 @@ class DIANNominaXML: if not isinstance(metadata, Metadata): raise ValueError('se espera tipo Metadata') self.metadata = metadata - self.metadata.apply(self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml) def asignar_informacion_general(self, general): @@ -555,8 +519,6 @@ class DIANNominaXML: devengados_total = Amount(0.0) for devengado in devengados: devengados_total += devengado - # TODO(bit4bit) nque valor va redondeado? - # NIE186 self.root_fragment.set_element('./Redondeo', str(round(0,2))) self.root_fragment.set_element('./DevengadosTotal', str(round(devengados_total,2))) @@ -592,8 +554,6 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML): fecha_generacion: str def apply(self, fragment): - # NIAE214 - fragment.set_element('./TipoNota', '1') fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None, # NIAE090 NumeroPred = self.numero, @@ -604,11 +564,9 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML): ) def __init__(self): - schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd" - - 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' + super().__init__('NominaIndividualDeAjuste', './Reemplazar') + # NIAE214 + self.root_fragment.set_element('./TipoNota', '1') def asignar_predecesor(self, predecesor): if not isinstance(predecesor, self.Predecesor): @@ -625,7 +583,6 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML): fecha_generacion: str def apply(self, fragment): - fragment.set_element('./TipoNota', '2') fragment.set_element('./Eliminar/EliminandoPredecesor', None, # NIAE090 NumeroPred = self.numero, @@ -636,9 +593,9 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML): ) def __init__(self): - schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd" - super().__init__('NominaIndividualDeAjuste', './Eliminar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste') + super().__init__('NominaIndividualDeAjuste', './Eliminar') + self.root_fragment.set_element('./TipoNota', '2') self.informacion_general_version = "V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica" def asignar_predecesor(self, predecesor): diff --git a/facho/fe/nomina/trabajador/__init__.py b/facho/fe/nomina/trabajador/__init__.py index a262866..9867b3e 100644 --- a/facho/fe/nomina/trabajador/__init__.py +++ b/facho/fe/nomina/trabajador/__init__.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from ..amount import Amount @@ -29,7 +29,7 @@ class Trabajador: codigo_trabajador: str = None otros_nombres: str = None - sub_tipo: SubTipoTrabajador = field(default_factory=lambda: SubTipoTrabajador(code='00')) + sub_tipo: SubTipoTrabajador = SubTipoTrabajador(code='00') def apply(self, fragment): fragment.set_attributes('./Trabajador', diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index e96da27..0000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,17 +0,0 @@ -attrs==22.1.0 -distlib==0.3.6 -filelock==3.8.0 -iniconfig==1.1.1 -packaging==21.3 -platformdirs==2.5.2 -pluggy==1.0.0 -py==1.11.0 -pyparsing==3.0.9 -pytest==7.1.3 -semantic-version==2.10.0 -setuptools-rust==1.5.2 -six==1.16.0 -tomli==2.0.1 -tox==3.26.0 -typing_extensions==4.4.0 -virtualenv==20.16.5 diff --git a/setup.cfg b/setup.cfg index 0f67e9d..f5945f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,4 +22,4 @@ exclude = docs test = pytest [tool:pytest] -addopts = --ignore=setup.py +collect_ignore = ['setup.py'] diff --git a/setup.py b/setup.py index 226c7f8..3a2147b 100644 --- a/setup.py +++ b/setup.py @@ -13,36 +13,19 @@ with open('README.rst') as readme_file: with open('HISTORY.rst') as history_file: history = history_file.read() -requirements = ['Click>=8.1.7', - 'zeep==4.2.1', - 'lxml==5.2.2', - 'cryptography==3.3.2', - 'pyOpenSSL==20.0.1', - 'xmlsig==0.1.7', - 'xades==1.0.0', - 'xmlsec==1.3.14', - 'python-dateutil==2.9.0.post0', - # usamos esta dependencia en runtime - # para forzar uso de policy_id de archivo local - 'mock>=5.1.0', - 'xmlschema>=3.0.0'] - -""" -Listado de Versiones Anteriores -requirements = ['Click>=6.0', - 'zeep==4.0.0', - 'lxml==4.6.3', - 'cryptography==3.3.2', - 'pyOpenSSL==20.0.1', - 'xmlsig==0.1.7', - 'xades==0.2.2', - 'xmlsec==1.3.12', - # usamos esta dependencia en runtime - # para forzar uso de policy_id de archivo local - 'mock>=2.0.0', - 'xmlschema>=1.8'] - -""" +requirements = ['Click>=8.1.7', + 'zeep==4.2.1', + 'lxml==5.2.2', + 'cryptography==3.3.2', + 'pyOpenSSL==20.0.1', + 'xmlsig==0.1.7', + 'xades==1.0.0', + 'xmlsec==1.3.14', + 'python-dateutil==2.9.0.post0', + # usamos esta dependencia en runtime + # para forzar uso de policy_id de archivo local + 'mock>=5.1.0', + 'xmlschema>=3.0.0' ] setup_requirements = ['pytest-runner', ] @@ -57,10 +40,10 @@ setup( 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Natural Language :: English', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], description="Facturacion Electronica Colombia", entry_points={ diff --git a/tests/cude.txt b/tests/cude.txt deleted file mode 100644 index d0bee00..0000000 --- a/tests/cude.txt +++ /dev/null @@ -1 +0,0 @@ -907e4444decc9e59c160a2fb3b6659b33dc5b632a5008922b9a62f83f757b1c448e47f5867f2b50dbdb96f48c7681168 diff --git a/tests/cufe.txt b/tests/cufe.txt deleted file mode 100644 index e69de29..0000000 diff --git a/tests/fixtures.py b/tests/fixtures.py index 574e3ac..34501d7 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -4,32 +4,30 @@ from datetime import datetime @pytest.fixture def simple_debit_note_without_lines(): - inv = form.DebitNote(form.InvoiceDocumentReference( - '1234', 'xx', datetime.now())) + inv = form.DebitNote(form.InvoiceDocumentReference('1234', 'xx', datetime.now())) inv.set_period(datetime.now(), datetime.now()) inv.set_issue(datetime.now()) inv.set_ident('ABC123') inv.set_operation_type('30') - inv.set_payment_mean(form.PaymentMean( - form.PaymentMean.DEBIT, '41', datetime.now(), '1234')) + inv.set_payment_mean(form.PaymentMean(form.PaymentMean.DEBIT, '41', datetime.now(), '1234')) inv.set_supplier(form.Party( - name='facho-supplier', - ident=form.PartyIdentification('123', '', '31'), - responsability_code = form.Responsability(['ZZ']), - responsability_regime_code='48', - organization_code='1', - address=form.Address( + name = 'facho-supplier', + ident = form.PartyIdentification('123','', '31'), + responsability_code = form.Responsability(['O-07']), + responsability_regime_code = '48', + organization_code = '1', + address = form.Address( '', '', form.City('05001', 'Medellín'), form.Country('CO', 'Colombia'), form.CountrySubentity('05', 'Antioquia')) )) inv.set_customer(form.Party( - name='facho-customer', - ident=form.PartyIdentification('321', '', '31'), - responsability_code=form.Responsability(['ZZ']), - responsability_regime_code='48', - organization_code='1', - address=form.Address( + name = 'facho-customer', + ident = form.PartyIdentification('321', '', '31'), + responsability_code = form.Responsability(['O-07']), + responsability_regime_code = '48', + organization_code = '1', + address = form.Address( '', '', form.City('05001', 'Medellín'), form.Country('CO', 'Colombia'), form.CountrySubentity('05', 'Antioquia')) @@ -47,7 +45,7 @@ def simple_credit_note_without_lines(): inv.set_supplier(form.Party( name = 'facho-supplier', ident = form.PartyIdentification('123','', '31'), - responsability_code = form.Responsability(['ZZ']), + responsability_code = form.Responsability(['O-07']), responsability_regime_code = '48', organization_code = '1', address = form.Address( @@ -58,7 +56,7 @@ def simple_credit_note_without_lines(): inv.set_customer(form.Party( name = 'facho-customer', ident = form.PartyIdentification('321', '', '31'), - responsability_code = form.Responsability(['ZZ']), + responsability_code = form.Responsability(['O-07']), responsability_regime_code = '48', organization_code = '1', address = form.Address( @@ -79,7 +77,7 @@ def simple_invoice_without_lines(): inv.set_supplier(form.Party( name = 'facho-supplier', ident = form.PartyIdentification('123','', '31'), - responsability_code = form.Responsability(['ZZ']), + responsability_code = form.Responsability(['O-07']), responsability_regime_code = '48', organization_code = '1', address = form.Address( @@ -90,7 +88,7 @@ def simple_invoice_without_lines(): inv.set_customer(form.Party( name = 'facho-customer', ident = form.PartyIdentification('321', '', '31'), - responsability_code = form.Responsability(['ZZ']), + responsability_code = form.Responsability(['O-07']), responsability_regime_code = '48', organization_code = '1', address = form.Address( @@ -100,7 +98,6 @@ def simple_invoice_without_lines(): )) return inv - @pytest.fixture def simple_invoice(): inv = form.NationalSalesInvoice() @@ -112,7 +109,7 @@ def simple_invoice(): inv.set_supplier(form.Party( name = 'facho-supplier', ident = form.PartyIdentification('123','', '31'), - responsability_code = form.Responsability(['ZZ']), + responsability_code = form.Responsability(['O-07']), responsability_regime_code = '48', organization_code = '1', address = form.Address( @@ -123,7 +120,7 @@ def simple_invoice(): inv.set_customer(form.Party( name = 'facho-customer', ident = form.PartyIdentification('321','', '31'), - responsability_code = form.Responsability(['ZZ']), + responsability_code = form.Responsability(['O-07']), responsability_regime_code = '48', organization_code = '1', address = form.Address( @@ -131,20 +128,19 @@ def simple_invoice(): form.Country('CO', 'Colombia'), form.CountrySubentity('05', 'Antioquia')) )) - inv.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='productofacho', - item=form.StandardItem(9999), - price=form.Price(form.Amount(100.0),'01',''), - tax=form.TaxTotal( - tax_amount=form.Amount(0.0), - taxable_amount=form.Amount(0.0), - subtotals=[ + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem( 9999), + price = form.Price(form.Amount(100.0), '01', ''), + tax = form.TaxTotal( + tax_amount = form.Amount(0.0), + taxable_amount = form.Amount(0.0), + subtotals = [ form.TaxSubTotal( - percent=19.0, - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + percent = 19.0, + ) + ] + ) )) return inv diff --git a/tests/test_application_response.py b/tests/test_application_response.py deleted file mode 100644 index 01f03be..0000000 --- a/tests/test_application_response.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# This file is part of facho. The COPYRIGHT file at the top level of -# this repository contains the full copyright notices and license terms. -import pytest -from facho.fe import form_xml -from fixtures import simple_invoice - -simple_invoice = simple_invoice - - -def test_application_response(simple_invoice): - - doc = form_xml.ApplicationResponse(simple_invoice) - xml = doc.toFachoXML() - - with open("application_response.xml", "w") as fh: - fh.write(xml.tostring()) - # raise Exception(xml.tostring()) - # assert xml.get_element_text( - # './apr:ApplicationResponse') diff --git a/tests/test_attached_document.py b/tests/test_attached_document.py index 868cfd2..0e58be9 100644 --- a/tests/test_attached_document.py +++ b/tests/test_attached_document.py @@ -2,124 +2,16 @@ # -*- coding: utf-8 -*- # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -# from datetime import datetime +from datetime import datetime import pytest from facho.fe import form_xml -from datetime import datetime + import helpers -from fixtures import simple_invoice -simple_invoice = simple_invoice - -def test_xml_with_required_elements(simple_invoice): - - xml_header = '' - DIANInvoiceXML = form_xml.DIANInvoiceXML( - simple_invoice) - - doc = form_xml.AttachedDocument( - simple_invoice, - DIANInvoiceXML, - id='123') +def test_xml_with_required_elements(): + doc = form_xml.AttachedDocument(id='123') xml = doc.toFachoXML() - - DIANInvoiceXML = form_xml.DIANInvoiceXML( - simple_invoice, 'Invoice').attach_invoice - - ApplicationResponse = xml_header + form_xml.ApplicationResponse(simple_invoice).toFachoXML().tostring() - - attached_document = xml_header + DIANInvoiceXML.tostring() - - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:UBLVersionID') == 'UBL 2.1' - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:CustomizationID') == 'Documentos adjuntos' - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:ProfileID') == 'Factura Electrónica de Venta' - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:ProfileExecutionID') == '1' - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:ID') == '123' - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:IssueDate') == str(datetime.today().date()) - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:IssueTime') == datetime.today( - ).time().strftime( - '%H:%M:%S-05:00') - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:DocumentType' - ) == 'Contenedor de Factura Electrónica' - assert xml.get_element_text( - '/atd:AttachedDocument/cbc:ParentDocumentID' - ) == 'ABC123' - - assert xml.get_element_text( - '/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cbc:RegistrationName' - ) == 'facho-supplier' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cbc:CompanyID' - ) == '123' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cbc:TaxLevelCode' - ) == 'ZZ' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID' - ) == '01' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:SenderParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name' - ) == 'IVA' - - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cbc:RegistrationName' - ) == 'facho-customer' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cbc:CompanyID' - ) == '321' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cbc:TaxLevelCode' - ) == 'ZZ' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:ID' - ) == '01' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ReceiverParty/cac:PartyTaxScheme/cac:TaxScheme/cbc:Name' - ) == 'IVA' - - assert xml.get_element_text( - '/atd:AttachedDocument/cac:Attachment/cac:ExternalReference/cbc:MimeCode' - ) == "text/xml" - assert xml.get_element_text( - '/atd:AttachedDocument/cac:Attachment/cac:ExternalReference/cbc:EncodingCode' - ) == "UTF-8" - assert xml.get_element_text( - '/atd:AttachedDocument/cac:Attachment/cac:ExternalReference/cbc:Description' - ) == "".format(attached_document) - - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cbc:LineID' - ) == '1' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:ID' - ) == '1234' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:UUID' - ) == '1234' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:IssueDate' - ) == '2024-11-28' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cbc:DocumentType' - ) == 'ApplicationResponse' - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:MimeCode' - ) == 'text/xml' - - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:EncodingCode' - ) == "UTF-8" - - assert xml.get_element_text( - '/atd:AttachedDocument/cac:ParentDocumentLineReference/cac:DocumentReference/cac:Attachment/cac:ExternalReference/cbc:Description' - ) == "".format(ApplicationResponse) + assert xml.get_element_text('/atd:AttachedDocument/cbc:ID') == '123' + diff --git a/tests/test_data.py b/tests/test_data.py index a463319..88381b0 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -4,26 +4,21 @@ # this repository contains the full copyright notices and license terms. """Tests for `facho` package.""" -from facho.fe.data.dian import codelist +import pytest +from facho.fe.data.dian import codelist def test_tiporesponsabilidad(): assert codelist.TipoResponsabilidad.short_name == 'TipoResponsabilidad' - assert codelist.TipoResponsabilidad.by_name( - 'Autorretenedor')['name'] == 'Autorretenedor' - + assert codelist.TipoResponsabilidad.by_name('Autorretenedor')['name'] == 'Autorretenedor' def test_tipoorganizacion(): assert codelist.TipoOrganizacion.short_name == 'TipoOrganizacion' - assert codelist.TipoOrganizacion.by_name( - 'Persona Natural')['name'] == 'Persona Natural' - + assert codelist.TipoOrganizacion.by_name('Persona Natural')['name'] == 'Persona Natural' def test_tipodocumento(): assert codelist.TipoDocumento.short_name == 'TipoDocumento' - assert codelist.TipoDocumento.by_name( - 'Factura electrónica de Venta')['code'] == '01' - + assert codelist.TipoDocumento.by_name('Factura de Venta Nacional')['code'] == '01' def test_departamento(): assert codelist.Departamento['05']['name'] == 'Antioquia' diff --git a/tests/test_fe.py b/tests/test_fe.py index d1fb4bb..58f4583 100644 --- a/tests/test_fe.py +++ b/tests/test_fe.py @@ -5,15 +5,15 @@ from datetime import datetime import pytest - from facho import fe + import helpers def test_xmlsigned_build(monkeypatch): - # openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1 -newkey rsa:2048 -keyout example.key -out example.pem - # openssl pkcs12 -export -out example.p12 -inkey example.key -in example.pem + #openssl req -x509 -sha256 -nodes -subj "/CN=test" -days 1 -newkey rsa:2048 -keyout example.key -out example.pem + #openssl pkcs12 -export -out example.p12 -inkey example.key -in example.pem signer = fe.DianXMLExtensionSigner('./tests/example.p12') xml = fe.FeXML('Invoice', @@ -116,17 +116,3 @@ def test_xml_sign_dian_using_bytes(monkeypatch): xmlsigned = signer.sign_xml_string(xmlstring) assert "Signature" in xmlsigned - -def test_xml_signature_timestamp(monkeypatch): - xml = fe.FeXML( - 'Invoice', - 'http://www.dian.gov.co/contratos/facturaelectronica/v1') - xml.find_or_create_element( - '/fe:Invoice/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent') - ublextension = xml.fragment( - '/fe:Invoice/ext:UBLExtensions/ext:UBLExtension', append=True) - ublextension.find_or_create_element( - '/ext:UBLExtension/ext:ExtensionContent') - xmlstring = xml.tostring() - signer = fe.DianXMLExtensionSigner('./tests/example.p12') - xmlsigned = signer.sign_xml_string(xmlstring) diff --git a/tests/test_fe_form.py b/tests/test_fe_form.py index cfbc060..ca7f4b9 100644 --- a/tests/test_fe_form.py +++ b/tests/test_fe_form.py @@ -5,47 +5,24 @@ """Tests for `facho` package.""" +import pytest from datetime import datetime import io import zipfile import facho.fe.form as form from facho import fe -from facho.fe.form_xml import ( - DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML) - -from fixtures import ( - simple_invoice, - simple_invoice_without_lines, - simple_credit_note_without_lines, - simple_debit_note_without_lines) - -try: - CUDE_ = open("./tests/cude.txt", 'r').read().strip() -except FileNotFoundError: - raise Exception("Archivo Cude No encontrado") - -CUFE_ = ( - '8bb918b19ba22a694f1da' - '11c643b5e9de39adf60311c' - 'f179179e9b33381030bcd4c3c' - '3f156c506ed5908f9276f5bd9b4') - -simple_invoice = simple_invoice -simple_invoice_without_lines = simple_invoice_without_lines -simple_credit_note_without_lines = simple_credit_note_without_lines -simple_debit_note_without_lines = simple_debit_note_without_lines +from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML +from fixtures import * def test_invoicesimple_build(simple_invoice): xml = DIANInvoiceXML(simple_invoice) - supplier_name = xml.get_element_text( - '/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name') + supplier_name = xml.get_element_text('/fe:Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name') assert supplier_name == simple_invoice.invoice_supplier.name - customer_name = xml.get_element_text( - '/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name') + customer_name = xml.get_element_text('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyName/cbc:Name') assert customer_name == simple_invoice.invoice_customer.name @@ -65,11 +42,9 @@ def test_invoicesimple_xml_signed(monkeypatch, simple_invoice): print(xml.tostring()) xml.add_extension(signer) - elem = xml.get_element( - '/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent/ds:Signature') + elem = xml.get_element('/fe:Invoice/ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent/ds:Signature') assert elem.text is not None - def test_invoicesimple_zip(simple_invoice): xml_invoice = DIANInvoiceXML(simple_invoice) @@ -91,34 +66,26 @@ def test_invoicesimple_zip(simple_invoice): def test_bug_cbcid_empty_on_invoice_line(simple_invoice): xml_invoice = DIANInvoiceXML(simple_invoice) - cbc_id = xml_invoice.get_element_text( - '/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int) + cbc_id = xml_invoice.get_element_text('/fe:Invoice/cac:InvoiceLine[1]/cbc:ID', format_=int) assert cbc_id == 1 - def test_invoice_line_count_numeric(simple_invoice): xml_invoice = DIANInvoiceXML(simple_invoice) - count = xml_invoice.get_element_text( - '/fe:Invoice/cbc:LineCountNumeric', format_=int) + count = xml_invoice.get_element_text('/fe:Invoice/cbc:LineCountNumeric', format_=int) assert count == len(simple_invoice.invoice_lines) - def test_invoice_profileexecutionid(simple_invoice): xml_invoice = DIANInvoiceXML(simple_invoice) cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice) xml_invoice.add_extension(cufe_extension) - id_ = xml_invoice.get_element_text( - '/fe:Invoice/cbc:ProfileExecutionID', format_=int) + id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:ProfileExecutionID', format_=int) assert id_ == 2 - def test_invoice_invoice_type_code(simple_invoice): xml_invoice = DIANInvoiceXML(simple_invoice) - id_ = xml_invoice.get_element_text( - '/fe:Invoice/cbc:InvoiceTypeCode', format_=int) + id_ = xml_invoice.get_element_text('/fe:Invoice/cbc:InvoiceTypeCode', format_=int) assert id_ == 1 - def test_invoice_totals(simple_invoice_without_lines): simple_invoice = simple_invoice_without_lines simple_invoice.invoice_ident = '323200000129' @@ -126,50 +93,39 @@ def test_invoice_totals(simple_invoice_without_lines): simple_invoice.invoice_supplier.ident = '700085371' simple_invoice.invoice_customer.ident = '800199436' simple_invoice.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='producto', - item=form.StandardItem(9999), - price=form.Price(form.Amount(1_500_000), '01', ''), - tax=form.TaxTotal( - subtotals=[ + quantity = form.Quantity(1, '94'), + description = 'producto', + item = form.StandardItem(9999), + price = form.Price(form.Amount(1_500_000), '01', ''), + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - scheme=form.TaxScheme('01'), - percent=19.0 - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + scheme = form.TaxScheme('01'), + percent = 19.0 + )]) )) simple_invoice.calculate() assert 1 == len(simple_invoice.invoice_lines) - assert form.Amount(1_500_000) == ( - simple_invoice.invoice_legal_monetary_total.line_extension_amount) - assert form.Amount(1_785_000) == ( - simple_invoice.invoice_legal_monetary_total.payable_amount) - + assert form.Amount(1_500_000) == simple_invoice.invoice_legal_monetary_total.line_extension_amount + assert form.Amount(1_785_000) == simple_invoice.invoice_legal_monetary_total.payable_amount def test_invoice_cufe(simple_invoice_without_lines): simple_invoice = simple_invoice_without_lines simple_invoice.invoice_ident = '323200000129' - simple_invoice.invoice_issue = datetime.strptime( - '2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z') - simple_invoice.invoice_supplier.ident = form.PartyIdentification( - '700085371', '5', '31') - simple_invoice.invoice_customer.ident = form.PartyIdentification( - '800199436', '5', '31') + simple_invoice.invoice_issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z') + simple_invoice.invoice_supplier.ident = form.PartyIdentification('700085371', '5', '31') + simple_invoice.invoice_customer.ident = form.PartyIdentification('800199436', '5', '31') simple_invoice.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity( - 1.00, '94'), - description='producto', - item=form.StandardItem(111), - price=form.Price(form.Amount(1_500_000), '01', ''), - tax=form.TaxTotal( - subtotals=[ + quantity = form.Quantity(1.00, '94'), + description = 'producto', + item = form.StandardItem(111), + price = form.Price(form.Amount(1_500_000), '01', ''), + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - scheme=form.TaxScheme('01'), - percent=19.0 - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + scheme = form.TaxScheme('01'), + percent = 19.0 + )]) )) simple_invoice.calculate() @@ -177,86 +133,65 @@ def test_invoice_cufe(simple_invoice_without_lines): cufe_extension = fe.DianXMLExtensionCUFE( simple_invoice, - tipo_ambiente=fe.AMBIENTE_PRODUCCION, - clave_tecnica='693ff6f2a553c3646a063436fd4dd9ded0311471' + tipo_ambiente = fe.AMBIENTE_PRODUCCION, + clave_tecnica = '693ff6f2a553c3646a063436fd4dd9ded0311471' ) formatVars = cufe_extension.formatVars() - - # NumFac + #NumFac assert formatVars[0] == '323200000129', "NumFac" - - # FecFac + #FecFac assert formatVars[1] == '2019-01-16', "FecFac" - - # HoraFac + #HoraFac assert formatVars[2] == '10:53:10-05:00', "HoraFac" - - # ValorBruto + #ValorBruto assert formatVars[3] == '1500000.00', "ValorBruto" - - # CodImpuesto1 + #CodImpuesto1 assert formatVars[4] == '01', "CodImpuesto1" - - # ValorImpuesto1 + #ValorImpuesto1 assert formatVars[5] == '285000.00', "ValorImpuesto1" - - # CodImpuesto2 + #CodImpuesto2 assert formatVars[6] == '04', "CodImpuesto2" - - # ValorImpuesto2 + #ValorImpuesto2 assert formatVars[7] == '0.00', "ValorImpuesto2" - - # CodImpuesto3 + #CodImpuesto3 assert formatVars[8] == '03', "CodImpuesto3" - - # ValorImpuesto3 + #ValorImpuesto3 assert formatVars[9] == '0.00', "ValorImpuesto3" - - # ValTotFac + #ValTotFac assert formatVars[10] == '1785000.00', "ValTotFac" - - # NitOFE + #NitOFE assert formatVars[11] == '700085371', "NitOFE" - - # NumAdq + #NumAdq assert formatVars[12] == '800199436', "NumAdq" - - # ClTec + #ClTec assert formatVars[13] == '693ff6f2a553c3646a063436fd4dd9ded0311471', "ClTec" - - # TipoAmbiente + #TipoAmbiente assert formatVars[14] == '1', "TipoAmbiente" xml_invoice.add_extension(cufe_extension) cufe = xml_invoice.get_element_text('/fe:Invoice/cbc:UUID') # RESOLUCION 004: pagina 689 - assert cufe == CUFE_ + assert cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' + def test_credit_note_cude(simple_credit_note_without_lines): simple_invoice = simple_credit_note_without_lines simple_invoice.invoice_ident = '8110007871' - simple_invoice.invoice_issue = datetime.strptime( - '2019-01-12 07:00:00-05:00', '%Y-%m-%d %H:%M:%S%z') - simple_invoice.invoice_supplier.ident = form.PartyIdentification( - '900373076', '5', '31') - simple_invoice.invoice_customer.ident = form.PartyIdentification( - '8355990', '5', '31') + simple_invoice.invoice_issue = datetime.strptime('2019-01-12 07:00:00-05:00', '%Y-%m-%d %H:%M:%S%z') + simple_invoice.invoice_supplier.ident = form.PartyIdentification('900373076', '5', '31') + simple_invoice.invoice_customer.ident = form.PartyIdentification('8355990', '5', '31') simple_invoice.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity( - 1, '94'), - description='producto', - item=form.StandardItem(111), - price=form.Price( - form.Amount(5_000), '01', ''), - tax=form.TaxTotal( - subtotals=[ + quantity = form.Quantity(1, '94'), + description = 'producto', + item = form.StandardItem(111), + price = form.Price(form.Amount(5_000), '01', ''), + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - scheme=form.TaxScheme('01'), - percent=19.0 - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + scheme = form.TaxScheme('01'), + percent = 19.0 + )]) )) simple_invoice.calculate() @@ -265,39 +200,33 @@ def test_credit_note_cude(simple_credit_note_without_lines): cude_extension = fe.DianXMLExtensionCUDE( simple_invoice, '12301', - tipo_ambiente=fe.AMBIENTE_PRODUCCION, + tipo_ambiente = fe.AMBIENTE_PRODUCCION, ) xml_invoice.add_extension(cude_extension) cude = xml_invoice.get_element_text('/fe:CreditNote/cbc:UUID') # pag 612 - - assert cude == CUDE_ + assert cude == '907e4444decc9e59c160a2fb3b6659b33dc5b632a5008922b9a62f83f757b1c448e47f5867f2b50dbdb96f48c7681168' # pag 614 def test_debit_note_cude(simple_debit_note_without_lines): simple_invoice = simple_debit_note_without_lines simple_invoice.invoice_ident = 'ND1001' - simple_invoice.invoice_issue = datetime.strptime( - '2019-01-18 10:58:00-05:00', '%Y-%m-%d %H:%M:%S%z') - simple_invoice.invoice_supplier.ident = form.PartyIdentification( - '900197264', '5', '31') - simple_invoice.invoice_customer.ident = form.PartyIdentification( - '10254102', '5', '31') + simple_invoice.invoice_issue = datetime.strptime('2019-01-18 10:58:00-05:00', '%Y-%m-%d %H:%M:%S%z') + simple_invoice.invoice_supplier.ident = form.PartyIdentification('900197264', '5', '31') + simple_invoice.invoice_customer.ident = form.PartyIdentification('10254102', '5', '31') simple_invoice.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='producto', - item=form.StandardItem(111), - price=form.Price(form.Amount(30_000), '01', ''), - tax=form.TaxTotal( - subtotals=[ + quantity = form.Quantity(1, '94'), + description = 'producto', + item = form.StandardItem(111), + price = form.Price(form.Amount(30_000), '01', ''), + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - scheme=form.TaxScheme('04'), - percent=8.0 - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + scheme = form.TaxScheme('04'), + percent = 8.0 + )]) )) simple_invoice.calculate() @@ -306,7 +235,7 @@ def test_debit_note_cude(simple_debit_note_without_lines): cude_extension = fe.DianXMLExtensionCUDE( simple_invoice, '10201', - tipo_ambiente=fe.AMBIENTE_PRUEBAS, + tipo_ambiente = fe.AMBIENTE_PRUEBAS, ) build_vars = cude_extension.buildVars() assert build_vars['NumFac'] == 'ND1001' @@ -322,7 +251,8 @@ def test_debit_note_cude(simple_debit_note_without_lines): assert build_vars['Software-PIN'] == '10201' assert build_vars['TipoAmb'] == 2 - cude_composicion = "".join(cude_extension.formatVars()) + + cude_composicion = "".join(cude_extension.formatVars()) assert cude_composicion == 'ND10012019-01-1810:58:00-05:0030000.00010.00042400.00030.0032400.0090019726410254102102012' xml_invoice.add_extension(cude_extension) diff --git a/tests/test_form.py b/tests/test_form.py index 2e2559a..0476db8 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -6,117 +6,91 @@ """Tests for `facho` package.""" import pytest -# from datetime import datetime -# import io -# import zipfile +from datetime import datetime +import io +import zipfile import facho.fe.form as form -# from facho import fe - -from fixtures import ( - simple_invoice, - simple_invoice_without_lines, - simple_credit_note_without_lines, - simple_debit_note_without_lines) - -simple_invoice = simple_invoice -simple_invoice_without_lines = simple_invoice_without_lines -simple_credit_note_without_lines = simple_credit_note_without_lines -simple_debit_note_without_lines = simple_debit_note_without_lines - +from facho import fe def test_invoice_legalmonetary(): inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='producto facho', - item=form.StandardItem(9999), - price=form.Price( - amount=form.Amount(100.0), - type_code='01', - type='x' + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem(9999), + price = form.Price( + amount = form.Amount(100.0), + type_code = '01', + type = 'x' ), - tax=form.TaxTotal( - subtotals=[ + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - percent=19.0, - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + percent = 19.0, + ) + ] + ) )) - inv.calculate() - assert inv.invoice_legal_monetary_total.line_extension_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.tax_exclusive_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.tax_inclusive_amount == ( - form.Amount(119.0)) - assert inv.invoice_legal_monetary_total.charge_total_amount == ( - form.Amount(0.0)) - + assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0) + assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0) def test_allowancecharge_as_discount(): discount = form.AllowanceChargeAsDiscount(amount=form.Amount(1000.0)) - - assert discount.isDiscount() - - + assert discount.isDiscount() == True + def test_FAU10(): inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='productofacho', - item=form.StandardItem(9999), - price=form.Price( - amount=form.Amount(100.0), - type_code='01', - type='x' + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem(9999), + price = form.Price( + amount = form.Amount(100.0), + type_code = '01', + type = 'x' ), - tax=form.TaxTotal( - subtotals=[ + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - percent=19.0)]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + percent = 19.0, + ) + ] + ) )) - inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0))) inv.calculate() - assert inv.invoice_legal_monetary_total.line_extension_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.tax_exclusive_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.tax_inclusive_amount == ( - form.Amount(119.0)) - assert inv.invoice_legal_monetary_total.charge_total_amount == ( - form.Amount(19.0)) + assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(119.0) + assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(19.0) def test_FAU14(): inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='productofacho', - item=form.StandardItem(9999), - price=form.Price( - amount=form.Amount(100.0), - type_code='01', - type='x' + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem(9999), + price = form.Price( + amount = form.Amount(100.0), + type_code = '01', + type = 'x' ), - tax=form.TaxTotal( - subtotals=[ + tax = form.TaxTotal( + subtotals = [ form.TaxSubTotal( - percent=19.0, - )]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + percent = 19.0, + ) + ] + ) )) - inv.add_allowance_charge(form.AllowanceCharge( - amount=form.Amount(19.0))) - inv.add_prepaid_payment(form.PrePaidPayment( - paid_amount=form.Amount(50.0))) + inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0))) + inv.add_prepaid_payment(form.PrePaidPayment(paid_amount = form.Amount(50.0))) inv.calculate() wants = form.Amount(119.0 + 19.0 - 50.0) @@ -126,42 +100,38 @@ def test_FAU14(): def test_invalid_tipo_operacion_nota_debito(): reference = form.InvoiceDocumentReference( - ident='11111', - uuid='21312312', - date='2020-05-05' + ident = '11111', + uuid = '21312312', + date = '2020-05-05' ) inv = form.DebitNote(reference) with pytest.raises(ValueError): inv.set_operation_type(22) - def test_valid_tipo_operacion_nota_debito(): reference = form.InvoiceDocumentReference( - ident='11111', - uuid='21312312', - date='2020-05-05' + ident = '11111', + uuid = '21312312', + date = '2020-05-05' ) inv = form.DebitNote(reference) inv.set_operation_type('30') - def test_invalid_tipo_operacion_nota_credito(): reference = form.InvoiceDocumentReference( - ident='11111', - uuid='21312312', - date='2020-05-05' + ident = '11111', + uuid = '21312312', + date = '2020-05-05' ) - inv = form.DebitNote(reference) with pytest.raises(ValueError): inv.set_operation_type('990') - def test_valid_tipo_operacion_nota_credito(): reference = form.InvoiceDocumentReference( - ident='11111', - uuid='21312312', - date='2020-05-05' + ident = '11111', + uuid = '21312312', + date = '2020-05-05' ) inv = form.CreditNote(reference) inv.set_operation_type('20') @@ -171,52 +141,41 @@ def test_quantity(): quantity1 = form.Quantity(10, '94') assert quantity1 * form.Amount(3) == form.Amount(30) - def test_invoice_line_quantity_without_taxes(): line = form.InvoiceLine( - quantity=form.Quantity(10, '94'), - description='', - item=form.StandardItem('test', 9999), - price=form.Price( - amount=form.Amount(30.00), - type_code='01', - type='x'), - tax=form.TaxTotal(subtotals=[]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) - ) + quantity = form.Quantity(10, '94'), + description = '', + item = form.StandardItem('test', 9999), + price = form.Price( + amount = form.Amount(30.00), + type_code = '01', + type = 'x' + ), + tax = form.TaxTotal(subtotals=[])) line.calculate() assert line.total_amount == form.Amount(300) assert line.tax_amount == form.Amount(0) - def test_invoice_legalmonetary_with_taxes(): inv = form.NationalSalesInvoice() inv.add_invoice_line(form.InvoiceLine( - quantity=form.Quantity(1, '94'), - description='productofacho', - item=form.StandardItem(9999), - price=form.Price( - amount=form.Amount(100.0), - type_code='01', - type='x' + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem(9999), + price = form.Price( + amount = form.Amount(100.0), + type_code = '01', + type = 'x' ), - tax=form.TaxTotal(subtotals=[]), - withholding=form.WithholdingTaxTotal( - subtotals=[]) + tax = form.TaxTotal(subtotals=[]) )) inv.calculate() - assert inv.invoice_legal_monetary_total.line_extension_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.tax_exclusive_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.tax_inclusive_amount == ( - form.Amount(100.0)) - assert inv.invoice_legal_monetary_total.charge_total_amount == ( - form.Amount(0.0)) - assert inv.invoice_legal_monetary_total.payable_amount == ( - form.Amount(100.0)) + assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.tax_exclusive_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.tax_inclusive_amount == form.Amount(100.0) + assert inv.invoice_legal_monetary_total.charge_total_amount == form.Amount(0.0) + assert inv.invoice_legal_monetary_total.payable_amount == form.Amount(100.0) def test_invoice_ident_prefix_automatic_invalid(): @@ -224,7 +183,6 @@ def test_invoice_ident_prefix_automatic_invalid(): with pytest.raises(ValueError): inv.set_ident('SETPQJQJ1234567') - def test_invoice_ident_prefix_automatic(): inv = form.NationalSalesInvoice() inv.set_ident('SETP1234567') @@ -242,15 +200,13 @@ def test_invoice_ident_prefix_automatic(): inv.set_ident('1234567') assert inv.invoice_ident_prefix == '' - def test_invoice_ident_prefix_manual(): inv = form.NationalSalesInvoice() inv.set_ident('SETP1234567') inv.set_ident_prefix('SETA') assert inv.invoice_ident_prefix == 'SETA' - def test_invoice_ident_prefix_automatic_debit(): - inv = form.DebitNote(form.BillingReference('', '', '')) + inv = form.DebitNote(form.BillingReference('','','')) inv.set_ident('ABCDEF1234567') assert inv.invoice_ident_prefix == 'ABCDEF' diff --git a/tests/test_form_xml.py b/tests/test_form_xml.py index 44a4ccc..74f8d9f 100644 --- a/tests/test_form_xml.py +++ b/tests/test_form_xml.py @@ -6,24 +6,13 @@ """Tests for `facho` package.""" import pytest -# from datetime import datetime +from datetime import datetime import copy from facho.fe import form from facho.fe import form_xml -# from fixtures import * - -from fixtures import ( - simple_invoice, - simple_invoice_without_lines, - simple_credit_note_without_lines, - simple_debit_note_without_lines) - -simple_invoice = simple_invoice -simple_invoice_without_lines = simple_invoice_without_lines -simple_credit_note_without_lines = simple_credit_note_without_lines -simple_debit_note_without_lines = simple_debit_note_without_lines +from fixtures import * def test_import_DIANInvoiceXML(): try: @@ -38,82 +27,70 @@ def test_import_DIANDebitNoteXML(): except AttributeError: pytest.fail("unexpected not found") - def test_import_DIANCreditNoteXML(): try: form_xml.DIANCreditNoteXML except AttributeError: pytest.fail("unexpected not found") +def test_allowance_charge_in_invoice(simple_invoice_without_lines): + inv = copy.copy(simple_invoice_without_lines) + inv.add_invoice_line(form.InvoiceLine( + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem(9999), + price = form.Price( + amount = form.Amount(100.0), + type_code = '01', + type = 'x' + ), + tax = form.TaxTotal( + subtotals = [ + form.TaxSubTotal( + percent = 19.0, + ) + ] + ) + )) + inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0))) + inv.calculate() + + xml = form_xml.DIANInvoiceXML(inv) + assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1' + assert xml.get_element_text('./cac:AllowanceCharge/cbc:ChargeIndicator') == 'true' + assert xml.get_element_text('./cac:AllowanceCharge/cbc:Amount') == '19.0' + assert xml.get_element_text('./cac:AllowanceCharge/cbc:BaseAmount') == '100.0' -# def test_allowance_charge_in_invoice(simple_invoice_without_lines): -# inv = copy.copy(simple_invoice_without_lines) -# inv.add_invoice_line(form.InvoiceLine( -# quantity=form.Quantity(1, '94'), -# description='productofacho', -# item=form.StandardItem(9999), -# price=form.Price( -# amount=form.Amount(100.0), -# type_code='01', -# type='x' -# ), -# tax=form.TaxTotal( -# subtotals=[ -# form.TaxSubTotal( -# percent=19.0, -# )]), -# withholding=form.WithholdingTaxTotal( -# subtotals=[]) -# )) +def test_allowance_charge_in_invoice_line(simple_invoice_without_lines): + inv = copy.copy(simple_invoice_without_lines) + inv.add_invoice_line(form.InvoiceLine( + quantity = form.Quantity(1, '94'), + description = 'producto facho', + item = form.StandardItem(9999), + price = form.Price( + amount = form.Amount(100.0), + type_code = '01', + type = 'x' + ), + tax = form.TaxTotal( + subtotals = [ + form.TaxSubTotal( + percent = 19.0, + ) + ] + ), + allowance_charge = [ + form.AllowanceChargeAsDiscount(amount=form.Amount(10.0)) + ] + )) + inv.calculate() -# inv.add_allowance_charge(form.AllowanceCharge(amount=form.Amount(19.0))) -# inv.calculate() + # se aplico descuento + assert inv.invoice_legal_monetary_total.line_extension_amount == form.Amount(90.0) + + xml = form_xml.DIANInvoiceXML(inv) -# xml = form_xml.DIANInvoiceXML(inv) -# assert xml.get_element_text('./cac:AllowanceCharge/cbc:ID') == '1' -# assert xml.get_element_text( -# './cac:AllowanceCharge/cbc:ChargeIndicator') == 'true' -# assert xml.get_element_text( -# './cac:AllowanceCharge/cbc:Amount') == '19.0' -# assert xml.get_element_text( -# './cac:AllowanceCharge/cbc:BaseAmount') == '100.0' - - -# def test_allowance_charge_in_invoice_line(simple_invoice_without_lines): -# inv = copy.copy(simple_invoice_without_lines) -# inv.add_invoice_line(form.InvoiceLine( -# quantity=form.Quantity(1, '94'), -# description='producto facho', -# item=form.StandardItem(9999), -# price=form.Price( -# amount=form.Amount(100.0), -# type_code='01', -# type='x' -# ), -# tax=form.TaxTotal( -# subtotals=[ -# form.TaxSubTotal( -# percent=19.0, -# )]), -# withholding=form.WithholdingTaxTotal( -# subtotals=[]), -# allowance_charge=[ -# form.AllowanceChargeAsDiscount(amount=form.Amount(10.0)) -# ] -# )) -# inv.calculate() - -# # se aplico descuento -# assert inv.invoice_legal_monetary_total.line_extension_amount == ( -# form.Amount(90.0)) - -# xml = form_xml.DIANInvoiceXML(inv) - -# with pytest.raises(AttributeError): -# assert xml.get_element_text( -# '/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1' -# xml.get_element_text( -# '/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1' -# xml.get_element_text( -# '/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount' -# ) == '100.0' + with pytest.raises(AttributeError): + assert xml.get_element_text('/fe:Invoice/cac:AllowanceCharge/cbc:ID') == '1' + xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:ID') == '1' + xml.get_element_text('/fe:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:BaseAmount') == '100.0' diff --git a/tests/test_nomina.py b/tests/test_nomina.py index 961bb77..a924ece 100644 --- a/tests/test_nomina.py +++ b/tests/test_nomina.py @@ -4,467 +4,597 @@ # this repository contains the full copyright notices and license terms. """Tests for `facho` package.""" -# import re -# import pytest +import pytest -# from facho import fe +from facho import fe -# import helpers +import helpers -# def assert_error(errors, msg): -# for error in errors: -# if str(error) == msg: -# return True +def assert_error(errors, msg): + for error in errors: + if str(error) == msg: + return True -# raise "wants error: %s" % (msg) + raise "wants error: %s" % (msg) -# def test_adicionar_devengado_Basico(): -# nomina = fe.nomina.DIANNominaIndividual() +def test_adicionar_devengado_Basico(): + nomina = fe.nomina.DIANNominaIndividual() -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 30, -# sueldo_trabajado = fe.nomina.Amount(1_000_000) -# )) + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 30, + sueldo_trabajado = fe.nomina.Amount(1_000_000) + )) -# xml = nomina.toFachoXML() -# assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30' -# assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') == '1000000.00' + xml = nomina.toFachoXML() + assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30' + assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') == '1000000.00' -# def test_adicionar_devengado_transporte(): -# nomina = fe.nomina.DIANNominaIndividual() +def test_adicionar_devengado_transporte(): + nomina = fe.nomina.DIANNominaIndividual() -# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte( -# auxilio_transporte = fe.nomina.Amount(2_000_000) -# )) + nomina.adicionar_devengado(fe.nomina.DevengadoTransporte( + auxilio_transporte = fe.nomina.Amount(2_000_000) + )) -# xml = nomina.toFachoXML() + xml = nomina.toFachoXML() -# assert xml.get_element_attribute('/nomina:NominaIndividual/Devengados/Transporte', 'AuxilioTransporte') == '2000000.0' + assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Transporte', 'AuxilioTransporte') == '2000000.0' -# def test_adicionar_devengado_comprobante_total(): -# nomina = fe.nomina.DIANNominaIndividual() +def test_adicionar_devengado_comprobante_total(): + nomina = fe.nomina.DIANNominaIndividual() -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 60, -# sueldo_trabajado = fe.nomina.Amount(2_000_000) -# )) + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(2_000_000) + )) -# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( -# porcentaje = fe.nomina.Amount(19), -# deduccion = fe.nomina.Amount(1_000_000) -# )) + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1_000_000) + )) - -# xml = nomina.toFachoXML() - -# assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '1000000.00' - -# def test_adicionar_devengado_comprobante_total_cero(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 60, -# sueldo_trabajado = fe.nomina.Amount(1_000_000) -# )) - -# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( -# porcentaje = fe.nomina.Amount(19), -# deduccion = fe.nomina.Amount(1_000_000) -# )) - -# xml = nomina.toFachoXML() - -# assert xml.get_element_text('/nomina:NominaIndividual/ComprobanteTotal') == '0.00' - -# def test_adicionar_devengado_transporte_muchos(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte( -# auxilio_transporte = fe.nomina.Amount(2_000_000) -# )) - -# nomina.adicionar_devengado(fe.nomina.DevengadoTransporte( -# auxilio_transporte = fe.nomina.Amount(3_000_000) -# )) - -# xml = nomina.toFachoXML() -# print(xml) -# assert xml.get_element_text('/nomina:NominaIndividual/DevengadosTotal') == '5000000.00' - -# def test_adicionar_deduccion_salud(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 60, -# sueldo_trabajado = fe.nomina.Amount(1000) -# )) - -# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( -# porcentaje = fe.nomina.Amount(19), -# deduccion = fe.nomina.Amount(1000) -# )) - -# xml = nomina.toFachoXML() -# print(xml) -# assert xml.get_element_text('/nomina:NominaIndividual/DeduccionesTotal') == '1000.00' - -# def test_nomina_obligatorios_segun_anexo_tecnico(): -# nomina = fe.nomina.DIANNominaIndividual() - -# errors = nomina.validate() - -# assert_error(errors, 'se requiere Periodo') -# assert_error(errors, 'se requiere DevengadoBasico') -# assert_error(errors, 'se requiere DeduccionSalud') -# assert_error(errors, 'se requiere DeduccionFondoPension') - -# def test_nomina_xml(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.asignar_metadata(fe.nomina.Metadata( -# novedad=fe.nomina.Novedad( -# activa = True, -# cune = "N0111" -# ), -# secuencia=fe.nomina.NumeroSecuencia( -# prefijo = 'N', -# consecutivo='00001' -# ), -# lugar_generacion=fe.nomina.Lugar( -# pais = fe.nomina.Pais( -# code = 'CO' -# ), -# departamento = fe.nomina.Departamento( -# code = '05' -# ), -# municipio = fe.nomina.Municipio( -# code = '05001' -# ), -# ), -# proveedor=fe.nomina.Proveedor( -# nit='999999', -# dv=2, -# software_id='xx', -# software_pin='12', -# razon_social='facho' -# ) -# )) - -# nomina.asignar_informacion_general(fe.nomina.InformacionGeneral( -# fecha_generacion = '2020-01-16', -# hora_generacion = '1053:10-05:00', -# tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION, -# software_pin = '693', -# tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_NORMAL, -# periodo_nomina = fe.nomina.PeriodoNomina(code='1'), -# tipo_moneda = fe.nomina.TipoMoneda(code='COP') -# )) - -# nomina.asignar_empleador(fe.nomina.Empleador( -# razon_social='facho', -# nit = '700085371', -# dv = '1', -# pais = fe.nomina.Pais( -# code = 'CO' -# ), -# departamento = fe.nomina.Departamento( -# code = '05' -# ), -# municipio = fe.nomina.Municipio( -# code = '05001' -# ), -# direccion = 'calle etrivial' -# )) - -# nomina.asignar_trabajador(fe.nomina.Trabajador( -# tipo_contrato = fe.nomina.TipoContrato( -# code = '1' -# ), -# alto_riesgo = False, -# tipo_documento = fe.nomina.TipoDocumento( -# code = '11' -# ), -# primer_apellido = 'gnu', -# segundo_apellido = 'emacs', -# primer_nombre = 'facho', -# lugar_trabajo = fe.nomina.LugarTrabajo( -# pais = fe.nomina.Pais(code='CO'), -# departamento = fe.nomina.Departamento(code='05'), -# municipio = fe.nomina.Municipio(code='05001'), -# direccion = 'calle facho' -# ), -# numero_documento = '800199436', -# tipo = fe.nomina.TipoTrabajador( -# code = '01' -# ), -# salario_integral = True, -# sueldo = fe.nomina.Amount(1_500_000) -# )) - -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 60, -# sueldo_trabajado = fe.nomina.Amount(3_500_000) -# )) - -# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( -# porcentaje = fe.nomina.Amount(19), -# deduccion = fe.nomina.Amount(1_000_000) -# )) - -# xml = nomina.toFachoXML() -# expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab' -# assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune -# assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'TipoXML') == '102' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@DepartamentoEstado') == '05' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@MunicipioCiudad') == '05001' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@NIT') == '999999' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@DV') == '2' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareID') == 'xx' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/ProveedorXML/@SoftwareSC') is not None -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/CodigoQR') == f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={expected_cune}" -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Empleador/@NIT') == '700085371' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Trabajador/@NumeroDocumento') == '800199436' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad') == 'True' -# assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/Novedad/@CUNENov') == 'N0111' - -# # confirmar el namespace -# assert 'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"' in xml.tostring() - -# def test_asignar_pago(): -# nomina = fe.nomina.DIANNominaIndividual() -# nomina.asignar_pago(fe.nomina.Pago( -# forma = fe.nomina.FormaPago(code='1'), -# metodo = fe.nomina.MetodoPago(code='1') -# )) - -# def test_nomina_xmlsign(monkeypatch): -# nomina = fe.nomina.DIANNominaIndividual() -# xml = nomina.toFachoXML() - -# signer = fe.nomina.DianXMLExtensionSigner('./tests/example.p12') -# xml.add_extension(signer) - -# print(xml.tostring()) -# elem = xml.get_element('/nomina:NominaIndividual/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature') -# assert elem is not None - - - -# def test_nomina_devengado_horas_extras_nocturnas(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnas( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENs/HEN', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' - -# def test_nomina_devengado_horas_recargo_nocturno(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturno( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNs/HRN', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' - -# def test_nomina_devengado_horas_extras_diarias_dominicales_y_festivos(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiariasDominicalesYFestivos( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDDFs/HEDDF', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' - -# def test_nomina_devengado_horas_recargo_diarias_dominicales_y_festivos(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoDiariasDominicalesYFestivos( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRDDFs/HRDDF', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' - - -# def test_nomina_devengado_horas_extras_nocturnas_dominicales_y_festivos(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnasDominicalesYFestivos( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HENDFs/HENDF', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' - -# def test_nomina_devengado_horas_recargo_nocturno_dominicales_y_festivos(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturnoDominicalesYFestivos( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element('/nomina:NominaIndividual/Devengados/HRNDFs/HRNDF', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' - -# def test_fecha_validacion(): -# with pytest.raises(ValueError) as e: -# fe.nomina.Fecha('535-35-3') + + xml = nomina.toFachoXML() + + assert xml.get_element_text('/fe:NominaIndividual/ComprobanteTotal') == '1000000.00' + +def test_adicionar_devengado_comprobante_total_cero(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(1_000_000) + )) + + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1_000_000) + )) + + xml = nomina.toFachoXML() + + assert xml.get_element_text('/fe:NominaIndividual/ComprobanteTotal') == '0.00' + +def test_adicionar_devengado_transporte_muchos(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoTransporte( + auxilio_transporte = fe.nomina.Amount(2_000_000) + )) + + nomina.adicionar_devengado(fe.nomina.DevengadoTransporte( + auxilio_transporte = fe.nomina.Amount(3_000_000) + )) + + xml = nomina.toFachoXML() + print(xml) + assert xml.get_element_text('/fe:NominaIndividual/DevengadosTotal') == '5000000.00' + +def test_adicionar_deduccion_salud(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(1000) + )) + + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1000) + )) + + xml = nomina.toFachoXML() + print(xml) + assert xml.get_element_text('/fe:NominaIndividual/DeduccionesTotal') == '1000.00' + +def test_nomina_obligatorios_segun_anexo_tecnico(): + nomina = fe.nomina.DIANNominaIndividual() + + errors = nomina.validate() + + assert_error(errors, 'se requiere Periodo') + assert_error(errors, 'se requiere DevengadoBasico') + assert_error(errors, 'se requiere DeduccionSalud') + assert_error(errors, 'se requiere DeduccionFondoPension') + +def test_nomina_xml(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.asignar_metadata(fe.nomina.Metadata( + secuencia=fe.nomina.NumeroSecuencia( + prefijo = 'N', + consecutivo='00001' + ), + lugar_generacion=fe.nomina.Lugar( + pais = fe.nomina.Pais( + code = 'CO' + ), + departamento = fe.nomina.Departamento( + code = '05' + ), + municipio = fe.nomina.Municipio( + code = '05001' + ), + ), + proveedor=fe.nomina.Proveedor( + nit='999999', + dv=2, + software_id='xx', + software_pin='12', + razon_social='facho' + ) + )) + + nomina.asignar_informacion_general(fe.nomina.InformacionGeneral( + fecha_generacion = '2020-01-16', + hora_generacion = '1053:10-05:00', + tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION, + software_pin = '693', + periodo_nomina = fe.nomina.PeriodoNomina(code='1'), + tipo_moneda = fe.nomina.TipoMoneda(code='COP') + )) + + nomina.asignar_empleador(fe.nomina.Empleador( + razon_social='facho', + nit = '700085371', + dv = '1', + pais = fe.nomina.Pais( + code = 'CO' + ), + departamento = fe.nomina.Departamento( + code = '05' + ), + municipio = fe.nomina.Municipio( + code = '05001' + ), + direccion = 'calle etrivial' + )) + + nomina.asignar_trabajador(fe.nomina.Trabajador( + tipo_contrato = fe.nomina.TipoContrato( + code = '1' + ), + alto_riesgo = False, + tipo_documento = fe.nomina.TipoDocumento( + code = '11' + ), + primer_apellido = 'gnu', + segundo_apellido = 'emacs', + primer_nombre = 'facho', + lugar_trabajo = fe.nomina.LugarTrabajo( + pais = fe.nomina.Pais(code='CO'), + departamento = fe.nomina.Departamento(code='05'), + municipio = fe.nomina.Municipio(code='05001'), + direccion = 'calle facho' + ), + numero_documento = '800199436', + tipo = fe.nomina.TipoTrabajador( + code = '01' + ), + salario_integral = True, + sueldo = fe.nomina.Amount(1_500_000) + )) + + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(3_500_000) + )) + + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1_000_000) + )) + + xml = nomina.toFachoXML() + # TODO(bit4bit) no logro generar cune igual al del anexo tecnico + #assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == '16560dc8956122e84ffb743c817fe7d494e058a44d9ca3fa4c234c268b4f766003253fbee7ea4af9682dd57210f3bac2' + + expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab' + assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'fachoCUNE') == "N000012020-01-161053:10-05:003500000.001000000.002500000.007000853718001994361026931" + assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/LugarGeneracionXML/@DepartamentoEstado') == '05' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/LugarGeneracionXML/@MunicipioCiudad') == '05001' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@NIT') == '999999' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@DV') == '2' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@SoftwareID') == 'xx' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@fachoSoftwareSC') == 'xx12N00001' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@SoftwareSC') is not None + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/CodigoQR') == f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={expected_cune}" + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/Empleador/@NIT') == '700085371' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/Trabajador/@NumeroDocumento') == '800199436' + +def test_asignar_pago(): + nomina = fe.nomina.DIANNominaIndividual() + nomina.asignar_pago(fe.nomina.Pago( + forma = fe.nomina.FormaPago(code='1'), + metodo = fe.nomina.MetodoPago(code='1') + )) + +def test_nomina_xmlsign(monkeypatch): + nomina = fe.nomina.DIANNominaIndividual() + xml = nomina.toFachoXML() + + signer = fe.nomina.DianXMLExtensionSigner('./tests/example.p12') + xml.add_extension(signer) + + print(xml.tostring()) + elem = xml.get_element('/fe:NominaIndividual/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/ds:Signature') + assert elem is not None + + +def atest_nomina_ajuste_reemplazar(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() + + xml = nomina.toFachoXML() + print(xml) + assert False + + +def test_adicionar_reemplazar_devengado_comprobante_total(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() + + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(2_000_000) + )) + + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1_000_000) + )) + + xml = nomina.toFachoXML() + + assert xml.get_element_text('/fe:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00' + + +def test_adicionar_reemplazar_asignar_predecesor(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() + + nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor( + numero = '123456', + cune = 'ABC123456', + fecha_generacion = '2021-11-16' + )) + + xml = nomina.toFachoXML() + print(xml.tostring()) + assert xml.get_element_text_or_attribute('/fe:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456' + assert xml.get_element_text_or_attribute('/fe:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456' + assert xml.get_element_text_or_attribute('/fe:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16' + + +def test_adicionar_reemplazar_eliminar_predecesor_opcional(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() + + nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor( + numero = '123456', + cune = 'ABC123456', + fecha_generacion = '2021-11-16' + )) + + xml = nomina.toFachoXML() + print(xml.tostring()) + + assert xml.get_element('/fe:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None + assert xml.get_element('/fe:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None + +def test_adicionar_eliminar_reemplazar_predecesor_opcional(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar() + + nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor( + numero = '123456', + cune = 'ABC123456', + fecha_generacion = '2021-11-16' + )) + + xml = nomina.toFachoXML() + print(xml.tostring()) + assert xml.get_element('/fe:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None + assert xml.get_element('/fe:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None + +def test_adicionar_eliminar_devengado_comprobante_total(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar() + + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(2_000_000) + )) + + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1_000_000) + )) + + xml = nomina.toFachoXML() + + assert xml.get_element_text('/fe:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00' + +def test_adicionar_eliminar_asignar_predecesor(): + nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar() + + nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor( + numero = '123456', + cune = 'ABC123456', + fecha_generacion = '2021-11-16' + )) + + xml = nomina.toFachoXML() + print(xml.tostring()) + assert xml.get_element_text_or_attribute('/fe:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456' + assert xml.get_element_text_or_attribute('/fe:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456' + assert xml.get_element_text_or_attribute('/fe:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16' + +def test_nomina_devengado_horas_extras_diarias(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HEDs/HED', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + +def test_nomina_devengado_horas_extras_nocturnas(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnas( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HENs/HEN', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + +def test_nomina_devengado_horas_recargo_nocturno(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturno( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HRNs/HRN', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + +def test_nomina_devengado_horas_extras_diarias_dominicales_y_festivos(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiariasDominicalesYFestivos( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HEDDFs/HEDDF', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + +def test_nomina_devengado_horas_recargo_diarias_dominicales_y_festivos(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoDiariasDominicalesYFestivos( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HRDDFs/HRDDF', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + + +def test_nomina_devengado_horas_extras_nocturnas_dominicales_y_festivos(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasNocturnasDominicalesYFestivos( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HENDFs/HENDF', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + +def test_nomina_devengado_horas_recargo_nocturno_dominicales_y_festivos(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.adicionar_devengado(fe.nomina.DevengadoHorasRecargoNocturnoDominicalesYFestivos( + horas_extras=[ + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T19:09:55', + hora_fin='2021-11-30T20:09:55', + cantidad=1, + porcentaje=fe.nomina.Amount(1), + pago=fe.nomina.Amount(100) + ), + fe.nomina.DevengadoHoraExtra( + hora_inicio='2021-11-30T18:09:55', + hora_fin='2021-11-30T19:09:55', + cantidad=2, + porcentaje=fe.nomina.Amount(2), + pago=fe.nomina.Amount(200) + ) + ] + )) + + xml = nomina.toFachoXML() + extras = xml.get_element('/fe:NominaIndividual/Devengados/HRNDFs/HRNDF', multiple=True) + assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' + assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' + assert extras[0].get('Cantidad') == '1' + assert extras[0].get('Porcentaje') == '1.00' + assert extras[0].get('Pago') == '100.00' + assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' + assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' + assert extras[1].get('Cantidad') == '2' + assert extras[1].get('Porcentaje') == '2.00' + assert extras[1].get('Pago') == '200.00' + +def test_fecha_validacion(): + with pytest.raises(ValueError) as e: + fe.nomina.Fecha('535-35-3') diff --git a/tests/test_nomina_ajuste.py b/tests/test_nomina_ajuste.py deleted file mode 100644 index a20c159..0000000 --- a/tests/test_nomina_ajuste.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# This file is part of facho. The COPYRIGHT file at the top level of -# this repository contains the full copyright notices and license terms. - -"""Tests for `facho` package.""" -# import re - -# import pytest - -# from facho import fe - -# import helpers - -# def atest_nomina_ajuste_reemplazar(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() - -# xml = nomina.toFachoXML() -# print(xml) -# assert False - -# def test_nomina_ajuste_reemplazar_asignacion_tipo_xml(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() -# nomina.asignar_metadata(fe.nomina.Metadata( -# novedad=fe.nomina.Novedad( -# activa = True, -# cune = "N0111" -# ), -# secuencia=fe.nomina.NumeroSecuencia( -# prefijo = 'N', -# consecutivo='00001' -# ), -# lugar_generacion=fe.nomina.Lugar( -# pais = fe.nomina.Pais( -# code = 'CO' -# ), -# departamento = fe.nomina.Departamento( -# code = '05' -# ), -# municipio = fe.nomina.Municipio( -# code = '05001' -# ), -# ), -# proveedor=fe.nomina.Proveedor( -# nit='999999', -# dv=2, -# software_id='xx', -# software_pin='12', -# razon_social='facho' -# ) -# )) -# nomina.asignar_empleador(fe.nomina.Empleador( -# razon_social='facho', -# nit = '700085371', -# dv = '1', -# pais = fe.nomina.Pais( -# code = 'CO' -# ), -# departamento = fe.nomina.Departamento( -# code = '05' -# ), -# municipio = fe.nomina.Municipio( -# code = '05001' -# ), -# direccion = 'calle etrivial' -# )) - -# nomina.asignar_trabajador(fe.nomina.Trabajador( -# tipo_contrato = fe.nomina.TipoContrato( -# code = '1' -# ), -# alto_riesgo = False, -# tipo_documento = fe.nomina.TipoDocumento( -# code = '11' -# ), -# primer_apellido = 'gnu', -# segundo_apellido = 'emacs', -# primer_nombre = 'facho', -# lugar_trabajo = fe.nomina.LugarTrabajo( -# pais = fe.nomina.Pais(code='CO'), -# departamento = fe.nomina.Departamento(code='05'), -# municipio = fe.nomina.Municipio(code='05001'), -# direccion = 'calle facho' -# ), -# numero_documento = '800199436', -# tipo = fe.nomina.TipoTrabajador( -# code = '01' -# ), -# salario_integral = True, -# sueldo = fe.nomina.Amount(1_500_000) -# )) -# nomina.asignar_informacion_general(fe.nomina.InformacionGeneral( -# fecha_generacion = '2020-01-16', -# hora_generacion = '1053:10-05:00', -# tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION, -# software_pin = '693', -# tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_AJUSTES, -# periodo_nomina = fe.nomina.PeriodoNomina(code='1'), -# tipo_moneda = fe.nomina.TipoMoneda(code='COP') -# )) - -# xml = nomina.toFachoXML() - -# assert xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/InformacionGeneral', 'TipoXML') == '103' - - -# def test_adicionar_reemplazar_devengado_comprobante_total(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() - -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 60, -# sueldo_trabajado = fe.nomina.Amount(2_000_000) -# )) - -# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( -# porcentaje = fe.nomina.Amount(19), -# deduccion = fe.nomina.Amount(1_000_000) -# )) - -# xml = nomina.toFachoXML() - -# assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00' - - -# def test_adicionar_reemplazar_asignar_predecesor(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() - -# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor( -# numero = '123456', -# cune = 'ABC123456', -# fecha_generacion = '2021-11-16' -# )) - -# xml = nomina.toFachoXML() -# print(xml.tostring()) -# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456' -# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456' -# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16' - - -# def test_adicionar_reemplazar_eliminar_predecesor_opcional(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar() - -# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor( -# numero = '123456', -# cune = 'ABC123456', -# fecha_generacion = '2021-11-16' -# )) - -# xml = nomina.toFachoXML() -# print(xml.tostring()) - -# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None -# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None - -# def test_adicionar_eliminar_reemplazar_predecesor_opcional(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar() - -# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor( -# numero = '123456', -# cune = 'ABC123456', -# fecha_generacion = '2021-11-16' -# )) - -# xml = nomina.toFachoXML() -# print(xml.tostring()) -# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None -# assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None - -# def test_adicionar_eliminar_devengado_comprobante_total(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar() - -# nomina.adicionar_devengado(fe.nomina.DevengadoBasico( -# dias_trabajados = 60, -# sueldo_trabajado = fe.nomina.Amount(2_000_000) -# )) - -# nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( -# porcentaje = fe.nomina.Amount(19), -# deduccion = fe.nomina.Amount(1_000_000) -# )) - -# xml = nomina.toFachoXML() - -# assert xml.get_element_text('/nominaajuste:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00' - -# def test_adicionar_eliminar_asignar_predecesor(): -# nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar() - -# nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor( -# numero = '123456', -# cune = 'ABC123456', -# fecha_generacion = '2021-11-16' -# )) - -# xml = nomina.toFachoXML() -# print(xml.tostring()) -# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456' -# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456' -# assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16' - -# def test_nomina_devengado_horas_extras_diarias(): -# nomina = fe.nomina.DIANNominaIndividual() - -# nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias( -# horas_extras=[ -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T19:09:55', -# hora_fin='2021-11-30T20:09:55', -# cantidad=1, -# porcentaje=fe.nomina.Amount(1), -# pago=fe.nomina.Amount(100) -# ), -# fe.nomina.DevengadoHoraExtra( -# hora_inicio='2021-11-30T18:09:55', -# hora_fin='2021-11-30T19:09:55', -# cantidad=2, -# porcentaje=fe.nomina.Amount(2), -# pago=fe.nomina.Amount(200) -# ) -# ] -# )) - -# xml = nomina.toFachoXML() -# extras = xml.get_element( -# '/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True) -# assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55' -# assert extras[0].get('HoraFin') == '2021-11-30T20:09:55' -# assert extras[0].get('Cantidad') == '1' -# assert extras[0].get('Porcentaje') == '1.00' -# assert extras[0].get('Pago') == '100.00' -# assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55' -# assert extras[1].get('HoraFin') == '2021-11-30T19:09:55' -# assert extras[1].get('Cantidad') == '2' -# assert extras[1].get('Porcentaje') == '2.00' -# assert extras[1].get('Pago') == '200.00' diff --git a/tests/test_query.py b/tests/test_query.py index 2962977..0faf899 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -3,28 +3,22 @@ # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -# import pytest +import pytest import facho.fe.form as form from facho import fe +from facho.fe.form_xml import DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML -from facho.fe.form_xml import DIANInvoiceXML -# from facho.fe.form_xml import ( -# DIANInvoiceXML, DIANCreditNoteXML, DIANDebitNoteXML) - -from fixtures import simple_invoice +from fixtures import * from facho.fe.form import query -simple_invoice = simple_invoice - - def test_query_billing_reference(simple_invoice): xml = DIANInvoiceXML(simple_invoice) cufe_extension = fe.DianXMLExtensionCUFE(simple_invoice) xml.add_extension(cufe_extension) out = xml.tostring() - + reference = query.billing_reference(out, form.BillingReference) assert isinstance(reference, form.BillingReference) assert reference.ident != '' diff --git a/tox.ini b/tox.ini index f06bea6..f7186a0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,12 @@ [tox] -envlist = py39, py310, py311, py312, flake8 +envlist = py27, py34, py35, py36, flake8 [travis] python = - 3.9: py39 - 3.10: py310 - 3.11: py311 - 3.12: py312 + 3.6: py36 + 3.5: py35 + 3.4: py34 + 2.7: py27 [testenv:flake8] basepython = python