se adicionan mas campos requeridos para NominaIndividual

FossilOrigin-Name: 12c10fc4b3e4b69c665c8cde599a788266402977af62eed9b9a25459e1545f02
This commit is contained in:
bit4bit 2021-11-12 02:56:17 +00:00
parent 8deebbdfa4
commit a34963ca49
3 changed files with 100 additions and 15 deletions
facho/fe
data/dian/codelist
nomina
tests

View File

@ -101,3 +101,4 @@ SubTipoTrabajador = CodeList(path_for_codelist('SubTipoTrabajador-2.1.gc'), 'cod
TipoContrato = CodeList(path_for_codelist('TipoContrato-2.1.gc'), 'code', 'name')
PeriodoNomina = CodeList(path_for_codelist('PeriodoNomina-2.1.gc'), 'code', 'name')
TipoMoneda = CodeList(path_for_codelist('TipoMoneda-2.1.gc'), 'code', 'name')
IdiomaISO6391 = CodeList(path_for_codelist('Idioma-2.1.gc'), 'iso-639-1', 'name')

View File

@ -17,23 +17,65 @@ from .deduccion import *
from .trabajador import *
from .empleador import *
from .pago import *
from .pais import Pais
from .departamento import Departamento
from .municipio import Municipio
from .lugar import Lugar
from .amount import Amount
from .exception import *
@dataclass
class NumeroSecuencia:
consecutivo: int
numero: str
def apply(self, fragment):
fragment.set_attributes('./NumeroSecuenciaXML',
# NIE011
Consecutivo=self.consecutivo,
# NIE012
Numero = self.numero)
@dataclass
class Proveedor:
nit: str
dv: int
software_id: str
software_sc: str
def apply(self, fragment):
fragment.set_attributes('./ProveedorXML',
# NIE017
NIT=self.nit,
# NIE018
DV=self.dv,
# NIE019
SoftwareID=self.software_id,
# NIE020
SoftwareSC=self.software_sc,
)
def post_apply(self, fexml, fragment):
cune_xpath = fexml.xpath_from_root('/InformacionGeneral')
cune = fexml.get_element_attribute(cune_xpath, 'CUNE')
codigo_qr = f"https://catalogovpfe.dian.gov.co/document/searchqr?documentkey={cune}"
fragment.set_attributes('./ProveedorXML',
CodigoQR=codigo_qr)
@dataclass
class Metadata:
secuencia: NumeroSecuencia
# NIE013, NIE014, NIE015, NIE016
lugar_generacion: Lugar
proveedor: Proveedor
def apply(self, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.secuencia.apply(numero_secuencia_xml)
self.lugar_generacion.apply(lugar_generacion_xml, './LugarGeneracionXML')
self.proveedor.apply(proveedor_xml)
def post_apply(self, fexml, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.proveedor.post_apply(fexml, proveedor_xml)
@dataclass
class PeriodoNomina:
code: str
@ -138,7 +180,12 @@ class DIANNominaXML:
# layout, la dian requiere que los elementos
# esten ordenados segun el anexo tecnico
self.fexml.placeholder_for('./UBLExtensions')
self.fexml.placeholder_for('./Novedad', optional=True)
self.fexml.placeholder_for('./Periodo')
self.fexml.placeholder_for('./NumeroSecuenciaXML')
self.fexml.placeholder_for('./LugarGeneracionXML')
self.fexml.placeholder_for('./ProveedorXML')
self.fexml.placeholder_for('./InformacionGeneral')
self.fexml.placeholder_for('./Empleador')
self.fexml.placeholder_for('./Trabajador')
@ -149,6 +196,8 @@ class DIANNominaXML:
self.informacion_general_xml = self.fexml.fragment('./InformacionGeneral')
self.numero_secuencia_xml = self.fexml.fragment('./NumeroSecuenciaXML')
self.lugar_generacion_xml = self.fexml.fragment('./LugarGeneracionXML')
self.proveedor_xml = self.fexml.fragment('./ProveedorXML')
self.empleador = self.fexml.fragment('./Empleador')
self.trabajador = self.fexml.fragment('./Trabajador')
self.pago_xml = self.fexml.fragment('./Pago')
@ -156,12 +205,14 @@ class DIANNominaXML:
self.deducciones = self.fexml.fragment('./Deducciones')
self.informacion_general = None
self.metadata = 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_metadata(self, metadata):
if not isinstance(metadata, Metadata):
raise ValueError('se espera tipo Metadata')
self.metadata = metadata
self.metadata.apply(self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
def asignar_informacion_general(self, general):
if not isinstance(general, InformacionGeneral):
raise ValueError('se espera tipo InformacionGeneral')
@ -246,13 +297,16 @@ class DIANNominaXML:
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.informacion_general.post_apply(self.fexml, self.informacion_general_xml)
if self.metadata is not None:
self.metadata.post_apply(self.fexml, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
return self.fexml
def _comprobante_total(self):

View File

@ -109,12 +109,32 @@ def test_nomina_obligatorios_segun_anexo_tecnico():
assert_error(errors, 'se requiere DeduccionSalud')
assert_error(errors, 'se requiere DeduccionFondoPension')
def test_nomina_cune():
def test_nomina_xml():
nomina = fe.nomina.DIANNominaIndividual()
nomina.asignar_numero_secuencia(fe.nomina.NumeroSecuencia(
numero = 'N00001'
))
nomina.asignar_metadata(fe.nomina.Metadata(
secuencia=fe.nomina.NumeroSecuencia(
numero = 'N00001',
consecutivo=232
),
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_sc='yy'
)
))
nomina.asignar_informacion_general(fe.nomina.InformacionGeneral(
fecha_generacion = '2020-01-16',
@ -179,6 +199,16 @@ def test_nomina_cune():
# TODO(bit4bit) no logro generar cune igual al del anexo tecnico
#assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == '16560dc8956122e84ffb743c817fe7d494e058a44d9ca3fa4c234c268b4f766003253fbee7ea4af9682dd57210f3bac2'
assert xml.get_element_attribute('/fe:NominaIndividual/InformacionGeneral', 'CUNE') == 'b8f9b6c24de07ffd92ea5467433a3b69357cfaffa7c19722db94b2e0eca41d057085a54f484b5da15ff585e773b0b0ab'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/NumeroSecuenciaXML/@Numero') == 'N00001'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/NumeroSecuenciaXML/@Consecutivo') == '232'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/LugarGeneracionXML/@Pais') == 'CO'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/LugarGeneracionXML/@DepartamentoEstado') == '05'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/LugarGeneracionXML/@MunicipioCiudad') == '05001'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@NIT') == '999999'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@DV') == '2'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@SoftwareID') == 'xx'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@SoftwareSC') == 'yy'
assert xml.get_element_text_or_attribute('/fe:NominaIndividual/ProveedorXML/@CodigoQR') != None
def assert_error(errors, msg):
for error in errors: