Primera version funcional, 'Refactorizacion y control de errores'
This commit is contained in:
parent
0ec8041ac7
commit
95e4b39d94
225
main.py
225
main.py
|
|
@ -1,9 +1,10 @@
|
|||
import argparse
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import warnings
|
||||
import os
|
||||
import re
|
||||
import getpass
|
||||
import tempfile
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
|
||||
|
|
@ -12,136 +13,166 @@ from pyhanko.stamp import TextStampStyle
|
|||
from pyhanko.pdf_utils.layout import SimpleBoxLayoutRule, AxisAlignment
|
||||
from pyhanko.pdf_utils.text import TextBoxStyle
|
||||
|
||||
# Ignorar avisos de pyhanko
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="pyhanko")
|
||||
import logging
|
||||
logging.getLogger("pyhanko").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("pyhanko.sign").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("pyhanko.sign.signers").setLevel(logging.CRITICAL)
|
||||
|
||||
class SolicitudRRHH:
|
||||
CONFIG_PATH = "docs/config.json"
|
||||
CAMPO_PERIODO = "PERÍODO DE TIEMPO POR EL QUE SE SOLICITA"
|
||||
|
||||
def __init__(self):
|
||||
self.base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
self.config = self._cargar_configuracion()
|
||||
|
||||
self.cert_path = os.path.join(self.base_path, self.config["rutas"]["certificado"])
|
||||
|
||||
self.cert_path = self._ruta(self.config["rutas"]["certificado"])
|
||||
self.plantilla_path = self._ruta(self.config["rutas"]["plantilla"])
|
||||
self.datos_personales = self.config["datos_personales"]
|
||||
|
||||
|
||||
self.args = self._parsear_argumentos()
|
||||
self.num_dias = self._obtener_num_dias()
|
||||
|
||||
def _ruta(self, relativa):
|
||||
return os.path.join(self.base_path, relativa)
|
||||
|
||||
def _cargar_configuracion(self):
|
||||
config_path = os.path.join(self.base_path, "docs/config.json")
|
||||
config_path = self._ruta(self.CONFIG_PATH)
|
||||
if not os.path.exists(config_path):
|
||||
raise FileNotFoundError(f"No se encuentra el archivo de configuración: {config_path}")
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
raise FileNotFoundError(f"No existe config: {config_path}")
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
def _validar_fecha(self, fecha_str):
|
||||
patron_fecha = r"\d{2}/\d{2}/\d{2}"
|
||||
patron_completo = rf"^{patron_fecha}((-{patron_fecha})|(,{patron_fecha})*)?$"
|
||||
if re.match(patron_completo, fecha_str):
|
||||
return fecha_str
|
||||
raise argparse.ArgumentTypeError(f"Formato de fecha inválido: {fecha_str}")
|
||||
patron = rf"^{patron_fecha}((-{patron_fecha})|(,{patron_fecha})*)?$"
|
||||
if not re.match(patron, fecha_str):
|
||||
raise argparse.ArgumentTypeError(f"Formato inválido: {fecha_str}")
|
||||
return fecha_str
|
||||
|
||||
def _parsear_argumentos(self):
|
||||
parser = argparse.ArgumentParser(description="Automatización de solicitudes RRHH")
|
||||
parser.add_argument("tipo", choices=["ap", "vacaciones"], help="Tipo de solicitud")
|
||||
parser.add_argument("fecha", type=self._validar_fecha, help="Fecha, rango o lista")
|
||||
parser.add_argument("--dias", type=int, help="Número de días hábiles")
|
||||
parser = argparse.ArgumentParser(description="Automatización RRHH")
|
||||
parser.add_argument("tipo", choices=["ap", "vacaciones"],help="Tipo de permiso")
|
||||
parser.add_argument("fecha", type=self._validar_fecha, help="Fechas en formato dd/mm/yy, se permiten fechas, rangos separados con '-' o listas separados con ','")
|
||||
parser.add_argument("--dias", type=int)
|
||||
return parser.parse_args()
|
||||
|
||||
def _obtener_num_dias(self):
|
||||
if self.args.tipo == "vacaciones":
|
||||
if self.args.dias: return self.args.dias
|
||||
while True:
|
||||
try:
|
||||
return int(input("👉 Introduce el Nº de días hábiles de vacaciones: "))
|
||||
except ValueError: print("❌ Introduce un número entero válido.")
|
||||
return None
|
||||
if self.args.tipo != "vacaciones":
|
||||
return None
|
||||
|
||||
if self.args.dias:
|
||||
return self.args.dias
|
||||
|
||||
while True:
|
||||
try:
|
||||
return int(input("👉 Nº días hábiles: "))
|
||||
except ValueError:
|
||||
print("❌ Número inválido")
|
||||
|
||||
def _formatear_fechas(self):
|
||||
fechas = self.args.fecha
|
||||
if "-" in fechas:
|
||||
inicio, fin = fechas.split("-")
|
||||
return f"Del {inicio} al {fin}"
|
||||
if "," in fechas:
|
||||
return fechas.replace(",", ", ")
|
||||
return fechas
|
||||
|
||||
def _obtener_passw_cert(self):
|
||||
return str(getpass.getpass(prompt="👉 Introduce la contraseña del certificado: ")).encode("utf-8")
|
||||
return getpass.getpass("👉 Contraseña certificado: ").encode()
|
||||
|
||||
def _firmar_pdf(self, archivo_entrada, archivo_salida):
|
||||
if not os.path.exists(self.cert_path):
|
||||
print(f"⚠️ No se encontró el certificado en {self.cert_path}. Se guardará sin firmar.")
|
||||
os.rename(archivo_entrada, archivo_salida)
|
||||
return
|
||||
|
||||
with open(archivo_entrada, 'rb') as inf:
|
||||
w = IncrementalPdfFileWriter(inf)
|
||||
signer = signers.SimpleSigner.load_pkcs12(
|
||||
pfx_file=self.cert_path,
|
||||
passphrase=self._obtener_passw_cert()
|
||||
)
|
||||
|
||||
estilo = TextStampStyle(
|
||||
border_width=0,
|
||||
text_box_style=TextBoxStyle(font_size=10),
|
||||
inner_content_layout=SimpleBoxLayoutRule(x_align=AxisAlignment.ALIGN_MIN, y_align=AxisAlignment.ALIGN_MAX),
|
||||
stamp_text='Firmado por: \n%(signer)s\nFecha: %(ts)s'
|
||||
)
|
||||
|
||||
pdf_signer = signers.PdfSigner(
|
||||
signature_meta=signers.PdfSignatureMetadata(field_name='Firma_Visible'),
|
||||
signer=signer,
|
||||
stamp_style=estilo,
|
||||
new_field_spec=fields.SigFieldSpec(
|
||||
'Firma_Visible',
|
||||
box=(350, 20, 550, 100),
|
||||
on_page=1
|
||||
raise FileNotFoundError("Certificado no encontrado.")
|
||||
try:
|
||||
with open(archivo_entrada, "rb") as inf:
|
||||
writer = IncrementalPdfFileWriter(inf)
|
||||
signer = signers.SimpleSigner.load_pkcs12(
|
||||
pfx_file=self.cert_path,
|
||||
passphrase=self._obtener_passw_cert()
|
||||
)
|
||||
)
|
||||
|
||||
with open(archivo_salida, 'wb') as outf:
|
||||
pdf_signer.sign_pdf(w, output=outf)
|
||||
estilo = TextStampStyle(
|
||||
border_width=0,
|
||||
text_box_style=TextBoxStyle(font_size=10),
|
||||
inner_content_layout=SimpleBoxLayoutRule(
|
||||
x_align=AxisAlignment.ALIGN_MIN,
|
||||
y_align=AxisAlignment.ALIGN_MAX
|
||||
),
|
||||
stamp_text="Firmado por:\n%(signer)s\nFecha: %(ts)s"
|
||||
)
|
||||
|
||||
pdf_signer = signers.PdfSigner(
|
||||
signature_meta=signers.PdfSignatureMetadata(
|
||||
field_name="Firma_Visible"
|
||||
),
|
||||
signer=signer,
|
||||
stamp_style=estilo,
|
||||
new_field_spec=fields.SigFieldSpec(
|
||||
"Firma_Visible",
|
||||
box=(350, 20, 550, 100),
|
||||
on_page=1
|
||||
)
|
||||
)
|
||||
|
||||
with open(archivo_salida, "wb") as outf:
|
||||
pdf_signer.sign_pdf(writer, output=outf)
|
||||
except Exception as e:
|
||||
raise ValueError("Contraseña incorrecta o certificado inválido.") from e
|
||||
|
||||
def _generar_campos(self):
|
||||
campos = {**self.datos_personales}
|
||||
if self.args.tipo == "ap":
|
||||
campos["moscoso"] = "/Yes"
|
||||
else:
|
||||
campos["diasHabiles"] = "/Yes"
|
||||
campos["NumDiasHabiles"] = str(self.num_dias)
|
||||
campos[self.CAMPO_PERIODO] = self._formatear_fechas()
|
||||
campos["Fecha"] = datetime.now().strftime("%d/%m/%Y")
|
||||
return campos
|
||||
|
||||
def _generar_nombre_archivo(self):
|
||||
tipo = self.args.tipo.upper()
|
||||
fecha = re.split(r"[,-]", self.args.fecha)[0].replace("/", "-")
|
||||
|
||||
return (
|
||||
f"{self.datos_personales['Apellido 1']}_"
|
||||
f"{self.datos_personales['Apellido 2']}_"
|
||||
f"{self.datos_personales['Nombre']}_"
|
||||
f"{tipo}_{fecha}.pdf"
|
||||
)
|
||||
|
||||
def generar_pdf(self):
|
||||
path_plantilla = os.path.join(self.base_path, self.config["rutas"]["plantilla"])
|
||||
reader = PdfReader(path_plantilla)
|
||||
reader = PdfReader(self.plantilla_path)
|
||||
writer = PdfWriter()
|
||||
writer.clone_reader_document_root(reader)
|
||||
|
||||
fechas_raw = self.args.fecha
|
||||
if "-" in fechas_raw:
|
||||
f = fechas_raw.split("-")
|
||||
texto_fechas = f"Del {f[0]} al {f[1]}"
|
||||
elif "," in fechas_raw:
|
||||
texto_fechas = f"{fechas_raw.replace(',', ', ')}"
|
||||
else:
|
||||
texto_fechas = fechas_raw
|
||||
|
||||
campos_finales = {**self.datos_personales}
|
||||
|
||||
if self.args.tipo == "ap":
|
||||
campos_finales["moscoso"] = "/Yes"
|
||||
else:
|
||||
campos_finales["diasHabiles"] = "/Yes"
|
||||
campos_finales["NumDiasHabiles"] = str(self.num_dias)
|
||||
|
||||
campos_finales["PERÍODO DE TIEMPO POR EL QUE SE SOLICITA"] = texto_fechas
|
||||
campos_finales["Fecha"] = datetime.now().strftime("%d/%m/%Y")
|
||||
|
||||
campos = self._generar_campos()
|
||||
for page in writer.pages:
|
||||
writer.update_page_form_field_values(page, campos_finales)
|
||||
|
||||
writer.update_page_form_field_values(page, campos)
|
||||
|
||||
writer.set_need_appearances_writer()
|
||||
|
||||
nombre_final = self._generar_nombre_archivo()
|
||||
temp_file = "temp_solicitud.pdf"
|
||||
|
||||
with open(temp_file, "wb") as f:
|
||||
writer.write(f)
|
||||
|
||||
self._firmar_pdf(temp_file, nombre_final)
|
||||
|
||||
if os.path.exists(temp_file):
|
||||
os.remove(temp_file)
|
||||
|
||||
print(f"✅ Proceso completado con éxito: {nombre_final}")
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||
writer.write(tmp)
|
||||
temp_path = tmp.name
|
||||
|
||||
self._firmar_pdf(temp_path, nombre_final)
|
||||
os.remove(temp_path)
|
||||
|
||||
print(f"✅ PDF generado: {nombre_final}")
|
||||
|
||||
def _generar_nombre_archivo(self):
|
||||
tipo_limpio = self.args.tipo.upper()
|
||||
primera_fecha = re.split(r'[,-]', self.args.fecha)[0].replace("/", "-")
|
||||
return f"{self.datos_personales['Apellido 1']}_{self.datos_personales['Apellido 2']}_{self.datos_personales['Nombre']}_{tipo_limpio}_{primera_fecha}.pdf"
|
||||
|
||||
if __name__ == "__main__":
|
||||
solicitud = SolicitudRRHH()
|
||||
solicitud.generar_pdf()
|
||||
try:
|
||||
SolicitudRRHH().generar_pdf()
|
||||
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
print(f"❌ {e}")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print("💥 Error interno inesperado")
|
||||
sys.exit(1)
|
||||
Loading…
Reference in New Issue
Block a user