primer bosquejo para cune

FossilOrigin-Name: 415cd610f28f743b8ae79e98f49d34c8d1fa4cf5f5a542c03226a642457b28c0
This commit is contained in:
bit4bit 2021-11-06 02:38:41 +00:00
parent 3b2e3ff8a0
commit 35e1c5b609
4 changed files with 179 additions and 7 deletions

View File

@ -353,7 +353,9 @@ class FachoXML:
def get_element_attribute(self, xpath, attribute): def get_element_attribute(self, xpath, attribute):
elem = self.get_element(xpath) 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) return self.builder.get_attribute(elem, attribute)
def get_element(self, xpath): def get_element(self, xpath):

View File

@ -6,6 +6,7 @@
# creando las estructuras minimas necesaras. # creando las estructuras minimas necesaras.
from dataclasses import dataclass from dataclasses import dataclass
import hashlib
from .. import fe from .. import fe
from .. import form from .. import form
@ -14,10 +15,114 @@ from .devengado import *
from .deduccion import * from .deduccion import *
from .amount import Amount from .amount import Amount
from .exception import *
@dataclass
class NumeroSecuencia:
numero: str
class DIANNominaIndividualError(Exception): def apply(self, fragment):
pass 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: class DIANNominaIndividual:
def __init__(self): def __init__(self):
@ -25,12 +130,44 @@ class DIANNominaIndividual:
# 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
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/Basico')
self.fexml.placeholder_for('./Devengados/Transporte', optional=True) 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.devengados = self.fexml.fragment('./Devengados')
self.deducciones = self.fexml.fragment('./Deducciones') 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): def adicionar_devengado(self, devengado):
if not isinstance(devengado, Devengado): if not isinstance(devengado, Devengado):
raise ValueError('se espera tipo Devengado') raise ValueError('se espera tipo Devengado')
@ -83,4 +220,7 @@ class DIANNominaIndividual:
return errors return errors
def toFachoXML(self): def toFachoXML(self):
if self.informacion_general is not None:
self.informacion_general.post_apply(self.fexml, self.informacion_general_xml)
return self.fexml return self.fexml

View File

@ -16,5 +16,5 @@ class DevengadoBasico(Devengado):
# NIE069 # NIE069
DiasTrabajados = str(self.dias_trabajados), DiasTrabajados = str(self.dias_trabajados),
# NIE070 # NIE070
SueldoTrabajado = str(self.sueldo_trabajado) SueldoTrabajado = round(self.sueldo_trabajado, 2)
) )

View File

@ -19,7 +19,7 @@ def test_adicionar_devengado_Basico():
xml = nomina.toFachoXML() xml = nomina.toFachoXML()
assert xml.get_element_attribute('/fe:NominaIndividual/Devengados/Basico', 'DiasTrabajados') == '30' 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(): def test_adicionar_devengado_transporte():
nomina = fe.nomina.DIANNominaIndividual() nomina = fe.nomina.DIANNominaIndividual()
@ -45,7 +45,7 @@ def test_adicionar_devengado_transporte_muchos():
xml = nomina.toFachoXML() xml = nomina.toFachoXML()
print(xml) 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(): def test_adicionar_deduccion_salud():
@ -58,7 +58,7 @@ def test_adicionar_deduccion_salud():
xml = nomina.toFachoXML() xml = nomina.toFachoXML()
print(xml) 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(): def test_nomina_obligatorios_segun_anexo_tecnico():
nomina = fe.nomina.DIANNominaIndividual() 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 DeduccionSalud')
assert_error(errors, 'se requiere DeduccionFondoPension') 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): def assert_error(errors, msg):
for error in errors: for error in errors:
if str(error) == msg: if str(error) == msg: