10 Commits

Author SHA1 Message Date
bit4bit
d5a96ea07d facho-signer: autoconf a version 2.69 compatible con PureOS
FossilOrigin-Name: e75ffb9d0e4f62680595617e69ef7890c33691be39df8cd04595dd0d7eee5deb
2022-10-14 15:47:17 +00:00
bit4bit
a59df60fc2 se adiciona prueba para tipo xml ajusts en nomina de ajuste
FossilOrigin-Name: 8cb6aa07cdafad91fc13f0d3a4454ddcd26c4ce40feb7cde43a413c3bddf409f
2022-05-30 19:50:47 +00:00
bit4bit
19c5a5bca6 se usa __eq__ en vez de same
FossilOrigin-Name: 05cda833815fbceb23ea6356c71daa37e3cc0d53300c726b0ffacae63bf4ab41
2022-05-30 19:28:00 +00:00
bit4bit
c50f1df1e7 se usa __eq__ en vez de same
FossilOrigin-Name: 96875a241ba61f2072f531e8845ae7dd981c9739c5d322555035243214204e4f
2022-05-30 19:26:44 +00:00
bit4bit
2a1f3b6b43 se actualiza CONTRIBUTING.md
FossilOrigin-Name: a814f92dd0ebacac700618b48c0cc3d677918c1e461519461489dd14f4b98cca
2022-05-30 19:18:47 +00:00
bit4bit
a208d924dd se mueve pruebas nomina de ajuste a test_nomina_ajuste.py
FossilOrigin-Name: 5cf7c9d1ea339d30416076f0fa1f8d3dd89452ac2f8f955b8daa67dd351c8843
2022-05-30 19:17:15 +00:00
pingara
c3b0f7cfe8 Pruebas de nómina en Nómina Ajuste actualizadas
FossilOrigin-Name: d799f3660231a69b7483298e7c7d62d51933529190fff51fa3d5de1a3ac50304
2022-05-19 21:33:46 +00:00
pingara
005f90166e Actualizar para envío de Nómina Ajuste
FossilOrigin-Name: 98e5f9522b428c2e401b9fb5ba2f8a96157415ada89aef70d7b7900674a461b8
2022-05-19 21:27:55 +00:00
pingara
73bb90b74b Validación contiene errores en campos mandatorio en schemalocation
FossilOrigin-Name: 834a1cd8bca356b5794fcaf6cafe5b84adc2a70bc719a25085bae4379a58f038
2022-05-19 02:18:02 +00:00
bit4bit
6bed600dd4 mezcla machete
FossilOrigin-Name: 0ea0f8f6c5fe4b809abdb59e33163316a1794bc4571649ce5a1c89aa58a6f32e
2022-05-11 01:19:59 +00:00
7 changed files with 307 additions and 164 deletions

View File

@@ -94,6 +94,14 @@ Ready to contribute? Here's how to set up `facho` for local development.
7. Submit a pull request through the GitHub website. 7. Submit a pull request through the GitHub website.
Using docker
------------
1. make -f Makefile.dev build
2. make -f Makefile.dev dev-shell
3. make -f Makefile.dev python3.8 setup.py develop
4. make -f Makefile.dev python3.8 setup.py test
Pull Request Guidelines Pull Request Guidelines
----------------------- -----------------------

View File

@@ -11,9 +11,6 @@
dev-setup: dev-setup:
docker build -t facho . docker build -t facho .
py-develop:
docker run -t -v $(PWD):/app -w /app facho sh -c 'python3.7 setup.py develop --user'
dev-shell: dev-shell:
docker run --rm -ti -v "$(PWD):/app" -w /app --name facho-cli facho bash docker run --rm -ti -v "$(PWD):/app" -w /app --name facho-cli facho bash

View File

@@ -1,7 +1,7 @@
# -*- Autoconf -*- # -*- Autoconf -*-
# Process this file with autoconf to produce a configure script. # Process this file with autoconf to produce a configure script.
AC_PREREQ([2.71]) AC_PREREQ([2.69])
AC_INIT([facho-signer], [0.0.1], [bit4bit@riseup.net]) AC_INIT([facho-signer], [0.0.1], [bit4bit@riseup.net])
AM_INIT_AUTOMAKE AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/facho_signer.c]) AC_CONFIG_SRCDIR([src/facho_signer.c])

View File

@@ -34,6 +34,7 @@ POLICY_NAME = u'Política de firma para facturas electrónicas de la República
NAMESPACES = { NAMESPACES = {
'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2',
'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual', 'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual',
'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste',
'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', 'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1',
'xs': 'http://www.w3.org/2001/XMLSchema-instance', 'xs': 'http://www.w3.org/2001/XMLSchema-instance',
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
@@ -91,8 +92,9 @@ class FeXML(FachoXML):
xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace] xmlns_name = {v: k for k, v in NAMESPACES.items()}[root_namespace]
return super().tostring(**kw)\ return super().tostring(**kw)\
.replace(xmlns_name + ':', '')\ .replace(xmlns_name + ':', '')\
.replace('xmlns:'+xmlns_name, 'xmlns') .replace('xmlns:'+xmlns_name, 'xmlns')\
.replace('schemaLocation', 'xsi:schemaLocation')
class DianXMLExtensionCUDFE(FachoXMLExtension): class DianXMLExtensionCUDFE(FachoXMLExtension):
def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS): def __init__(self, invoice, tipo_ambiente = AMBIENTE_PRUEBAS):

View File

@@ -144,13 +144,12 @@ class Proveedor:
ambiente = fexml.get_element_attribute(scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente') ambiente = fexml.get_element_attribute(scopexml.xpath_from_root('/InformacionGeneral'), 'Ambiente')
codigo_qr = f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={cune}" codigo_qr = f"https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey={cune}"
if InformacionGeneral.AMBIENTE_PRUEBAS.same(ambiente): if InformacionGeneral.AMBIENTE_PRUEBAS == ambiente:
codigo_qr = f"https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey={cune}" codigo_qr = f"https://catalogo-vpfe-hab.dian.gov.co/document/searchqr?documentkey={cune}"
elif ambiente is None: elif ambiente is None:
raise RuntimeError('fail to get InformacionGeneral/@Ambiente') raise RuntimeError('fail to get InformacionGeneral/@Ambiente')
scopexml.set_element('./CodigoQR', codigo_qr) scopexml.set_element('./CodigoQR', codigo_qr)
scopexml.set_element('./Novedad', "false")
# NIE020 # NIE020
software_code = self._software_security_code(fexml, scopexml) software_code = self._software_security_code(fexml, scopexml)
@@ -183,14 +182,16 @@ class Metadata:
proveedor: Proveedor proveedor: Proveedor
def apply(self, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml): def apply(self, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.novedad.apply(novedad) if novedad:
self.novedad.apply(novedad)
self.secuencia.apply(numero_secuencia_xml) self.secuencia.apply(numero_secuencia_xml)
self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML') self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML')
self.proveedor.apply(proveedor_xml) self.proveedor.apply(proveedor_xml)
def post_apply(self, fexml, scopexml, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml): def post_apply(self, fexml, scopexml, novedad, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.proveedor.post_apply(fexml, scopexml, proveedor_xml) self.proveedor.post_apply(fexml, scopexml, proveedor_xml)
self.novedad.post_apply(fexml, scopexml, proveedor_xml) if novedad:
self.novedad.post_apply(fexml, scopexml, proveedor_xml)
@dataclass @dataclass
class PeriodoNomina: class PeriodoNomina:
@@ -218,9 +219,8 @@ class InformacionGeneral:
class TIPO_AMBIENTE: class TIPO_AMBIENTE:
valor: str valor: str
@classmethod def __eq__(self, other):
def same(cls, value): return self.valor == str(other)
return cls.valor == str(value)
# TABLA 5.1.1 # TABLA 5.1.1
@dataclass @dataclass
@@ -234,6 +234,28 @@ class InformacionGeneral:
class AMBIENTE_PRUEBAS(TIPO_AMBIENTE): class AMBIENTE_PRUEBAS(TIPO_AMBIENTE):
valor: str = '2' valor: str = '2'
def __str__(self):
self.valor
# TABLA 5.5.7
@dataclass
class TIPO_XML:
valor: str
def __eq__(self, other):
return self.valor == str(other)
@dataclass
class TIPO_XML_NORMAL(TIPO_XML):
valor: str = '102'
def __str__(self):
self.valor
@dataclass
class TIPO_XML_AJUSTES(TIPO_XML):
valor: str = '103'
def __str__(self): def __str__(self):
self.valor self.valor
@@ -242,6 +264,7 @@ class InformacionGeneral:
periodo_nomina: PeriodoNomina periodo_nomina: PeriodoNomina
tipo_moneda: TipoMoneda tipo_moneda: TipoMoneda
tipo_ambiente: TIPO_AMBIENTE tipo_ambiente: TIPO_AMBIENTE
tipo_xml: TIPO_XML
software_pin: str software_pin: str
def __post_init__(self): def __post_init__(self):
@@ -256,7 +279,7 @@ class InformacionGeneral:
# NIE202 # NIE202
# TABLA 5.5.2 # TABLA 5.5.2
# TODO(bit4bit) solo NominaIndividual # TODO(bit4bit) solo NominaIndividual
TipoXML = '102', TipoXML = self.tipo_xml.valor,
# NIE024 # NIE024
CUNE = None, CUNE = None,
# NIE025 # NIE025
@@ -315,14 +338,18 @@ class DianXMLExtensionSigner(fe.DianXMLExtensionSigner):
class DIANNominaXML: class DIANNominaXML:
def __init__(self, tag_document, xpath_ajuste=None,schemaLocation=None): def __init__(self, tag_document, xpath_ajuste=None, schemaLocation=None, namespace_ajuste=None):
self.informacion_general_version = None self.informacion_general_version = None
self.tag_document = tag_document self.tag_document = tag_document
self.fexml = fe.FeXML(tag_document, 'dian:gov:co:facturaelectronica:NominaIndividual')
if schemaLocation is not None: if namespace_ajuste:
self.fexml.root.set("SchemaLocation", schemaLocation) self.fexml = fe.FeXML(tag_document, namespace_ajuste)
else:
self.fexml = fe.FeXML(tag_document, 'dian:gov:co:facturaelectronica:NominaIndividual')
self.fexml.root.set("SchemaLocation", "")
self.fexml.root.set("schemaLocation", schemaLocation)
# 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
@@ -334,7 +361,8 @@ class DIANNominaXML:
self.root_fragment = self.fexml.fragment(xpath_ajuste) self.root_fragment = self.fexml.fragment(xpath_ajuste)
self.root_fragment.placeholder_for('./ReemplazandoPredecesor', optional=True) self.root_fragment.placeholder_for('./ReemplazandoPredecesor', optional=True)
self.root_fragment.placeholder_for('./EliminandoPredecesor', optional=True) self.root_fragment.placeholder_for('./EliminandoPredecesor', optional=True)
self.root_fragment.placeholder_for('./Novedad', optional=False) if not namespace_ajuste:
self.root_fragment.placeholder_for('./Novedad', optional=False)
self.root_fragment.placeholder_for('./Periodo') self.root_fragment.placeholder_for('./Periodo')
self.root_fragment.placeholder_for('./NumeroSecuenciaXML') self.root_fragment.placeholder_for('./NumeroSecuenciaXML')
self.root_fragment.placeholder_for('./LugarGeneracionXML') self.root_fragment.placeholder_for('./LugarGeneracionXML')
@@ -347,8 +375,10 @@ class DIANNominaXML:
self.root_fragment.placeholder_for('./FechasPagos') self.root_fragment.placeholder_for('./FechasPagos')
self.root_fragment.placeholder_for('./Devengados/Basico') self.root_fragment.placeholder_for('./Devengados/Basico')
self.root_fragment.placeholder_for('./Devengados/Transporte', optional=True) self.root_fragment.placeholder_for('./Devengados/Transporte', optional=True)
if not namespace_ajuste:
self.novedad = self.root_fragment.fragment('./Novedad') self.novedad = self.root_fragment.fragment('./Novedad')
else:
self.novedad = None
self.informacion_general_xml = self.root_fragment.fragment('./InformacionGeneral') self.informacion_general_xml = self.root_fragment.fragment('./InformacionGeneral')
self.periodo_xml = self.root_fragment.fragment('./Periodo') self.periodo_xml = self.root_fragment.fragment('./Periodo')
self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos') self.fecha_pagos_xml = self.root_fragment.fragment('./FechasPagos')
@@ -368,6 +398,7 @@ class DIANNominaXML:
if not isinstance(metadata, Metadata): if not isinstance(metadata, Metadata):
raise ValueError('se espera tipo Metadata') raise ValueError('se espera tipo Metadata')
self.metadata = metadata self.metadata = metadata
self.metadata.apply(self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml) self.metadata.apply(self.novedad, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
def asignar_informacion_general(self, general): def asignar_informacion_general(self, general):
@@ -545,7 +576,7 @@ class DIANNominaXML:
class DIANNominaIndividual(DIANNominaXML): class DIANNominaIndividual(DIANNominaXML):
def __init__(self): def __init__(self):
schema = "dian:gov:co:facturaelectronica:NominaIndividual" schema = "dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"
super().__init__('NominaIndividual', schemaLocation=schema) super().__init__('NominaIndividual', schemaLocation=schema)
self.informacion_general_version = 'V1.0: Documento Soporte de Pago de Nómina Electrónica' self.informacion_general_version = 'V1.0: Documento Soporte de Pago de Nómina Electrónica'
@@ -561,6 +592,8 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
fecha_generacion: str fecha_generacion: str
def apply(self, fragment): def apply(self, fragment):
# NIAE214
fragment.set_element('./TipoNota', '1')
fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None, fragment.set_element('./Reemplazar/ReemplazandoPredecesor', None,
# NIAE090 # NIAE090
NumeroPred = self.numero, NumeroPred = self.numero,
@@ -571,9 +604,11 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
) )
def __init__(self): def __init__(self):
super().__init__('NominaIndividualDeAjuste', './Reemplazar') schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
# NIAE214
self.root_fragment.set_element('./TipoNota', '1') super().__init__('NominaIndividualDeAjuste', './Reemplazar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste')
self.informacion_general_version = 'V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica'
def asignar_predecesor(self, predecesor): def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor): if not isinstance(predecesor, self.Predecesor):
@@ -590,6 +625,7 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
fecha_generacion: str fecha_generacion: str
def apply(self, fragment): def apply(self, fragment):
fragment.set_element('./TipoNota', '2')
fragment.set_element('./Eliminar/EliminandoPredecesor', None, fragment.set_element('./Eliminar/EliminandoPredecesor', None,
# NIAE090 # NIAE090
NumeroPred = self.numero, NumeroPred = self.numero,
@@ -600,9 +636,9 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
) )
def __init__(self): def __init__(self):
super().__init__('NominaIndividualDeAjuste', './Eliminar') schema = "dian:gov:co:facturaelectronica:NominaIndividualDeAjuste NominaIndividualDeAjusteElectronicaXSD.xsd"
super().__init__('NominaIndividualDeAjuste', './Eliminar', schemaLocation=schema, namespace_ajuste='dian:gov:co:facturaelectronica:NominaIndividualDeAjuste')
self.root_fragment.set_element('./TipoNota', '2')
self.informacion_general_version = "V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica" self.informacion_general_version = "V1.0: Nota de Ajuste de Documento Soporte de Pago de Nómina Electrónica"
def asignar_predecesor(self, predecesor): def asignar_predecesor(self, predecesor):

View File

@@ -156,6 +156,7 @@ def test_nomina_xml():
hora_generacion = '1053:10-05:00', hora_generacion = '1053:10-05:00',
tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION, tipo_ambiente = fe.nomina.InformacionGeneral.AMBIENTE_PRODUCCION,
software_pin = '693', software_pin = '693',
tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_NORMAL,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'), periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP') tipo_moneda = fe.nomina.TipoMoneda(code='COP')
)) ))
@@ -214,6 +215,7 @@ def test_nomina_xml():
xml = nomina.toFachoXML() xml = nomina.toFachoXML()
expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab' expected_cune = 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab'
assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'CUNE') == expected_cune
assert xml.get_element_attribute('/nomina:NominaIndividual/InformacionGeneral', 'TipoXML') == '102'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001' assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001' assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '00001'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO' assert xml.get_element_text_or_attribute('/nomina:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO'
@@ -251,143 +253,6 @@ def test_nomina_xmlsign(monkeypatch):
assert elem is not None assert elem is not None
def atest_nomina_ajuste_reemplazar():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
xml = nomina.toFachoXML()
print(xml)
assert False
def test_adicionar_reemplazar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
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('/nomina:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00'
def test_adicionar_reemplazar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nomina:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16'
def test_adicionar_reemplazar_eliminar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element('/nomina:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None
assert xml.get_element('/nomina:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None
def test_adicionar_eliminar_reemplazar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element('/nomina:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None
assert xml.get_element('/nomina:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None
def test_adicionar_eliminar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
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('/nomina:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00'
def test_adicionar_eliminar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nomina:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nomina:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16'
def test_nomina_devengado_horas_extras_diarias():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'
def test_nomina_devengado_horas_extras_nocturnas(): def test_nomina_devengado_horas_extras_nocturnas():
nomina = fe.nomina.DIANNominaIndividual() nomina = fe.nomina.DIANNominaIndividual()

235
tests/test_nomina_ajuste.py Normal file
View File

@@ -0,0 +1,235 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
"""Tests for `facho` package."""
import re
import pytest
from facho import fe
import helpers
def atest_nomina_ajuste_reemplazar():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
xml = nomina.toFachoXML()
print(xml)
assert False
def test_nomina_ajuste_reemplazar_asignacion_tipo_xml():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_metadata(fe.nomina.Metadata(
novedad=fe.nomina.Novedad(
activa = True,
cune = "N0111"
),
secuencia=fe.nomina.NumeroSecuencia(
prefijo = 'N',
consecutivo='00001'
),
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_pin='12',
razon_social='facho'
)
))
nomina.asignar_empleador(fe.nomina.Empleador(
razon_social='facho',
nit = '700085371',
dv = '1',
pais = fe.nomina.Pais(
code = 'CO'
),
departamento = fe.nomina.Departamento(
code = '05'
),
municipio = fe.nomina.Municipio(
code = '05001'
),
direccion = 'calle etrivial'
))
nomina.asignar_trabajador(fe.nomina.Trabajador(
tipo_contrato = fe.nomina.TipoContrato(
code = '1'
),
alto_riesgo = False,
tipo_documento = fe.nomina.TipoDocumento(
code = '11'
),
primer_apellido = 'gnu',
segundo_apellido = 'emacs',
primer_nombre = 'facho',
lugar_trabajo = fe.nomina.LugarTrabajo(
pais = fe.nomina.Pais(code='CO'),
departamento = fe.nomina.Departamento(code='05'),
municipio = fe.nomina.Municipio(code='05001'),
direccion = 'calle facho'
),
numero_documento = '800199436',
tipo = fe.nomina.TipoTrabajador(
code = '01'
),
salario_integral = True,
sueldo = fe.nomina.Amount(1_500_000)
))
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',
tipo_xml = fe.nomina.InformacionGeneral.TIPO_XML_AJUSTES,
periodo_nomina = fe.nomina.PeriodoNomina(code='1'),
tipo_moneda = fe.nomina.TipoMoneda(code='COP')
))
xml = nomina.toFachoXML()
assert xml.get_element_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/InformacionGeneral', 'TipoXML') == '103'
def test_adicionar_reemplazar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
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('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ComprobanteTotal') == '1000000.00'
def test_adicionar_reemplazar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor/@FechaGenPred') == '2021-11-16'
def test_adicionar_reemplazar_eliminar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Reemplazar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is not None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is None
def test_adicionar_eliminar_reemplazar_predecesor_opcional():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor') is not None
assert xml.get_element('/nominaajuste:NominaIndividualDeAjuste/Reemplazar/ReemplazandoPredecesor') is None
def test_adicionar_eliminar_devengado_comprobante_total():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
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('/nominaajuste:NominaIndividualDeAjuste/Eliminar/ComprobanteTotal') == '1000000.00'
def test_adicionar_eliminar_asignar_predecesor():
nomina = fe.nomina.DIANNominaIndividualDeAjuste.Eliminar()
nomina.asignar_predecesor(fe.nomina.DIANNominaIndividualDeAjuste.Eliminar.Predecesor(
numero = '123456',
cune = 'ABC123456',
fecha_generacion = '2021-11-16'
))
xml = nomina.toFachoXML()
print(xml.tostring())
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@NumeroPred') == '123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@CUNEPred') == 'ABC123456'
assert xml.get_element_text_or_attribute('/nominaajuste:NominaIndividualDeAjuste/Eliminar/EliminandoPredecesor/@FechaGenPred') == '2021-11-16'
def test_nomina_devengado_horas_extras_diarias():
nomina = fe.nomina.DIANNominaIndividual()
nomina.adicionar_devengado(fe.nomina.DevengadoHorasExtrasDiarias(
horas_extras=[
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T19:09:55',
hora_fin='2021-11-30T20:09:55',
cantidad=1,
porcentaje=fe.nomina.Amount(1),
pago=fe.nomina.Amount(100)
),
fe.nomina.DevengadoHoraExtra(
hora_inicio='2021-11-30T18:09:55',
hora_fin='2021-11-30T19:09:55',
cantidad=2,
porcentaje=fe.nomina.Amount(2),
pago=fe.nomina.Amount(200)
)
]
))
xml = nomina.toFachoXML()
extras = xml.get_element('/nomina:NominaIndividual/Devengados/HEDs/HED', multiple=True)
assert extras[0].get('HoraInicio') == '2021-11-30T19:09:55'
assert extras[0].get('HoraFin') == '2021-11-30T20:09:55'
assert extras[0].get('Cantidad') == '1'
assert extras[0].get('Porcentaje') == '1.00'
assert extras[0].get('Pago') == '100.00'
assert extras[1].get('HoraInicio') == '2021-11-30T18:09:55'
assert extras[1].get('HoraFin') == '2021-11-30T19:09:55'
assert extras[1].get('Cantidad') == '2'
assert extras[1].get('Porcentaje') == '2.00'
assert extras[1].get('Pago') == '200.00'