ajuste usando validacion de la dian
FossilOrigin-Name: 0e42c46b8071bc8d8ed750160d739de073fd7dfb8037bc15b241748a8ac529fd
This commit is contained in:
parent
ab1646f156
commit
ee3910136d
20
facho/cli.py
20
facho/cli.py
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,6 +515,7 @@ 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):
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user