se adiciona archivo faltante de nomina, y se agregan mas deducciones
FossilOrigin-Name: afb6c19bd3d7f71dd3ceff6e95cc39aaf65d15707eef8535d092e68b34c05c65
This commit is contained in:
		| @@ -117,8 +117,12 @@ class LXMLBuilder: | ||||
|     def set_attribute(self,  elem, key, value): | ||||
|         elem.attrib[key] = value | ||||
|  | ||||
|     def remove_attributes(self, elem, keys): | ||||
|     @classmethod | ||||
|     def remove_attributes(cls, elem, keys, exclude = []): | ||||
|         for key in keys: | ||||
|             if key in exclude: | ||||
|                 continue | ||||
|  | ||||
|             try: | ||||
|                 del elem.attrib[key] | ||||
|             except KeyError: | ||||
| @@ -132,10 +136,8 @@ class LXMLBuilder: | ||||
|         attrs['encoding'] = attrs.pop('encoding', 'UTF-8') | ||||
|  | ||||
|         for el in elem.getiterator(): | ||||
|             try: | ||||
|                 del el.attrib['facho_placeholder'] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|             keys = filter(lambda key: key.startswith('facho_'), el.keys()) | ||||
|             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']: | ||||
| @@ -364,6 +366,20 @@ class FachoXML: | ||||
|         text = self.builder.get_text(elem) | ||||
|         return format_(text) | ||||
|  | ||||
|     def exist_element(self, xpath): | ||||
|         elem = self.get_element(xpath) | ||||
|  | ||||
|         if elem is None: | ||||
|             return False | ||||
|  | ||||
|         if elem.get('facho_placeholder') == 'True': | ||||
|             return False | ||||
|  | ||||
|         if elem.get('facho_optional') == 'True': | ||||
|             return False | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def _remove_facho_attributes(self, elem): | ||||
|         self.builder.remove_attributes(elem, ['facho_optional', 'facho_placeholder']) | ||||
|  | ||||
|   | ||||
							
								
								
									
										125
									
								
								facho/fe/nomina/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								facho/fe/nomina/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| from .. import fe | ||||
| from .. import form | ||||
|  | ||||
| from dataclasses import dataclass | ||||
|  | ||||
| class Amount(form.Amount): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class Devengado: | ||||
|     pass | ||||
|  | ||||
| @dataclass | ||||
| class DevengadoBasico(Devengado): | ||||
|     dias_trabajados: int | ||||
|     sueldo_trabajado: Amount | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.find_or_create_element('./Basico') | ||||
|          | ||||
|         fragment.set_attributes('/Basico', | ||||
|                                 # NIE069 | ||||
|                                 DiasTrabajados = str(self.dias_trabajados), | ||||
|                                 # NIE070 | ||||
|                                 SueldoTrabajado = str(self.sueldo_trabajado) | ||||
|                                 ) | ||||
|  | ||||
| @dataclass | ||||
| class DevengadoTransporte(Devengado): | ||||
|     auxilio_transporte: Amount = None | ||||
|     viatico_manutencion: Amount = None | ||||
|     viatico_manutencion_no_salarial: Amount = None | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_element('./Transporte', None, | ||||
|                              append_ = True, | ||||
|                              # NIE071 | ||||
|                              AuxilioTransporte = self.auxilio_transporte, | ||||
|                              # NIE072 | ||||
|                              ViaticoManuAlojS = self.viatico_manutencion, | ||||
|                              # NIE073 | ||||
|                              ViaticoManuAlojNS = self.viatico_manutencion_no_salarial | ||||
|                              ) | ||||
|  | ||||
| class Deduccion: | ||||
|     pass | ||||
|  | ||||
| @dataclass | ||||
| class DeduccionSalud(Deduccion): | ||||
|     porcentaje: Amount | ||||
|     deduccion: Amount | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_element('./Salud', None, | ||||
|                              append_ = True, | ||||
|                              # NIE161 | ||||
|                              Porcentaje = self.porcentaje, | ||||
|                              #  NIE163 | ||||
|                              Deduccion = self.deduccion | ||||
|                              ) | ||||
|  | ||||
| @dataclass | ||||
| class DeduccionFondoPension(Deduccion): | ||||
|     porcentaje: Amount | ||||
|     deduccion: Amount | ||||
|  | ||||
|     def apply(self, fragment): | ||||
|         fragment.set_element('./FondoPension', None, | ||||
|                              append_ = True, | ||||
|                              # NIE164 | ||||
|                              Porcentaje = self.porcentaje, | ||||
|                              #  NIE166 | ||||
|                              Deduccion = self.deduccion | ||||
|                              ) | ||||
|  | ||||
| class DIANNominaIndividualError(Exception): | ||||
|     pass | ||||
|  | ||||
| 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('./Devengados/Basico') | ||||
|         self.fexml.placeholder_for('./Devengados/Transporte', optional=True) | ||||
|  | ||||
|         self.devengados = self.fexml.fragment('./Devengados') | ||||
|         self.deducciones = self.fexml.fragment('./Deducciones') | ||||
|          | ||||
|     def adicionar_devengado(self, devengado): | ||||
|         if not isinstance(devengado, Devengado): | ||||
|             raise ValueError('se espera tipo Devengado') | ||||
|  | ||||
|         devengado.apply(self.devengados) | ||||
|  | ||||
|     def adicionar_deduccion(self, deduccion): | ||||
|         if not isinstance(deduccion, Deduccion): | ||||
|             raise ValueError('se espera tipo Devengado') | ||||
|  | ||||
|         deduccion.apply(self.deducciones) | ||||
|  | ||||
|     def validate(self): | ||||
|         """ | ||||
|         Valida requisitos segun anexo tecnico | ||||
|         """ | ||||
|         errors = [] | ||||
|  | ||||
|         def add_error(xpath, msg): | ||||
|             if not self.fexml.exist_element(xpath): | ||||
|                 errors.append(DIANNominaIndividualError(msg)) | ||||
|  | ||||
|         add_error('/fe:NominaIndividual/Devengados/Basico', | ||||
|                   'se requiere DevengadoBasico') | ||||
|  | ||||
|         add_error('/fe:NominaIndividual/Deducciones/Salud', | ||||
|                   'se requiere DeduccionSalud') | ||||
|  | ||||
|         add_error('/fe:NominaIndividual/Deducciones/FondoPension', | ||||
|                   'se requiere DeduccionFondoPension') | ||||
|  | ||||
|         return errors | ||||
|  | ||||
|     def toFachoXML(self): | ||||
|         return self.fexml | ||||
| @@ -351,3 +351,14 @@ def test_facho_xml_placeholder_optional_and_fragment_with_set_element(): | ||||
|  | ||||
|     assert xml.tostring() == '<root><A><AA prueba="OK"/></A></root>' | ||||
|     assert xml.get_element_attribute('/root/A/AA', 'prueba') == 'OK' | ||||
|  | ||||
| def test_facho_xml_exist_element(): | ||||
|     xml = facho.FachoXML('root') | ||||
|  | ||||
|     xml.placeholder_for('./A') | ||||
|     assert xml.exist_element('/root/A') == False | ||||
|     assert xml.tostring() == '<root><A/></root>' | ||||
|      | ||||
|     xml.find_or_create_element('./A') | ||||
|     assert xml.exist_element('/root/A') == True | ||||
|     assert xml.tostring() == '<root><A/></root>' | ||||
|   | ||||
| @@ -60,3 +60,19 @@ def test_adicionar_deduccion_salud(): | ||||
|     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>""" | ||||
|  | ||||
| def test_nomina_obligatorios_segun_anexo_tecnico(): | ||||
|     nomina = fe.nomina.DIANNominaIndividual() | ||||
|  | ||||
|     errors = nomina.validate() | ||||
|  | ||||
|     assert_error(errors, 'se requiere DevengadoBasico') | ||||
|     assert_error(errors, 'se requiere DeduccionSalud') | ||||
|     assert_error(errors, 'se requiere DeduccionFondoPension') | ||||
|  | ||||
| def assert_error(errors, msg): | ||||
|     for error in errors: | ||||
|         if str(error) == msg: | ||||
|             return True | ||||
|  | ||||
|     raise "wants error: %s" % (msg) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user