Peticiones_RRHH/main.py

178 lines
6.3 KiB
Python

import argparse
import json
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
from pyhanko.sign import signers, fields
from pyhanko.stamp import TextStampStyle
from pyhanko.pdf_utils.layout import SimpleBoxLayoutRule, AxisAlignment
from pyhanko.pdf_utils.text import TextBoxStyle
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 = 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 = self._ruta(self.CONFIG_PATH)
if not os.path.exists(config_path):
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 = 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 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":
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 getpass.getpass("👉 Contraseña certificado: ").encode()
def _firmar_pdf(self, archivo_entrada, archivo_salida):
if not os.path.exists(self.cert_path):
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()
)
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):
reader = PdfReader(self.plantilla_path)
writer = PdfWriter()
writer.clone_reader_document_root(reader)
campos = self._generar_campos()
for page in writer.pages:
writer.update_page_form_field_values(page, campos)
writer.set_need_appearances_writer()
nombre_final = self._generar_nombre_archivo()
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}")
if __name__ == "__main__":
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)