ajuste usando validacion de la dian

FossilOrigin-Name: 0e42c46b8071bc8d8ed750160d739de073fd7dfb8037bc15b241748a8ac529fd
This commit is contained in:
bit4bit 2021-11-27 01:24:55 +00:00
parent ab1646f156
commit ee3910136d
4 changed files with 67 additions and 31 deletions

View File

@ -366,6 +366,25 @@ def sign_verify_xml(private_key, passphrase, xmlfile, ssl=True, use_cache_policy
else:
print("-INVALID")
@click.command()
@click.option('--software-id')
@click.option('--software-pin')
@click.option('--nit')
@click.option('--dv')
@click.option('--output-zippath')
def generate_nomina_habilitacion(software_id, software_pin, nit, dv, output_zippath):
from facho import fe
generador = fe.nomina.habilitacion.Habilitacion(
fe.nomina.habilitacion.Habilitacion.Metadata(
software_id=software_id,
software_pin=software_pin,
nit=nit,
dv=dv
)
)
generador.generar(output_zippath)
@click.group()
def main():
pass
@ -384,3 +403,4 @@ main.add_command(sign_verify_xml)
main.add_command(generate_nomina)
main.add_command(soap_send_nomina_sync)
main.add_command(validate_nominaindividual)
main.add_command(generate_nomina_habilitacion)

View File

@ -451,7 +451,7 @@ class DianZIP:
self.zipfile = zipfile.ZipFile(file_like, mode='w', compression=zipfile.ZIP_DEFLATED)
self.num_files = 0
def add_invoice_xml(self, name, xml_data):
def add_xml(self, name, xml_data):
self.num_files += 1
# TODO cual es la norma para los nombres de archivos?
m = hashlib.sha256()
@ -462,6 +462,9 @@ class DianZIP:
return filename
def add_invoice_xml(self, name, xml_data):
return self.add_xml(name, xml_data)
def __enter__(self):
return self

View File

@ -98,6 +98,7 @@ class Periodo:
@dataclass
class Proveedor:
razon_social: str
nit: str
dv: int
software_id: str
@ -112,14 +113,16 @@ class Proveedor:
# NIE019
SoftwareID=self.software_id,
SoftwareSC=None
SoftwareSC=None,
# NIE025
RazonSocial=self.razon_social
)
def post_apply(self, fexml, fragment):
cune_xpath = fexml.xpath_from_root('/InformacionGeneral')
def post_apply(self, fexml, scopexml, fragment):
cune_xpath = scopexml.xpath_from_root('/InformacionGeneral')
cune = fexml.get_element_attribute(cune_xpath, 'CUNE')
ambiente = fexml.get_element_attribute(fexml.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}"
if InformacionGeneral.AMBIENTE_PRUEBAS.same(ambiente):
@ -127,15 +130,17 @@ class Proveedor:
elif ambiente is None:
raise RuntimeError('fail to get InformacionGeneral/@Ambiente')
fexml.set_element('./CodigoQR', codigo_qr)
scopexml.set_element('./CodigoQR', codigo_qr)
# NIE020
software_code = self._software_security_code(fexml)
fexml.set_attributes('./ProveedorXML', SoftwareSC=software_code)
software_code = self._software_security_code(fexml, scopexml)
fexml.set_attributes(scopexml.xpath_from_root('/ProveedorXML'), SoftwareSC=software_code)
def _software_security_code(self, fexml, scopexml):
def _software_security_code(self, fexml):
# 8.2
numero = fexml.get_element_text_or_attribute('./NumeroSecuenciaXML/@Numero')
numero = fexml.get_element_attribute(scopexml.xpath_from_root('/NumeroSecuenciaXML'), 'Numero')
if numero is None:
raise RuntimeError('fallo obtener NumeroSequenciaXML/@Numero')
@ -144,7 +149,7 @@ class Proveedor:
code = "".join([id_software, software_pin, numero])
fexml.set_attributes('./ProveedorXML', fachoSoftwareSC=code)
fexml.set_attributes(scopexml.xpath_from_root('/ProveedorXML'), fachoSoftwareSC=code)
h = hashlib.sha384()
h.update(code.encode('utf-8'))
return h.hexdigest()
@ -161,8 +166,8 @@ class Metadata:
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)
def post_apply(self, fexml, scopexml, numero_secuencia_xml, lugar_generacion_xml, proveedor_xml):
self.proveedor.post_apply(fexml, scopexml, proveedor_xml)
@dataclass
class PeriodoNomina:
@ -219,10 +224,10 @@ class InformacionGeneral:
def __post_init__(self):
self.fecha_generacion = Fecha.cast(self.fecha_generacion)
def apply(self, fragment):
def apply(self, fragment, version):
fragment.set_attributes('./InformacionGeneral',
# NIE022
Version = 'V1.0: Documento Soporte de Pago de Nómina Electrónica',
Version = version,
# NIE023
Ambiente = self.tipo_ambiente.valor,
# NIE202
@ -245,22 +250,23 @@ class InformacionGeneral:
# .....
)
def post_apply(self, fexml, fragment):
def post_apply(self, fexml, scopexml, fragment):
# generar cune
# ver 8.1.1.1
xpaths = [
fexml.xpath_from_root('/NumeroSecuenciaXML/@Numero'),
fexml.xpath_from_root('/InformacionGeneral/@FechaGen'),
fexml.xpath_from_root('/InformacionGeneral/@HoraGen'),
fexml.xpath_from_root('/DevengadosTotal'),
fexml.xpath_from_root('/DeduccionesTotal'),
fexml.xpath_from_root('/ComprobanteTotal'),
fexml.xpath_from_root('/Empleador/@NIT'),
fexml.xpath_from_root('/Trabajador/@NumeroDocumento'),
fexml.xpath_from_root('/InformacionGeneral/@TipoXML'),
scopexml.xpath_from_root('/NumeroSecuenciaXML/@Numero'),
scopexml.xpath_from_root('/InformacionGeneral/@FechaGen'),
scopexml.xpath_from_root('/InformacionGeneral/@HoraGen'),
scopexml.xpath_from_root('/DevengadosTotal'),
scopexml.xpath_from_root('/DeduccionesTotal'),
scopexml.xpath_from_root('/ComprobanteTotal'),
scopexml.xpath_from_root('/Empleador/@NIT'),
scopexml.xpath_from_root('/Trabajador/@NumeroDocumento'),
scopexml.xpath_from_root('/InformacionGeneral/@TipoXML'),
tuple([self.software_pin]),
fexml.xpath_from_root('/InformacionGeneral/@Ambiente')
scopexml.xpath_from_root('/InformacionGeneral/@Ambiente')
]
campos = fexml.get_elements_text_or_attributes(xpaths)
cune = "".join(campos)
@ -287,6 +293,8 @@ class DianXMLExtensionSigner(fe.DianXMLExtensionSigner):
class DIANNominaXML:
def __init__(self, tag_document, xpath_ajuste=None,schemaLocation=None):
self.informacion_general_version = None
self.tag_document = tag_document
self.fexml = fe.FeXML(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
@ -343,7 +351,7 @@ class DIANNominaXML:
if not isinstance(general, InformacionGeneral):
raise ValueError('se espera tipo InformacionGeneral')
self.informacion_general = general
self.informacion_general.apply(self.informacion_general_xml)
self.informacion_general.apply(self.informacion_general_xml, self.informacion_general_version)
def asignar_periodo(self, periodo):
if not isinstance(periodo, Periodo):
@ -442,10 +450,10 @@ class DIANNominaXML:
#TODO(bit4bit) acoplamiento temporal
# es importante el orden de ejecucion
self.informacion_general.post_apply(self.root_fragment, self.informacion_general_xml)
self.informacion_general.post_apply(self.fexml, self.root_fragment, self.informacion_general_xml)
if self.metadata is not None:
self.metadata.post_apply(self.root_fragment, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
self.metadata.post_apply(self.fexml, self.root_fragment, self.numero_secuencia_xml, self.lugar_generacion_xml, self.proveedor_xml)
return self.fexml
@ -507,7 +515,8 @@ class DIANNominaIndividual(DIANNominaXML):
schema = "dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd"
super().__init__('NominaIndividual', schemaLocation=schema)
self.informacion_general_version = 'V1.0: Documento Soporte de Pago de Nómina Electrónica'
# TODO(bit4bit) confirmar que no tienen en comun con NominaIndividual
class DIANNominaIndividualDeAjuste(DIANNominaXML):
@ -561,6 +570,7 @@ class DIANNominaIndividualDeAjuste(DIANNominaXML):
super().__init__('NominaIndividualDeAjuste', './Eliminar')
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"
def asignar_predecesor(self, predecesor):
if not isinstance(predecesor, self.Predecesor):

View File

@ -6,6 +6,7 @@ from ..municipio import Municipio
@dataclass
class Empleador:
razon_social: str
nit: str
dv: str
pais: Pais
@ -26,7 +27,9 @@ class Empleador:
# NIE037
MunicipioCiudad = self.municipio.code,
# NIE038
Direccion = self.direccion
Direccion = self.direccion,
RazonSocial=self.razon_social
)