From 903b1bad640d0036d7b4576500baa46c00fa6871 Mon Sep 17 00:00:00 2001 From: bit4bit Date: Sat, 13 Nov 2021 18:26:21 +0000 Subject: [PATCH] cli permite generar NominaIndividual FossilOrigin-Name: c30edea460eb16340183c373ed448e3719e2277550882db2d2daafe7bd4bca7e --- Makefile.dev | 11 +++- examples/generate-nomina-from-cli.py | 93 ++++++++++++++++++++++++++++ facho/cli.py | 41 ++++++++++++ facho/fe/fe.py | 9 ++- facho/fe/form_xml/utils.py | 8 ++- facho/fe/nomina/__init__.py | 8 +-- 6 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 examples/generate-nomina-from-cli.py diff --git a/Makefile.dev b/Makefile.dev index db6c183..f2aaeaf 100644 --- a/Makefile.dev +++ b/Makefile.dev @@ -6,14 +6,19 @@ # luego se pueden ejecutar las pruebas: # * make -f Makefile.dev test -.PHONY: dev-setup +.PHONY: dev-setup dev-shell py-develop test tox + dev-setup: docker build -t facho . -.PHONY: test +py-develop: + docker run -t -v $(PWD):/app -w /app facho sh -c 'python3.7 setup.py develop --user' + +dev-shell: + docker run --rm -ti -v "$(PWD):/app" -w /app --name facho-cli facho bash + test: docker run -t -v $(PWD):/app -w /app facho sh -c 'cd /app; python3.7 setup.py test' -.PHONY: tox tox: docker run -it -v $(PWD)/:/app -w /app facho tox diff --git a/examples/generate-nomina-from-cli.py b/examples/generate-nomina-from-cli.py new file mode 100644 index 0000000..05fccbe --- /dev/null +++ b/examples/generate-nomina-from-cli.py @@ -0,0 +1,93 @@ + +from facho import fe + +def extensions(nomina): + return [] + +def nomina(): + nomina = fe.nomina.DIANNominaIndividual() + + nomina.asignar_metadata(fe.nomina.Metadata( + secuencia=fe.nomina.NumeroSecuencia( + numero = 'N00001', + consecutivo=232 + ), + lugar_generacion=fe.nomina.Lugar( + pais = fe.nomina.Pais( + code = 'CO' + ), + departamento = fe.nomina.Departamento( + code = '05' + ), + municipio = fe.nomina.Municipio( + code = '05001' + ), + ), + proveedor=fe.nomina.Proveedor( + nit='999999', + dv=2, + software_id='xx', + software_sc='yy' + ) + )) + + 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', + periodo_nomina = fe.nomina.PeriodoNomina(code='1'), + tipo_moneda = fe.nomina.TipoMoneda(code='COP') + )) + + nomina.asignar_empleador(fe.nomina.Empleador( + nit = '700085371', + dv = '1', + pais = fe.nomina.Pais( + code = 'CO' + ), + departamento = fe.nomina.Departamento( + code = '05' + ), + municipio = fe.nomina.Municipio( + code = '05001' + ), + direccion = 'calle etrivial' + )) + + nomina.asignar_trabajador(fe.nomina.Trabajador( + tipo_contrato = fe.nomina.TipoContrato( + code = '1' + ), + alto_riesgo = False, + tipo_documento = fe.nomina.TipoDocumento( + code = '11' + ), + primer_apellido = 'gnu', + segundo_apellido = 'emacs', + primer_nombre = 'facho', + lugar_trabajo = fe.nomina.LugarTrabajo( + pais = fe.nomina.Pais(code='CO'), + departamento = fe.nomina.Departamento(code='05'), + municipio = fe.nomina.Municipio(code='05001'), + direccion = 'calle facho' + ), + numero_documento = '800199436', + tipo = fe.nomina.TipoTrabajador( + code = '01' + ), + salario_integral = True, + sueldo = fe.nomina.Amount(1_500_000) + )) + + nomina.adicionar_devengado(fe.nomina.DevengadoBasico( + dias_trabajados = 60, + sueldo_trabajado = fe.nomina.Amount(3_500_000) + )) + + nomina.adicionar_deduccion(fe.nomina.DeduccionSalud( + porcentaje = fe.nomina.Amount(19), + deduccion = fe.nomina.Amount(1_000_000) + )) + + return nomina diff --git a/facho/cli.py b/facho/cli.py index a305ea5..399c679 100644 --- a/facho/cli.py +++ b/facho/cli.py @@ -271,6 +271,46 @@ def generate_invoice(private_key, passphrase, scriptname, generate=False, ssl=Tr else: DIANWrite(xml, output) +@click.command() +@click.option('--private-key', type=click.Path(exists=True)) +@click.option('--passphrase') +@click.option('--ssl/--no-ssl', default=False) +@click.option('--sign/--no-sign', default=False) +@click.option('--use-cache-policy/--no-use-cache-policy', default=False) +@click.argument('scriptname', type=click.Path(exists=True), required=True) +@click.argument('output', required=True) +def generate_nomina(private_key, passphrase, scriptname, ssl=True, sign=False, use_cache_policy=False, output=None): + """ + imprime xml en pantalla. + SCRIPTNAME espera + def nomina() -> fe.nomina.NominaIndividual + def extensions(fe.nomina.NominaIndividual): -> List[facho.FachoXMLExtension] + """ + + if not ssl: + disable_ssl() + + import importlib.util + + spec = importlib.util.spec_from_file_location('nomina', scriptname) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + from facho.fe.form_xml import DIANWriteSigned, DIANWrite + import facho.fe + + nomina = module.nomina() + + xml = nomina.toFachoXML() + + extensions = module.extensions(nomina) + for extension in extensions: + xml.add_extension(extension) + + if sign: + DIANWriteSigned(xml, output, private_key, passphrase, use_cache_policy, dian_signer=facho.fe.nomina.DianXMLExtensionSigner) + else: + DIANWrite(xml, output) @click.command() @click.option('--private-key', type=click.Path(exists=True)) @@ -307,6 +347,7 @@ main.add_command(soap_get_status) main.add_command(soap_get_status_zip) main.add_command(soap_get_numbering_range) main.add_command(generate_invoice) +main.add_command(generate_nomina) main.add_command(validate_invoice) main.add_command(sign_xml) main.add_command(sign_verify_xml) diff --git a/facho/fe/fe.py b/facho/fe/fe.py index 0db31d1..ab26728 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -301,14 +301,17 @@ class DianXMLExtensionSigner: self._passphrase = passphrase.encode('utf-8') return self - + + def _element_extension_content(self, fachoxml): + return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') + def sign_xml_string(self, document): xml = LXMLBuilder.from_string(document) signature = self.sign_xml_element(xml) fachoxml = FachoXML(xml,nsmap=NAMESPACES) #DIAN 1.7.-2020: FAB01 - extcontent = fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') + extcontent = self._element_extension_content(fachoxml) fachoxml.append_element(extcontent, signature) return fachoxml.tostring(xml_declaration=True, encoding='UTF-8') @@ -372,7 +375,7 @@ class DianXMLExtensionSigner: def build(self, fachoxml): signature = self.sign_xml_element(fachoxml.root) - extcontent = fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') + extcontent = self._element_extension_content(fachoxml) fachoxml.append_element(extcontent, signature) diff --git a/facho/fe/form_xml/utils.py b/facho/fe/form_xml/utils.py index 1b4a04a..5aa919c 100644 --- a/facho/fe/form_xml/utils.py +++ b/facho/fe/form_xml/utils.py @@ -8,8 +8,12 @@ def DIANWrite(xml, filename): f.write(document) -def DIANWriteSigned(xml, filename, private_key, passphrase, use_cache_policy=False): +def DIANWriteSigned(xml, filename, private_key, passphrase, use_cache_policy=False, dian_signer=None): document = xml.tostring(xml_declaration=True, encoding='UTF-8').encode('utf-8') - signer = fe.DianXMLExtensionSigner(private_key, passphrase=passphrase, mockpolicy=use_cache_policy) + if dian_signer is None: + dian_signer = fe.DianXMLExtensionSigner + + signer = dian_signer(private_key, passphrase=passphrase, mockpolicy=use_cache_policy) + with open(filename, 'w') as f: f.write(signer.sign_xml_string(document)) diff --git a/facho/fe/nomina/__init__.py b/facho/fe/nomina/__init__.py index 96156a2..a47c44f 100644 --- a/facho/fe/nomina/__init__.py +++ b/facho/fe/nomina/__init__.py @@ -177,11 +177,9 @@ class DianXMLExtensionSigner(fe.DianXMLExtensionSigner): def __init__(self, pkcs12_path, passphrase=None, mockpolicy=False): super().__init__(pkcs12_path, passphrase=passphrase, mockpolicy=mockpolicy) - def build(self, fachoxml): - signature = self.sign_xml_element(fachoxml.root) - xpath = fachoxml.xpath_from_root('/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent') - extcontent = fachoxml.get_element(xpath) - fachoxml.append_element(extcontent, signature) + def _element_extension_content(self, fachoxml): + return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent') + class DIANNominaXML: def __init__(self, tag_document):