From a34963ca49ef3ff2b1edfea1541988540b3ac8aa Mon Sep 17 00:00:00 2001 From: bit4bit Date: Fri, 12 Nov 2021 02:56:17 +0000 Subject: [PATCH] se adicionan mas campos requeridos para NominaIndividual FossilOrigin-Name: 12c10fc4b3e4b69c665c8cde599a788266402977af62eed9b9a25459e1545f02 --- facho/fe/data/dian/codelist/__init__.py | 1 + facho/fe/nomina/__init__.py | 76 +++++++++++++++++++++---- tests/test_nomina.py | 38 +++++++++++-- 3 files changed, 100 insertions(+), 15 deletions(-) diff --git a/facho/fe/data/dian/codelist/__init__.py b/facho/fe/data/dian/codelist/__init__.py index f1ba264..feeaa45 100644 --- a/facho/fe/data/dian/codelist/__init__.py +++ b/facho/fe/data/dian/codelist/__init__.py @@ -101,3 +101,4 @@ SubTipoTrabajador = CodeList(path_for_codelist('SubTipoTrabajador-2.1.gc'), 'cod TipoContrato = CodeList(path_for_codelist('TipoContrato-2.1.gc'), 'code', 'name') PeriodoNomina = CodeList(path_for_codelist('PeriodoNomina-2.1.gc'), 'code', 'name') TipoMoneda = CodeList(path_for_codelist('TipoMoneda-2.1.gc'), 'code', 'name') +IdiomaISO6391 = CodeList(path_for_codelist('Idioma-2.1.gc'), 'iso-639-1', 'name') diff --git a/facho/fe/nomina/__init__.py b/facho/fe/nomina/__init__.py index 1b5cdc4..9acd574 100644 --- a/facho/fe/nomina/__init__.py +++ b/facho/fe/nomina/__init__.py @@ -17,23 +17,65 @@ from .deduccion import * from .trabajador import * from .empleador import * from .pago import * - -from .pais import Pais -from .departamento import Departamento -from .municipio import Municipio +from .lugar import Lugar from .amount import Amount from .exception import * @dataclass class NumeroSecuencia: + consecutivo: int numero: str def apply(self, fragment): fragment.set_attributes('./NumeroSecuenciaXML', + # NIE011 + Consecutivo=self.consecutivo, + # NIE012 Numero = self.numero) - + +@dataclass +class Proveedor: + nit: str + dv: int + software_id: str + software_sc: str + + def apply(self, fragment): + fragment.set_attributes('./ProveedorXML', + # NIE017 + NIT=self.nit, + # NIE018 + DV=self.dv, + # NIE019 + SoftwareID=self.software_id, + # NIE020 + SoftwareSC=self.software_sc, + ) + + def post_apply(self, fexml, fragment): + cune_xpath = fexml.xpath_from_root('/InformacionGeneral') + cune = fexml.get_element_attribute(cune_xpath, 'CUNE') + codigo_qr = f"https://catalogo‐vpfe.dian.gov.co/document/searchqr?documentkey={cune}" + fragment.set_attributes('./ProveedorXML', + CodigoQR=codigo_qr) + +@dataclass +class Metadata: + secuencia: NumeroSecuencia + # NIE013, NIE014, NIE015, NIE016 + lugar_generacion: Lugar + proveedor: Proveedor + + def apply(self, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml): + self.secuencia.apply(numero_secuencia_xml) + self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML') + self.proveedor.apply(proveedor_xml) + + def post_apply(self, fexml, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml): + self.proveedor.post_apply(fexml, proveedor_xml) + @dataclass class PeriodoNomina: code: str @@ -138,7 +180,12 @@ class DIANNominaXML: # layout, la dian requiere que los elementos # esten ordenados segun el anexo tecnico + self.fexml.placeholder_for('./UBLExtensions') + self.fexml.placeholder_for('./Novedad', optional=True) + self.fexml.placeholder_for('./Periodo') self.fexml.placeholder_for('./NumeroSecuenciaXML') + self.fexml.placeholder_for('./LugarGeneracionXML') + self.fexml.placeholder_for('./ProveedorXML') self.fexml.placeholder_for('./InformacionGeneral') self.fexml.placeholder_for('./Empleador') self.fexml.placeholder_for('./Trabajador') @@ -149,6 +196,8 @@ class DIANNominaXML: self.informacion_general_xml = self.fexml.fragment('./InformacionGeneral') self.numero_secuencia_xml = self.fexml.fragment('./NumeroSecuenciaXML') + self.lugar_generacion_xml = self.fexml.fragment('./LugarGeneracionXML') + self.proveedor_xml = self.fexml.fragment('./ProveedorXML') self.empleador = self.fexml.fragment('./Empleador') self.trabajador = self.fexml.fragment('./Trabajador') self.pago_xml = self.fexml.fragment('./Pago') @@ -156,12 +205,14 @@ class DIANNominaXML: self.deducciones = self.fexml.fragment('./Deducciones') self.informacion_general = None + self.metadata = None - def asignar_numero_secuencia(self, secuencia): - if not isinstance(secuencia, NumeroSecuencia): - raise ValueError('se espera tipo NumeroSecuencia') - secuencia.apply(self.numero_secuencia_xml) - + def asignar_metadata(self, metadata): + if not isinstance(metadata, Metadata): + raise ValueError('se espera tipo Metadata') + self.metadata = metadata + self.metadata.apply(self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml) + def asignar_informacion_general(self, general): if not isinstance(general, InformacionGeneral): raise ValueError('se espera tipo InformacionGeneral') @@ -246,13 +297,16 @@ class DIANNominaXML: self._devengados_total() self._deducciones_total() self._comprobante_total() - + if self.informacion_general is not None: #TODO(bit4bit) acoplamiento temporal # es importante el orden de ejecucion self.informacion_general.post_apply(self.fexml, self.informacion_general_xml) + if self.metadata is not None: + self.metadata.post_apply(self.fexml, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml) + return self.fexml def _comprobante_total(self): diff --git a/tests/test_nomina.py b/tests/test_nomina.py index 63e7837..961f9b2 100644 --- a/tests/test_nomina.py +++ b/tests/test_nomina.py @@ -109,12 +109,32 @@ def test_nomina_obligatorios_segun_anexo_tecnico(): assert_error(errors, 'se requiere DeduccionSalud') assert_error(errors, 'se requiere DeduccionFondoPension') -def test_nomina_cune(): +def test_nomina_xml(): nomina = fe.nomina.DIANNominaIndividual() - nomina.asignar_numero_secuencia(fe.nomina.NumeroSecuencia( - numero = 'N00001' - )) + nomina.asignar_metadata(fe.nomina.Metadata( + secuencia=fe.nomina.NumeroSecuencia( + numero = 'N00001', + consecutivo=232 + ), + 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_sc='yy' + ) + )) nomina.asignar_informacion_general(fe.nomina.InformacionGeneral( fecha_generacion = '2020-01-16', @@ -179,6 +199,16 @@ def test_nomina_cune(): # TODO(bit4bit) no logro generar cune igual al del anexo tecnico #assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == '16560dc8956122e84ffb743c817fe7d494e058a44d9ca3fa4c234c268b4f766003253fbee7ea4af9682dd57210f3bac2' assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '232' + 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/@SoftwareSC') == 'yy' + assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@CodigoQR') != None def assert_error(errors, msg): for error in errors: