primer bosquejo para cune
FossilOrigin-Name: 415cd610f28f743b8ae79e98f49d34c8d1fa4cf5f5a542c03226a642457b28c0
This commit is contained in:
		| @@ -353,7 +353,9 @@ class FachoXML: | ||||
|  | ||||
|     def get_element_attribute(self, xpath, attribute): | ||||
|         elem = self.get_element(xpath) | ||||
|         print(elem.attrib) | ||||
|         if elem is None: | ||||
|             raise ValueError("xpath %s not found" % (xpath)) | ||||
|  | ||||
|         return self.builder.get_attribute(elem, attribute) | ||||
|  | ||||
|     def get_element(self, xpath): | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| # creando las estructuras minimas necesaras. | ||||
|  | ||||
| from dataclasses import dataclass | ||||
| import hashlib | ||||
|  | ||||
| from .. import fe | ||||
| from .. import form | ||||
| @@ -14,23 +15,159 @@ from .devengado import * | ||||
| from .deduccion import * | ||||
|  | ||||
| from .amount import Amount | ||||
| from .exception import * | ||||
|  | ||||
| @dataclass | ||||
| class NumeroSecuencia: | ||||
|     numero: str | ||||
|  | ||||
| class DIANNominaIndividualError(Exception): | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_attributes('./NumeroSecuenciaXML', | ||||
|                                 Numero = self.numero) | ||||
|          | ||||
| @dataclass | ||||
| class InformacionGeneral: | ||||
|     class TIPO_AMBIENTE: | ||||
|         pass | ||||
|  | ||||
|     # TABLA 5.1.1 | ||||
|     @dataclass | ||||
|     class AMBIENTE_PRODUCCION(TIPO_AMBIENTE): | ||||
|         valor: str = '1' | ||||
|     @dataclass | ||||
|     class AMBIENTE_PRUEBAS(TIPO_AMBIENTE): | ||||
|         valor: str = '2' | ||||
|  | ||||
|     fecha_generacion: str | ||||
|     hora_generacion: str | ||||
|     tipo_ambiente: TIPO_AMBIENTE | ||||
|     software_pin: str | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_attributes('./InformacionGeneral', | ||||
|                                 # NIE022 | ||||
|                                 Version = 'V1.0: Documento Soporte de Pago de Nómina ElectrónicaV1.0', | ||||
|                                 # NIE023 | ||||
|                                 Ambiente = self.tipo_ambiente.valor, | ||||
|                                 # NIE202 | ||||
|                                 # TABLA 5.5.2 | ||||
|                                 # TODO(bit4bit) solo NominaIndividual | ||||
|                                 TipoXML = '102', | ||||
|                                 # NIE024 | ||||
|                                 CUNE = None, | ||||
|                                 # NIE025 | ||||
|                                 EncripCUNE = 'SHA-384', | ||||
|                                 # NIE026 | ||||
|                                 FechaGen = self.fecha_generacion, | ||||
|                                 # NIE027 | ||||
|                                 HoraGen = self.hora_generacion, | ||||
|                                 # TODO(bit4bit) resto... | ||||
|                                 # ..... | ||||
|                                 ) | ||||
|  | ||||
|     def post_apply(self, fexml, fragment): | ||||
|         devengados = map(lambda valor: Amount(valor), | ||||
|                          [ | ||||
|                              fexml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') | ||||
|                          ] | ||||
|                          ) | ||||
|         devengados_total = Amount(0.0) | ||||
|         for devengado in devengados: | ||||
|             devengados_total += devengado | ||||
|         fexml.set_element('/fe:NominaIndividual/DevengadosTotal', round(devengados_total,2)) | ||||
|  | ||||
|         # TODO | ||||
|         fexml.set_element('/fe:NominaIndividual/DeduccionesTotal', '1000000.00') | ||||
|  | ||||
|         # TODO | ||||
|         fexml.set_element('/fe:NominaIndividual/ComprobanteTotal', '2500000.00') | ||||
|  | ||||
|         # generar cune | ||||
|         campos = [ | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/NumeroSecuenciaXML', 'Numero'), | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'FechaGen'), | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'HoraGen'), | ||||
|             fexml.get_element_text('/fe:NominaIndividual/DevengadosTotal'), | ||||
|             fexml.get_element_text('/fe:NominaIndividual/DeduccionesTotal'), | ||||
|             fexml.get_element_text('/fe:NominaIndividual/ComprobanteTotal'), | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/Empleador', 'NIT'), | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/Trabajador', 'NumeroDocumento'), | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'TipoXML'), | ||||
|             self.software_pin, | ||||
|             fexml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'Ambiente') | ||||
|         ] | ||||
|         cune = "".join(campos) | ||||
|         print(cune) | ||||
|         h = hashlib.sha384() | ||||
|         h.update(cune.encode('utf-8')) | ||||
|         cune_hash = h.hexdigest() | ||||
|      | ||||
|         fragment.set_attributes( | ||||
|             './InformacionGeneral', | ||||
|             # NIE024 | ||||
|             CUNE = cune_hash | ||||
|         ) | ||||
|  | ||||
| @dataclass | ||||
| class Empleador: | ||||
|     nit: str | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_attributes('./Empleador', | ||||
|                                 NIT = self.nit) | ||||
|      | ||||
| @dataclass | ||||
| class Trabajador: | ||||
|     numero_documento: str | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_attributes('./Trabajador', | ||||
|                                 NumeroDocumento = self.numero_documento) | ||||
|  | ||||
| class DIANNominaIndividual: | ||||
|     def __init__(self): | ||||
|         self.fexml = fe.FeXML('NominaIndividual', 'http://www.dian.gov.co/contratos/facturaelectronica/v1') | ||||
|  | ||||
|         # layout, la dian requiere que los elementos | ||||
|         # esten ordenados segun el anexo tecnico | ||||
|         self.fexml.placeholder_for('./NumeroSecuenciaXML') | ||||
|         self.fexml.placeholder_for('./InformacionGeneral') | ||||
|         self.fexml.placeholder_for('./Empleador') | ||||
|         self.fexml.placeholder_for('./Trabajador') | ||||
|         self.fexml.placeholder_for('./Devengados/Basico') | ||||
|         self.fexml.placeholder_for('./Devengados/Transporte', optional=True) | ||||
|  | ||||
|  | ||||
|         self.informacion_general_xml = self.fexml.fragment('./InformacionGeneral') | ||||
|         self.numero_secuencia_xml = self.fexml.fragment('./NumeroSecuenciaXML') | ||||
|         self.empleador = self.fexml.fragment('./Empleador') | ||||
|         self.trabajador = self.fexml.fragment('./Trabajador') | ||||
|         self.devengados = self.fexml.fragment('./Devengados') | ||||
|         self.deducciones = self.fexml.fragment('./Deducciones') | ||||
|  | ||||
|         self.informacion_general = 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_informacion_general(self, general): | ||||
|         if not isinstance(general, InformacionGeneral): | ||||
|             raise ValueError('se espera tipo InformacionGeneral') | ||||
|         self.informacion_general = general | ||||
|         self.informacion_general.apply(self.informacion_general_xml) | ||||
|  | ||||
|     def asignar_empleador(self, empleador): | ||||
|         if not isinstance(empleador, Empleador): | ||||
|             raise ValueError('se espera tipo Empleador') | ||||
|         empleador.apply(self.empleador) | ||||
|  | ||||
|     def asignar_trabajador(self, trabajador): | ||||
|         if not isinstance(trabajador, Trabajador): | ||||
|             raise ValueError('se espera tipo Trabajador') | ||||
|         trabajador.apply(self.trabajador) | ||||
|          | ||||
|     def adicionar_devengado(self, devengado): | ||||
|         if not isinstance(devengado, Devengado): | ||||
|             raise ValueError('se espera tipo Devengado') | ||||
| @@ -83,4 +220,7 @@ class DIANNominaIndividual: | ||||
|         return errors | ||||
|  | ||||
|     def toFachoXML(self): | ||||
|         if self.informacion_general is not None: | ||||
|             self.informacion_general.post_apply(self.fexml, self.informacion_general_xml) | ||||
|  | ||||
|         return self.fexml | ||||
|   | ||||
| @@ -16,5 +16,5 @@ class DevengadoBasico(Devengado): | ||||
|                                 # NIE069 | ||||
|                                 DiasTrabajados = str(self.dias_trabajados), | ||||
|                                 # NIE070 | ||||
|                                 SueldoTrabajado = str(self.sueldo_trabajado) | ||||
|                                 SueldoTrabajado = round(self.sueldo_trabajado, 2) | ||||
|                                 ) | ||||
|   | ||||
| @@ -19,7 +19,7 @@ def test_adicionar_devengado_Basico(): | ||||
|  | ||||
|     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.0' | ||||
|     assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'SueldoTrabajado') == '1000000.00' | ||||
|  | ||||
| def test_adicionar_devengado_transporte(): | ||||
|     nomina = fe.nomina.DIANNominaIndividual() | ||||
| @@ -45,7 +45,7 @@ def test_adicionar_devengado_transporte_muchos(): | ||||
|  | ||||
|     xml = nomina.toFachoXML() | ||||
|     print(xml) | ||||
|     assert str(xml) == """<NominaIndividual xmlns:facho="http://git.disroot.org/Etrivial/facho" xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#"><Devengados><Basico/><Transporte AuxilioTransporte="2000000.0"/><Transporte AuxilioTransporte="3000000.0"/></Devengados><Deducciones/></NominaIndividual>""" | ||||
|     assert str(xml) == """<NominaIndividual xmlns:facho="http://git.disroot.org/Etrivial/facho" xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#"><NumeroSecuenciaXML/><InformacionGeneral/><Empleador/><Trabajador/><Devengados><Basico/><Transporte AuxilioTransporte="2000000.0"/><Transporte AuxilioTransporte="3000000.0"/></Devengados><Deducciones/></NominaIndividual>""" | ||||
|  | ||||
|  | ||||
| def test_adicionar_deduccion_salud(): | ||||
| @@ -58,7 +58,7 @@ def test_adicionar_deduccion_salud(): | ||||
|  | ||||
|     xml = nomina.toFachoXML() | ||||
|     print(xml) | ||||
|     assert str(xml) == """<NominaIndividual xmlns:facho="http://git.disroot.org/Etrivial/facho" xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#"><Devengados><Basico/></Devengados><Deducciones><Salud Porcentaje="19.0" Deduccion="1000.0"/></Deducciones></NominaIndividual>""" | ||||
|     assert str(xml) == """<NominaIndividual xmlns:facho="http://git.disroot.org/Etrivial/facho" xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:cdt="urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:sig="http://www.w3.org/2000/09/xmldsig#"><NumeroSecuenciaXML/><InformacionGeneral/><Empleador/><Trabajador/><Devengados><Basico/></Devengados><Deducciones><Salud Porcentaje="19.0" Deduccion="1000.0"/></Deducciones></NominaIndividual>""" | ||||
|  | ||||
| def test_nomina_obligatorios_segun_anexo_tecnico(): | ||||
|     nomina = fe.nomina.DIANNominaIndividual() | ||||
| @@ -70,6 +70,36 @@ def test_nomina_obligatorios_segun_anexo_tecnico(): | ||||
|     assert_error(errors, 'se requiere DeduccionSalud') | ||||
|     assert_error(errors, 'se requiere DeduccionFondoPension') | ||||
|  | ||||
| def test_nomina_cune(): | ||||
|     nomina = fe.nomina.DIANNominaIndividual() | ||||
|  | ||||
|     nomina.asignar_numero_secuencia(fe.nomina.NumeroSecuencia( | ||||
|         numero = 'N00001' | ||||
|         )) | ||||
|  | ||||
|     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' | ||||
|     )) | ||||
|  | ||||
|     nomina.asignar_empleador(fe.nomina.Empleador( | ||||
|         nit = '700085371', | ||||
|     )) | ||||
|  | ||||
|     nomina.asignar_trabajador(fe.nomina.Trabajador( | ||||
|         numero_documento = '800199436' | ||||
|     )) | ||||
|  | ||||
|     nomina.adicionar_devengado(fe.nomina.DevengadoBasico( | ||||
|         dias_trabajados = 60, | ||||
|         sueldo_trabajado = fe.nomina.Amount(3_500_000) | ||||
|     )) | ||||
|  | ||||
|     xml = nomina.toFachoXML() | ||||
|     assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == '16560dc8956122e84ffb743c817fe7d494e058a44d9ca3fa4c234c268b4f766003253fbee7ea4af9682dd57210f3bac2' | ||||
|  | ||||
| def assert_error(errors, msg): | ||||
|     for error in errors: | ||||
|         if str(error) == msg: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user