primer bosquejo para cune
FossilOrigin-Name: 415cd610f28f743b8ae79e98f49d34c8d1fa4cf5f5a542c03226a642457b28c0
This commit is contained in:
parent
3b2e3ff8a0
commit
35e1c5b609
@ -353,7 +353,9 @@ class FachoXML:
|
||||
|
||||
def get_element_attribute(self, xpath, attribute):
|
||||
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)
|
||||
|
||||
def get_element(self, xpath):
|
||||
|
@ -6,6 +6,7 @@
|
||||
# creando las estructuras minimas necesaras.
|
||||
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
|
||||
from .. import fe
|
||||
from .. import form
|
||||
@ -14,10 +15,114 @@ from .devengado import *
|
||||
from .deduccion import *
|
||||
|
||||
from .amount import Amount
|
||||
from .exception import *
|
||||
|
||||
@dataclass
|
||||
class NumeroSecuencia:
|
||||
numero: str
|
||||
|
||||
class DIANNominaIndividualError(Exception):
|
||||
pass
|
||||
def apply(self, fragment):
|
||||
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:
|
||||
def __init__(self):
|
||||
@ -25,12 +130,44 @@ class DIANNominaIndividual:
|
||||
|
||||
# layout, la dian requiere que los elementos
|
||||
# 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/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.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):
|
||||
if not isinstance(devengado, Devengado):
|
||||
raise ValueError('se espera tipo Devengado')
|
||||
@ -83,4 +220,7 @@ class DIANNominaIndividual:
|
||||
return errors
|
||||
|
||||
def toFachoXML(self):
|
||||
if self.informacion_general is not None:
|
||||
self.informacion_general.post_apply(self.fexml, self.informacion_general_xml)
|
||||
|
||||
return self.fexml
|
||||
|
@ -16,5 +16,5 @@ class DevengadoBasico(Devengado):
|
||||
# NIE069
|
||||
DiasTrabajados = str(self.dias_trabajados),
|
||||
# NIE070
|
||||
SueldoTrabajado = str(self.sueldo_trabajado)
|
||||
SueldoTrabajado = round(self.sueldo_trabajado, 2)
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ def test_adicionar_devengado_Basico():
|
||||
|
||||
xml = nomina.toFachoXML()
|
||||
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():
|
||||
nomina = fe.nomina.DIANNominaIndividual()
|
||||
@ -45,7 +45,7 @@ def test_adicionar_devengado_transporte_muchos():
|
||||
|
||||
xml = nomina.toFachoXML()
|
||||
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():
|
||||
@ -58,7 +58,7 @@ def test_adicionar_deduccion_salud():
|
||||
|
||||
xml = nomina.toFachoXML()
|
||||
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():
|
||||
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 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):
|
||||
for error in errors:
|
||||
if str(error) == msg:
|
||||
|
Loading…
Reference in New Issue
Block a user