""" Procesador de albaranes con OCR y vinculación automática """ from pathlib import Path from typing import Dict, Optional, List from datetime import datetime from prisma import Prisma from app.services.ocr_service import OCRService class AlbaranProcessor: """Procesa albaranes y los vincula con pedidos pendientes""" def __init__(self, db: Prisma): self.db = db self.ocr_service = OCRService() async def _find_proveedor(self, datos: Dict) -> Optional[int]: """Busca el proveedor basándose en los datos del albarán""" nombre = datos.get('proveedor', {}).get('nombre', '').strip() if nombre: proveedor = await self.db.proveedor.find_first( where={"nombre": {"contains": nombre, "mode": "insensitive"}} ) if proveedor: return proveedor.id return None def _parse_fecha(self, fecha_str: str) -> Optional[datetime]: """Parsea una fecha desde string""" if not fecha_str: return None from datetime import datetime formatos = [ '%Y-%m-%d', '%d/%m/%Y', '%d-%m-%Y', '%Y/%m/%d', ] for fmt in formatos: try: return datetime.strptime(fecha_str, fmt).date() except ValueError: continue return None async def _match_referencias( self, referencias_albaran: List[Dict], proveedor_id: int ) -> Dict[str, int]: """ Busca referencias del albarán en pedidos pendientes del proveedor Returns: Dict mapping referencia -> referencia_pedido_proveedor_id """ matches = {} # Obtener todas las referencias pendientes del proveedor pedidos_pendientes = await self.db.pedidoproveedor.find_many( where={ "proveedorId": proveedor_id, "estado": {"in": ["pendiente_recepcion", "parcial"]} }, include={"referencias": True} ) for pedido in pedidos_pendientes: for ref_pedido in pedido.referencias: if ref_pedido.estado in ["pendiente", "parcial"]: # Buscar coincidencia en el albarán for ref_albaran in referencias_albaran: if ref_albaran['referencia'].strip().upper() == ref_pedido.referencia.strip().upper(): if ref_pedido.referencia not in matches: matches[ref_pedido.referencia] = ref_pedido.id break return matches async def match_and_update_referencias(self, albaran): """Vincula y actualiza referencias del albarán con pedidos pendientes""" if not albaran.proveedorId: return referencias_albaran = await self.db.referenciaalbaran.find_many( where={"albaranId": albaran.id} ) matches = await self._match_referencias( [{"referencia": ref.referencia} for ref in referencias_albaran], albaran.proveedorId ) for ref_albaran in referencias_albaran: ref_pedido_proveedor_id = matches.get(ref_albaran.referencia.strip().upper()) if ref_pedido_proveedor_id: # Actualizar referencia albarán await self.db.referenciaalbaran.update( where={"id": ref_albaran.id}, data={"referenciaPedidoProveedorId": ref_pedido_proveedor_id} ) # Actualizar pedido proveedor ref_pedido = await self.db.referenciapedidoproveedor.find_unique( where={"id": ref_pedido_proveedor_id} ) nuevas_unidades_recibidas = ref_pedido.unidadesRecibidas + ref_albaran.unidades nuevo_estado = "recibido" if nuevas_unidades_recibidas < ref_pedido.unidadesPedidas: nuevo_estado = "parcial" if nuevas_unidades_recibidas > 0 else "pendiente" await self.db.referenciapedidoproveedor.update( where={"id": ref_pedido_proveedor_id}, data={ "unidadesRecibidas": nuevas_unidades_recibidas, "estado": nuevo_estado } ) # Actualizar referencia pedido cliente if ref_pedido.referenciaPedidoClienteId: ref_cliente = await self.db.referenciapedidocliente.find_unique( where={"id": ref_pedido.referenciaPedidoClienteId} ) await self.db.referenciapedidocliente.update( where={"id": ref_cliente.id}, data={ "unidadesEnStock": ref_cliente.unidadesEnStock + ref_albaran.unidades, "unidadesPendientes": max(0, ref_cliente.unidadesSolicitadas - (ref_cliente.unidadesEnStock + ref_albaran.unidades)) } ) async def process_albaran_file(self, file_path: Path): """ Procesa un archivo de albarán (imagen o PDF) Returns: Albaran creado """ # Procesar con OCR datos = self.ocr_service.process_albaran(file_path) # Buscar proveedor proveedor_id = await self._find_proveedor(datos) # Parsear fecha fecha_albaran = self._parse_fecha(datos.get('fecha_albaran', '')) # Crear albarán albaran = await self.db.albaran.create( data={ "proveedorId": proveedor_id, "numeroAlbaran": datos.get('numero_albaran', '').strip() or None, "fechaAlbaran": fecha_albaran, "archivoPath": str(file_path), "estadoProcesado": "procesado" if proveedor_id else "clasificacion", "fechaProcesado": datetime.now() if proveedor_id else None, "datosOcr": datos, "referencias": { "create": [ { "referencia": ref_data.get('referencia', '').strip(), "denominacion": ref_data.get('denominacion', '').strip(), "unidades": int(ref_data.get('unidades', 1)), "precioUnitario": float(ref_data.get('precio_unitario', 0)), "impuestoTipo": ref_data.get('impuesto_tipo', '21'), "impuestoValor": float(ref_data.get('impuesto_valor', 0)), } for ref_data in datos.get('referencias', []) ] } }, include={"proveedor": True, "referencias": True} ) # Vincular referencias si hay proveedor if proveedor_id: await self.match_and_update_referencias(albaran) # Recargar albarán con referencias actualizadas albaran = await self.db.albaran.find_unique( where={"id": albaran.id}, include={"proveedor": True, "referencias": True} ) return albaran