diff --git a/facho/cli.py b/facho/cli.py index 7cf4382..d3063f1 100644 --- a/facho/cli.py +++ b/facho/cli.py @@ -200,6 +200,18 @@ def validate_invoice(invoice_path): XSD.validate(content, XSD.UBLInvoice) +@click.command() +@click.argument('nomina_path') +def validate_nominaindividual(nomina_path): + from facho.fe.data.dian import XSD + content = open(nomina_path, 'r').read() + content = content.replace( + 'xmlns="http://www.dian.gov.co/contratos/facturaelectronica/v1"', + 'xmlns="dian:gov:co:facturaelectronica:NominaIndividual"', + ) + XSD.validate(content, XSD.NominaIndividual) + + @click.command() @click.option('--private-key', type=click.Path(exists=True)) @click.option('--passphrase') @@ -327,7 +339,6 @@ def soap_send_nomina_sync(private_key, public_key, habilitacion, password, filen if habilitacion: req = dian.Habilitacion.SendNominaSync resp = client.request(req( - filename, open(zipfile, 'rb').read() )) print(resp) @@ -372,3 +383,4 @@ main.add_command(sign_xml) main.add_command(sign_verify_xml) main.add_command(generate_nomina) main.add_command(soap_send_nomina_sync) +main.add_command(validate_nominaindividual) diff --git a/facho/fe/client/dian.py b/facho/fe/client/dian.py index cd6feca..5909c80 100644 --- a/facho/fe/client/dian.py +++ b/facho/fe/client/dian.py @@ -177,7 +177,6 @@ class GetStatusZip(SOAPService): @dataclass class SendNominaSync(SOAPService): - fileName: str contentFile: bytes def get_wsdl(self): diff --git a/facho/fe/data/dian/XSD/__init__.py b/facho/fe/data/dian/XSD/__init__.py index b70fc0d..ca33563 100644 --- a/facho/fe/data/dian/XSD/__init__.py +++ b/facho/fe/data/dian/XSD/__init__.py @@ -9,5 +9,8 @@ def path_for_xsd(dirname, xsdname): UBLInvoice= xmlschema.XMLSchema(path_for_xsd('maindoc', 'UBL-Invoice-2.1.xsd')) +NominaIndividual = xmlschema.XMLSchema(path_for_xsd('nomina', 'NominaIndividualElectronicaXSDV1.0.6.xsd')) +NominaIndividualDeAjuste = xmlschema.XMLSchema(path_for_xsd('nomina', 'NominaIndividualDeAjusteElectronicaXSDV1.0.6.xsd')) + def validate(xml, schema): schema.validate(xml) diff --git a/facho/fe/nomina/__init__.py b/facho/fe/nomina/__init__.py index 437953a..fe95321 100644 --- a/facho/fe/nomina/__init__.py +++ b/facho/fe/nomina/__init__.py @@ -34,7 +34,31 @@ class NumeroSecuencia: # NIE012 Numero = self.numero) +@dataclass +class Periodo: + fecha_ingreso: str + fecha_liquidacion_inicio: str + fecha_liquidacion_fin: str + fecha_generacion: str + tiempo_laborado: int = 1 + fecha_retiro: str = None + + def apply(self, fragment): + fragment.set_attributes('./Periodo', + #NIE002 + FechaIngreso=self.fecha_ingreso, + #NIE003 + FechaRetiro=self.fecha_retiro, + #NIE004 + FechaLiquidacionInicio=self.fecha_liquidacion_inicio, + #NIE005 + FechaLiquidacionFin=self.fecha_liquidacion_fin, + #NIE006 + TiempoLaborado=self.tiempo_laborado, + #NIE008 + FechaGen=self.fecha_generacion) + @dataclass class Proveedor: nit: str @@ -60,8 +84,7 @@ class Proveedor: # TODO(bit4bit) https://catalogo‐vpfe‐hab.dian.gov.co/document/searchqr?documentkey=CUNE para habilitacion # https://catalogo‐vpfe.dian.gov.co/document/searchqr?documentkey=CUNE codigo_qr = f"https://catalogo‐vpfe.dian.gov.co/document/searchqr?documentkey={cune}" - fragment.set_attributes('./ProveedorXML', - CodigoQR=codigo_qr) + fexml.set_element('./CodigoQR', codigo_qr) @dataclass class Metadata: @@ -184,10 +207,13 @@ class DianXMLExtensionSigner(fe.DianXMLExtensionSigner): class DIANNominaXML: - def __init__(self, tag_document, xpath_ajuste=None): + def __init__(self, tag_document, xpath_ajuste=None,schemaLocation=None): self.tag_document = tag_document self.fexml = fe.FeXML(tag_document, 'http://www.dian.gov.co/contratos/facturaelectronica/v1') + if schemaLocation is not None: + self.fexml.root.set("SchemaLocation", schemaLocation) + # layout, la dian requiere que los elementos # esten ordenados segun el anexo tecnico self.fexml.placeholder_for('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent') @@ -203,15 +229,19 @@ class DIANNominaXML: self.root_fragment.placeholder_for('./NumeroSecuenciaXML') self.root_fragment.placeholder_for('./LugarGeneracionXML') self.root_fragment.placeholder_for('./ProveedorXML') + self.root_fragment.placeholder_for('./CodigoQR') self.root_fragment.placeholder_for('./InformacionGeneral') self.root_fragment.placeholder_for('./Empleador') self.root_fragment.placeholder_for('./Trabajador') self.root_fragment.placeholder_for('./Pago') + self.root_fragment.placeholder_for('./FechasPagos') self.root_fragment.placeholder_for('./Devengados/Basico') self.root_fragment.placeholder_for('./Devengados/Transporte', optional=True) self.informacion_general_xml = self.root_fragment.fragment('./InformacionGeneral') + self.periodo_xml = self.root_fragment.fragment('./Periodo') + self.numero_secuencia_xml = self.root_fragment.fragment('./NumeroSecuenciaXML') self.lugar_generacion_xml = self.root_fragment.fragment('./LugarGeneracionXML') self.proveedor_xml = self.root_fragment.fragment('./ProveedorXML') @@ -236,11 +266,20 @@ class DIANNominaXML: self.informacion_general = general self.informacion_general.apply(self.informacion_general_xml) + def asignar_periodo(self, periodo): + if not isinstance(periodo, Periodo): + raise ValueError('se espera tipo Periodo') + + periodo.apply(self.periodo_xml) + def asignar_pago(self, pago): if not isinstance(pago, Pago): raise ValueError('se espera tipo Pago') pago.apply(self.pago_xml) + def asignar_fecha_pago(self, fecha): + self.fexml.set_element('./FechasPagos/FechaPago', fecha) + def asignar_empleador(self, empleador): if not isinstance(empleador, Empleador): raise ValueError('se espera tipo Empleador') @@ -381,8 +420,9 @@ class DIANNominaXML: class DIANNominaIndividual(DIANNominaXML): def __init__(self): - super().__init__('NominaIndividual') + schema = "dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd" + super().__init__('NominaIndividual', schemaLocation=schema) # TODO(bit4bit) confirmar que no tienen en comun con NominaIndividual class DIANNominaIndividualDeAjuste(DIANNominaXML): diff --git a/facho/fe/nomina/lugar.py b/facho/fe/nomina/lugar.py index 8ffca88..e11a24e 100644 --- a/facho/fe/nomina/lugar.py +++ b/facho/fe/nomina/lugar.py @@ -21,4 +21,5 @@ class Lugar: fragment.set_attributes(root, Pais=self.pais.code, DepartamentoEstado=self.departamento.code, - MunicipioCiudad=self.municipio.code) + MunicipioCiudad=self.municipio.code, + Idioma=self.idioma) diff --git a/facho/fe/nomina/trabajador/__init__.py b/facho/fe/nomina/trabajador/__init__.py index 5d6a046..9867b3e 100644 --- a/facho/fe/nomina/trabajador/__init__.py +++ b/facho/fe/nomina/trabajador/__init__.py @@ -55,7 +55,7 @@ class Trabajador: LugarTrabajoPais = self.lugar_trabajo.pais.code, # NIE051 - LugarTrabajoDepartamentoEstadoEstado = self.lugar_trabajo.departamento.code, + LugarTrabajoDepartamentoEstado = self.lugar_trabajo.departamento.code, # NIE052 LugarTrabajoMunicipioCiudad = self.lugar_trabajo.municipio.code, diff --git a/setup.py b/setup.py index e4e0df7..207ab10 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,8 @@ requirements = ['Click>=6.0', 'xmlsig>=0.1.3', 'xades>=0.2.1', 'mock==2.0.0', - 'xmlsec>=1.3.8'] + 'xmlsec>=1.3.8', + 'xmlschema>=1.8'] setup_requirements = ['pytest-runner', ]