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): |     def get_element_attribute(self, xpath, attribute): | ||||||
|         elem = self.get_element(xpath) |         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) |         return self.builder.get_attribute(elem, attribute) | ||||||
|  |  | ||||||
|     def get_element(self, xpath): |     def get_element(self, xpath): | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| # creando las estructuras minimas necesaras. | # creando las estructuras minimas necesaras. | ||||||
|  |  | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
|  | import hashlib | ||||||
|  |  | ||||||
| from .. import fe | from .. import fe | ||||||
| from .. import form | from .. import form | ||||||
| @@ -14,10 +15,114 @@ from .devengado import * | |||||||
| from .deduccion import * | from .deduccion import * | ||||||
|  |  | ||||||
| from .amount import Amount | from .amount import Amount | ||||||
|  | from .exception import * | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class NumeroSecuencia: | ||||||
|  |     numero: str | ||||||
|  |  | ||||||
| class DIANNominaIndividualError(Exception): |     def apply(self, fragment): | ||||||
|     pass |         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: | class DIANNominaIndividual: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
| @@ -25,12 +130,44 @@ class DIANNominaIndividual: | |||||||
|  |  | ||||||
|         # layout, la dian requiere que los elementos |         # layout, la dian requiere que los elementos | ||||||
|         # esten ordenados segun el anexo tecnico |         # esten ordenados segun el anexo tecnico | ||||||
|  |         self.fexml.placeholder_for('./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/Basico') | ||||||
|         self.fexml.placeholder_for('./Devengados/Transporte', optional=True) |         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.devengados = self.fexml.fragment('./Devengados') | ||||||
|         self.deducciones = self.fexml.fragment('./Deducciones') |         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): |     def adicionar_devengado(self, devengado): | ||||||
|         if not isinstance(devengado, Devengado): |         if not isinstance(devengado, Devengado): | ||||||
|             raise ValueError('se espera tipo Devengado') |             raise ValueError('se espera tipo Devengado') | ||||||
| @@ -83,4 +220,7 @@ class DIANNominaIndividual: | |||||||
|         return errors |         return errors | ||||||
|  |  | ||||||
|     def toFachoXML(self): |     def toFachoXML(self): | ||||||
|  |         if self.informacion_general is not None: | ||||||
|  |             self.informacion_general.post_apply(self.fexml, self.informacion_general_xml) | ||||||
|  |  | ||||||
|         return self.fexml |         return self.fexml | ||||||
|   | |||||||
| @@ -16,5 +16,5 @@ class DevengadoBasico(Devengado): | |||||||
|                                 # NIE069 |                                 # NIE069 | ||||||
|                                 DiasTrabajados = str(self.dias_trabajados), |                                 DiasTrabajados = str(self.dias_trabajados), | ||||||
|                                 # NIE070 |                                 # NIE070 | ||||||
|                                 SueldoTrabajado = str(self.sueldo_trabajado) |                                 SueldoTrabajado = round(self.sueldo_trabajado, 2) | ||||||
|                                 ) |                                 ) | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ def test_adicionar_devengado_Basico(): | |||||||
|  |  | ||||||
|     xml = nomina.toFachoXML() |     xml = nomina.toFachoXML() | ||||||
|     assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30' |     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(): | def test_adicionar_devengado_transporte(): | ||||||
|     nomina = fe.nomina.DIANNominaIndividual() |     nomina = fe.nomina.DIANNominaIndividual() | ||||||
| @@ -45,7 +45,7 @@ def test_adicionar_devengado_transporte_muchos(): | |||||||
|  |  | ||||||
|     xml = nomina.toFachoXML() |     xml = nomina.toFachoXML() | ||||||
|     print(xml) |     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(): | def test_adicionar_deduccion_salud(): | ||||||
| @@ -58,7 +58,7 @@ def test_adicionar_deduccion_salud(): | |||||||
|  |  | ||||||
|     xml = nomina.toFachoXML() |     xml = nomina.toFachoXML() | ||||||
|     print(xml) |     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(): | def test_nomina_obligatorios_segun_anexo_tecnico(): | ||||||
|     nomina = fe.nomina.DIANNominaIndividual() |     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 DeduccionSalud') | ||||||
|     assert_error(errors, 'se requiere DeduccionFondoPension') |     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): | def assert_error(errors, msg): | ||||||
|     for error in errors: |     for error in errors: | ||||||
|         if str(error) == msg: |         if str(error) == msg: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user