Fascicoli+Tassonomia+permessi
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
Service per la gestione dei Fascicoli (fascicolazione pratiche).
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.exceptions import ConflictError, ForbiddenError, NotFoundError
|
||||
from app.models.fascicolo import Fascicolo, FascicoloMessage
|
||||
from app.models.message import Message
|
||||
from app.models.user import User
|
||||
from app.schemas.fascicolo import FascicoloCreate, FascicoloUpdate
|
||||
|
||||
|
||||
class FascicoloService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
# ─── CRUD Fascicolo ───────────────────────────────────────────────────────
|
||||
|
||||
async def list_fascicoli(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
stato: str | None = None,
|
||||
responsabile_id: uuid.UUID | None = None,
|
||||
search: str | None = None,
|
||||
) -> list[tuple[Fascicolo, int]]:
|
||||
"""
|
||||
Restituisce lista di (Fascicolo, message_count) con filtri opzionali.
|
||||
"""
|
||||
# Subquery per il conteggio messaggi
|
||||
count_sub = (
|
||||
select(
|
||||
FascicoloMessage.fascicolo_id,
|
||||
func.count(FascicoloMessage.message_id).label("cnt"),
|
||||
)
|
||||
.group_by(FascicoloMessage.fascicolo_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
stmt = (
|
||||
select(Fascicolo, func.coalesce(count_sub.c.cnt, 0).label("message_count"))
|
||||
.outerjoin(count_sub, Fascicolo.id == count_sub.c.fascicolo_id)
|
||||
.where(Fascicolo.tenant_id == tenant_id)
|
||||
.order_by(Fascicolo.updated_at.desc())
|
||||
)
|
||||
|
||||
if stato:
|
||||
stmt = stmt.where(Fascicolo.stato == stato)
|
||||
if responsabile_id:
|
||||
stmt = stmt.where(Fascicolo.responsabile_id == responsabile_id)
|
||||
if search:
|
||||
pattern = f"%{search}%"
|
||||
stmt = stmt.where(
|
||||
Fascicolo.titolo.ilike(pattern)
|
||||
| Fascicolo.numero_pratica.ilike(pattern)
|
||||
| Fascicolo.categoria.ilike(pattern)
|
||||
)
|
||||
|
||||
result = await self.db.execute(stmt)
|
||||
return list(result.all())
|
||||
|
||||
async def get_fascicolo(
|
||||
self, tenant_id: uuid.UUID, fascicolo_id: uuid.UUID
|
||||
) -> tuple[Fascicolo, int]:
|
||||
"""Restituisce (Fascicolo, message_count) o solleva NotFoundError."""
|
||||
count_sub = (
|
||||
select(
|
||||
FascicoloMessage.fascicolo_id,
|
||||
func.count(FascicoloMessage.message_id).label("cnt"),
|
||||
)
|
||||
.group_by(FascicoloMessage.fascicolo_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
result = await self.db.execute(
|
||||
select(Fascicolo, func.coalesce(count_sub.c.cnt, 0).label("message_count"))
|
||||
.outerjoin(count_sub, Fascicolo.id == count_sub.c.fascicolo_id)
|
||||
.where(
|
||||
Fascicolo.id == fascicolo_id,
|
||||
Fascicolo.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
row = result.one_or_none()
|
||||
if not row:
|
||||
raise NotFoundError(f"Fascicolo {fascicolo_id} non trovato")
|
||||
return row
|
||||
|
||||
async def create_fascicolo(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
data: FascicoloCreate,
|
||||
created_by: uuid.UUID,
|
||||
) -> tuple[Fascicolo, int]:
|
||||
fascicolo = Fascicolo(
|
||||
id=uuid.uuid4(),
|
||||
tenant_id=tenant_id,
|
||||
titolo=data.titolo,
|
||||
numero_pratica=data.numero_pratica,
|
||||
stato=data.stato or "aperto",
|
||||
categoria=data.categoria,
|
||||
responsabile_id=data.responsabile_id,
|
||||
scadenza=data.scadenza,
|
||||
note=data.note,
|
||||
created_by=created_by,
|
||||
)
|
||||
self.db.add(fascicolo)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(fascicolo)
|
||||
return fascicolo, 0
|
||||
|
||||
async def update_fascicolo(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
fascicolo_id: uuid.UUID,
|
||||
data: FascicoloUpdate,
|
||||
current_user_id: uuid.UUID,
|
||||
is_admin: bool,
|
||||
) -> tuple[Fascicolo, int]:
|
||||
fascicolo, count = await self.get_fascicolo(tenant_id, fascicolo_id)
|
||||
|
||||
# Solo admin o il creatore possono modificare
|
||||
if not is_admin and fascicolo.created_by != current_user_id:
|
||||
raise ForbiddenError("Solo il creatore o un amministratore puo' modificare questo fascicolo")
|
||||
|
||||
if data.titolo is not None:
|
||||
fascicolo.titolo = data.titolo
|
||||
if data.numero_pratica is not None:
|
||||
fascicolo.numero_pratica = data.numero_pratica
|
||||
if data.stato is not None:
|
||||
fascicolo.stato = data.stato
|
||||
if data.categoria is not None:
|
||||
fascicolo.categoria = data.categoria
|
||||
if data.responsabile_id is not None:
|
||||
fascicolo.responsabile_id = data.responsabile_id
|
||||
if data.scadenza is not None:
|
||||
fascicolo.scadenza = data.scadenza
|
||||
if data.note is not None:
|
||||
fascicolo.note = data.note
|
||||
|
||||
fascicolo.updated_at = datetime.utcnow()
|
||||
await self.db.commit()
|
||||
await self.db.refresh(fascicolo)
|
||||
return fascicolo, count
|
||||
|
||||
async def delete_fascicolo(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
fascicolo_id: uuid.UUID,
|
||||
) -> None:
|
||||
fascicolo, _ = await self.get_fascicolo(tenant_id, fascicolo_id)
|
||||
await self.db.delete(fascicolo)
|
||||
await self.db.commit()
|
||||
|
||||
# ─── Messaggi nel fascicolo ───────────────────────────────────────────────
|
||||
|
||||
async def get_fascicolo_messages(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
fascicolo_id: uuid.UUID,
|
||||
) -> list[tuple[Message, datetime]]:
|
||||
"""
|
||||
Restituisce lista di (Message, added_at) ordinati per added_at desc.
|
||||
"""
|
||||
# Verifica fascicolo appartiene al tenant
|
||||
await self.get_fascicolo(tenant_id, fascicolo_id)
|
||||
|
||||
result = await self.db.execute(
|
||||
select(Message, FascicoloMessage.added_at)
|
||||
.join(FascicoloMessage, Message.id == FascicoloMessage.message_id)
|
||||
.where(
|
||||
FascicoloMessage.fascicolo_id == fascicolo_id,
|
||||
Message.tenant_id == tenant_id,
|
||||
)
|
||||
.order_by(FascicoloMessage.added_at.desc())
|
||||
)
|
||||
return list(result.all())
|
||||
|
||||
async def add_messages(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
fascicolo_id: uuid.UUID,
|
||||
message_ids: list[uuid.UUID],
|
||||
added_by: uuid.UUID,
|
||||
) -> int:
|
||||
"""
|
||||
Aggiunge messaggi al fascicolo. Ignora duplicati e messaggi non del tenant.
|
||||
Restituisce il numero di messaggi aggiunti.
|
||||
"""
|
||||
await self.get_fascicolo(tenant_id, fascicolo_id)
|
||||
|
||||
# Verifica che i messaggi appartengano al tenant
|
||||
msg_result = await self.db.execute(
|
||||
select(Message.id).where(
|
||||
Message.id.in_(message_ids),
|
||||
Message.tenant_id == tenant_id,
|
||||
)
|
||||
)
|
||||
valid_ids = list(msg_result.scalars().all())
|
||||
|
||||
if not valid_ids:
|
||||
return 0
|
||||
|
||||
# Carica associazioni esistenti per evitare duplicati
|
||||
existing_result = await self.db.execute(
|
||||
select(FascicoloMessage.message_id).where(
|
||||
FascicoloMessage.fascicolo_id == fascicolo_id,
|
||||
FascicoloMessage.message_id.in_(valid_ids),
|
||||
)
|
||||
)
|
||||
existing_ids = set(existing_result.scalars().all())
|
||||
|
||||
added = 0
|
||||
for msg_id in valid_ids:
|
||||
if msg_id not in existing_ids:
|
||||
self.db.add(
|
||||
FascicoloMessage(
|
||||
fascicolo_id=fascicolo_id,
|
||||
message_id=msg_id,
|
||||
added_by=added_by,
|
||||
)
|
||||
)
|
||||
added += 1
|
||||
|
||||
if added:
|
||||
await self.db.commit()
|
||||
|
||||
return added
|
||||
|
||||
async def remove_messages(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
fascicolo_id: uuid.UUID,
|
||||
message_ids: list[uuid.UUID],
|
||||
) -> int:
|
||||
"""Rimuove messaggi dal fascicolo. Restituisce il numero rimosso."""
|
||||
await self.get_fascicolo(tenant_id, fascicolo_id)
|
||||
|
||||
result = await self.db.execute(
|
||||
delete(FascicoloMessage).where(
|
||||
FascicoloMessage.fascicolo_id == fascicolo_id,
|
||||
FascicoloMessage.message_id.in_(message_ids),
|
||||
).returning(FascicoloMessage.message_id)
|
||||
)
|
||||
removed = len(result.fetchall())
|
||||
if removed:
|
||||
await self.db.commit()
|
||||
return removed
|
||||
|
||||
# ─── Fascicoli di un messaggio ────────────────────────────────────────────
|
||||
|
||||
async def get_message_fascicoli(
|
||||
self,
|
||||
tenant_id: uuid.UUID,
|
||||
message_id: uuid.UUID,
|
||||
) -> list[Fascicolo]:
|
||||
"""Restituisce i fascicoli a cui appartiene un messaggio."""
|
||||
result = await self.db.execute(
|
||||
select(Fascicolo)
|
||||
.join(FascicoloMessage, Fascicolo.id == FascicoloMessage.fascicolo_id)
|
||||
.where(
|
||||
FascicoloMessage.message_id == message_id,
|
||||
Fascicolo.tenant_id == tenant_id,
|
||||
)
|
||||
.order_by(Fascicolo.titolo)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
Reference in New Issue
Block a user