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):
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):

View File

@ -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

View File

@ -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)
)

View File

@ -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: