135 lines
5.6 KiB
Python
Executable File
135 lines
5.6 KiB
Python
Executable File
# /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 |