# /opt/docker/dev/service_finder/backend/app/services/document_service.py import os import logging import asyncio from PIL import Image from uuid import uuid4 from datetime import datetime, timezone from fastapi import UploadFile, BackgroundTasks, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func, and_ from app.models import Document from app.models.identity import User from app.services.config_service import config # 2.0 Dinamikus beállítások from app.workers.ocr.robot_1_ocr_processor import OCRRobot # Robot 1 hívása logger = logging.getLogger("Document-Service-2.0") class DocumentService: """ Document Service 2.0 - Admin-vezérelt Pipeline. Feladata: Tárolás, Optimalizálás, Kvótamanagement és Robot Trigger. """ @staticmethod async def process_upload( db: AsyncSession, user_id: int, file: UploadFile, parent_type: str, # pl. "asset", "organization", "transfer" parent_id: str, doc_type: str, # pl. "invoice", "registration_card", "sale_contract" background_tasks: BackgroundTasks ): try: # --- 1. ADMIN KVÓTA ELLENŐRZÉS --- user_stmt = select(User).where(User.id == user_id) user = (await db.execute(user_stmt)).scalar_one() # Lekérjük a csomagnak megfelelő havi limitet (pl. Free: 1, Premium: 10) limits = await config.get_setting(db, "ocr_monthly_limit", default={"free": 1, "premium": 10, "vip": 100}) user_role = user.role.value if hasattr(user.role, 'value') else str(user.role) allowed_ocr = limits.get(user_role, 1) # Megnézzük a havi felhasználást now = datetime.now(timezone.utc) start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) count_stmt = select(func.count(Document.id)).where( and_( Document.user_id == user_id, Document.created_at >= start_of_month ) ) used_count = (await db.execute(count_stmt)).scalar() if used_count >= allowed_ocr: raise HTTPException( status_code=403, detail=f"Havi dokumentum limit túllépve ({allowed_ocr}). Válts csomagot a folytatáshoz!" ) # --- 2. DINAMIKUS TÁROLÁS ÉS OPTIMALIZÁLÁS --- file_uuid = str(uuid4()) nas_base = await config.get_setting(db, "storage_nas_path", default="/mnt/nas/app_data") # Útvonal sablonok az adminból (Pl. "vault/{parent_type}/{parent_id}") vault_dir = os.path.join(nas_base, parent_type, parent_id, "vault") thumb_dir = os.path.join(getattr(config, "STATIC_DIR", "static"), "previews", parent_type, parent_id) os.makedirs(vault_dir, exist_ok=True) os.makedirs(thumb_dir, exist_ok=True) content = await file.read() temp_path = f"/tmp/{file_uuid}_{file.filename}" with open(temp_path, "wb") as f: f.write(content) # Kép feldolgozása PIL-lel img = Image.open(temp_path) # Thumbnail generálás (SSD/Static területre) thumb_filename = f"{file_uuid}_thumb.webp" thumb_path = os.path.join(thumb_dir, thumb_filename) thumb_img = img.copy() thumb_img.thumbnail((300, 300)) thumb_img.save(thumb_path, "WEBP", quality=80) # Optimalizált eredeti (NAS / Vault területre) max_width = await config.get_setting(db, "img_max_width", default=1600) vault_filename = f"{file_uuid}.webp" vault_path = os.path.join(vault_dir, vault_filename) if img.width > max_width: ratio = max_width / img.width img = img.resize((max_width, int(img.height * ratio)), Image.Resampling.LANCZOS) img.save(vault_path, "WEBP", quality=85) # --- 3. MENTÉS --- new_doc = Document( id=uuid4(), user_id=user_id, parent_type=parent_type, parent_id=parent_id, doc_type=doc_type, original_name=file.filename, file_hash=file_uuid, file_ext="webp", mime_type="image/webp", file_size=os.path.getsize(vault_path), has_thumbnail=True, thumbnail_path=f"/static/previews/{parent_type}/{parent_id}/{thumb_filename}", status="uploaded" ) db.add(new_doc) await db.flush() # --- 4. ROBOT TRIGGER (OCR AUTOMATIZMUS) --- # Megnézzük, hogy ez a típus (pl. invoice) igényel-e automatikus OCR-t auto_ocr_types = await config.get_setting(db, "ocr_auto_trigger_types", default=["invoice", "registration_card", "sale_contract"]) if doc_type in auto_ocr_types: # Robot 1 (OCR) sorba állítása háttérfolyamatként background_tasks.add_task(OCRRobot.process_document, db, new_doc.id) new_doc.status = "pending_ocr" logger.info(f"🤖 Robot 1 (OCR) riasztva: {new_doc.id}") await db.commit() os.remove(temp_path) return new_doc except Exception as e: if 'temp_path' in locals() and os.path.exists(temp_path): os.remove(temp_path) logger.error(f"Document Upload Error: {e}") raise e