diff --git a/facho/facho.py b/facho/facho.py
index fe769f2..d87eef0 100644
--- a/facho/facho.py
+++ b/facho/facho.py
@@ -98,10 +98,13 @@ class LXMLBuilder:
def set_text(self, elem, text):
elem.text = text
- def xpath(self, elem, xpath):
+ def xpath(self, elem, xpath, multiple=False):
elems = elem.xpath(xpath, namespaces=self.nsmap)
if elems:
- return elems[0]
+ if multiple:
+ return elems
+ else:
+ return elems[0]
return None
@@ -351,32 +354,63 @@ class FachoXML:
self.builder.set_attribute(elem, k, str(v))
return self
- def get_element_attribute(self, xpath, attribute):
- elem = self.get_element(xpath)
+ def get_element_attribute(self, xpath, attribute, multiple=False):
+ elem = self.get_element(xpath, multiple=multiple)
+
if elem is None:
raise ValueError("xpath %s not found" % (xpath))
- return self.builder.get_attribute(elem, attribute)
+ if multiple:
+ vals = []
+ for e in elem:
+ vals.append(self.builder.get_attribute(e, attribute))
+ return vals
+ else:
+ return self.builder.get_attribute(elem, attribute)
- def get_element(self, xpath):
+ def get_element(self, xpath, multiple=False):
xpath = self.fragment_prefix + self._path_xpath_for(xpath)
- return self.builder.xpath(self.root, xpath)
+ return self.builder.xpath(self.root, xpath, multiple=multiple)
- def get_element_text(self, xpath, format_=str):
+ def get_element_text(self, xpath, format_=str, multiple=False):
xpath = self.fragment_prefix + self._path_xpath_for(xpath)
- elem = self.builder.xpath(self.root, xpath)
- text = self.builder.get_text(elem)
- return format_(text)
+ elem = self.builder.xpath(self.root, xpath, multiple=multiple)
+ if multiple:
+ vals = []
+ for e in elem:
+ text = self.builder.get_text(e)
+ if text is not None:
+ vals.append(format_(text))
+ return vals
+ else:
+ text = self.builder.get_text(elem)
+ if text is None:
+ return None
+ return format_(text)
- def get_element_text_or_attribute(self, xpath):
+ def get_element_text_or_attribute(self, xpath, default=None, multiple=False):
parts = xpath.split('/')
is_attribute = parts[-1].startswith('@')
if is_attribute:
attribute_name = parts.pop(-1).lstrip('@')
element_path = "/".join(parts)
- return self.get_element_attribute(element_path, attribute_name)
+ try:
+ val = self.get_element_attribute(element_path, attribute_name, multiple=multiple)
+ if val is None:
+ return default
+ return val
+ except KeyError:
+ return default
+ except ValueError:
+ return default
else:
- return self.get_element_text(xpath)
+ try:
+ val = self.get_element_text(xpath, multiple=multiple)
+ if val is None:
+ return default
+ return val
+ except ValueError:
+ return default
def exist_element(self, xpath):
elem = self.get_element(xpath)
diff --git a/facho/fe/nomina/__init__.py b/facho/fe/nomina/__init__.py
index 9ec385a..f52db51 100644
--- a/facho/fe/nomina/__init__.py
+++ b/facho/fe/nomina/__init__.py
@@ -204,33 +204,66 @@ class DIANNominaIndividual:
return errors
def toFachoXML(self):
+ 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._devengados_total()
- self._deducciones_total()
- self._comprobante_total()
-
+
self.informacion_general.post_apply(self.fexml, self.informacion_general_xml)
return self.fexml
def _comprobante_total(self):
- # TODO
- self.fexml.set_element('/fe:NominaIndividual/ComprobanteTotal', '2500000.00')
+ devengados_total = self.fexml.get_element_text_or_attribute('/fe:NominaIndividual/DevengadosTotal', '0.0')
+ deducciones_total = self.fexml.get_element_text_or_attribute('/fe:NominaIndividual/DeduccionesTotal', '0.0')
+
+ comprobante_total = Amount(devengados_total) - Amount(deducciones_total)
+
+ self.fexml.set_element('/fe:NominaIndividual/ComprobanteTotal', str(round(comprobante_total, 2)))
def _deducciones_total(self):
- # TODO
- self.fexml.set_element('/fe:NominaIndividual/DeduccionesTotal', '1000000.00')
+ xpaths = [
+ '/fe:NominaIndividual/Deducciones/Salud/@Deduccion',
+ '/fe:NominaIndividual/Deducciones/FondoPension/@Deduccion'
+ ]
+ deducciones = map(lambda valor: Amount(valor),
+ self._values_of_xpaths(xpaths))
+
+ deducciones_total = Amount(0.0)
+
+ for deduccion in deducciones:
+ deducciones_total += deduccion
+
+ self.fexml.set_element('/fe:NominaIndividual/DeduccionesTotal', str(round(deducciones_total, 2)))
def _devengados_total(self):
+ xpaths = [
+ '/fe:NominaIndividual/Devengados/Basico/@SueldoTrabajado',
+ '/fe:NominaIndividual/Devengados/Transporte/@AuxilioTransporte',
+ '/fe:NominaIndividual/Devengados/Transporte/@ViaticoManuAlojS',
+ '/fe:NominaIndividual/Devengados/Transporte/@ViaticoManuAlojNS'
+ ]
devengados = map(lambda valor: Amount(valor),
- [
- self.fexml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'SueldoTrabajado')
- ]
- )
+ self._values_of_xpaths(xpaths))
+
devengados_total = Amount(0.0)
for devengado in devengados:
devengados_total += devengado
- self.fexml.set_element('/fe:NominaIndividual/DevengadosTotal', round(devengados_total,2))
+
+ self.fexml.set_element('/fe:NominaIndividual/DevengadosTotal', str(round(devengados_total,2)))
+ def _values_of_xpaths(self, xpaths):
+ xpaths_values_of_values = map(lambda val: self.fexml.get_element_text_or_attribute(val, multiple=True), xpaths)
+ xpaths_values = []
+ # toda esta carreta para hacer un aplano de lista
+ for xpath_values in xpaths_values_of_values:
+ if xpath_values is None:
+ continue
+
+ for xpath_value in xpath_values:
+ xpaths_values.append(xpath_value)
+
+ return filter(lambda val: val is not None, xpaths_values)
diff --git a/tests/test_nomina.py b/tests/test_nomina.py
index 5881d1e..e65893a 100644
--- a/tests/test_nomina.py
+++ b/tests/test_nomina.py
@@ -32,6 +32,41 @@ def test_adicionar_devengado_transporte():
assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Transporte', 'AuxilioTransporte') == '2000000.0'
+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_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') == '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()
@@ -45,12 +80,16 @@ def test_adicionar_devengado_transporte_muchos():
xml = nomina.toFachoXML()
print(xml)
- assert str(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)
@@ -58,7 +97,7 @@ def test_adicionar_deduccion_salud():
xml = nomina.toFachoXML()
print(xml)
- assert str(xml) == """"""
+ assert xml.get_element_text('/fe:NominaIndividual/DeduccionesTotal') == '1000.00'
def test_nomina_obligatorios_segun_anexo_tecnico():
nomina = fe.nomina.DIANNominaIndividual()
@@ -98,6 +137,11 @@ def test_nomina_cune():
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()
assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == '16560dc8956122e84ffb743c817fe7d494e058a44d9ca3fa4c234c268b4f766003253fbee7ea4af9682dd57210f3bac2'