import uuid from typing import Literal from fastapi import APIRouter, Depends, Query, Request from sqlalchemy.ext.asyncio import AsyncSession from app.db.engine import get_db from app.routers.pm import ArrivalScope, arrivals_scope from app.schemas.project_transfers import ( ProjectTransferCreate, ProjectTransferOut, ProjectTransferReceive, ProjectTransferUpdate, ) from app.services import project_transfer_service from app.utils.errors import ForbiddenError router = APIRouter(tags=["project-transfers"]) def _to_transfer_out(t) -> dict: return { "id": t.id, "from_project": ( {"id": t.from_project.id, "name": t.from_project.name} if t.from_project else {"id": t.from_project_id, "name": ""} ), "from_warehouse": ( {"id": t.from_warehouse.id, "name": t.from_warehouse.name} if t.from_warehouse else {"id": t.from_warehouse_id, "name": ""} ), "to_project": ( {"id": t.to_project.id, "name": t.to_project.name} if t.to_project else {"id": t.to_project_id, "name": ""} ), "to_warehouse": ( {"id": t.to_warehouse.id, "name": t.to_warehouse.name} if t.to_warehouse else None ), "material": ( {"id": t.material.id, "name": t.material.name} if t.material else {"id": t.material_id, "name": ""} ), "brand": ( {"id": t.brand.id, "name": t.brand.name} if t.brand else None ), "unit": ( {"id": t.unit.id, "code": t.unit.code, "name_fa": t.unit.name_fa} if t.unit else {"id": t.unit_id, "code": "", "name_fa": None} ), "quantity": t.quantity, "specs": t.specs or {}, "specs_hash": t.specs_hash, "note": t.note, "status": t.status, "transfer_date": t.transfer_date, "transferred_by": t.transferred_by, "received_at": t.received_at, "received_by": t.received_by, "receipt_allocations": t.receipt_allocations, "created_at": t.created_at, "updated_at": t.updated_at, } # --------------------------------------------------------------------------- # POST /inventory/project-transfers — Create inter-project transfer # --------------------------------------------------------------------------- @router.post("/inventory/project-transfers", status_code=201) async def create_transfer( payload: ProjectTransferCreate, ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): transfer = await project_transfer_service.create_project_transfer( db=db, payload=payload, caller_project_id=ctx.project_id, caller_role=ctx.role, transferred_by=ctx.display_name, ) return {"data": _to_transfer_out(transfer)} # --------------------------------------------------------------------------- # GET /inventory/project-transfers — List transfers with direction and filters # --------------------------------------------------------------------------- @router.get("/inventory/project-transfers") async def list_transfers( project_id: uuid.UUID | None = Query(default=None), direction: Literal["outgoing", "incoming", "all"] = Query(default="all"), status: str | None = Query(default=None), ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): target_pid = project_id or ctx.project_id transfers = await project_transfer_service.list_project_transfers( db=db, project_id=target_pid, direction=direction, status=status, ) return {"data": [_to_transfer_out(t) for t in transfers]} # --------------------------------------------------------------------------- # GET /inventory/project-transfers/{id} — Single transfer details # --------------------------------------------------------------------------- @router.get("/inventory/project-transfers/{transfer_id}") async def get_transfer( transfer_id: uuid.UUID, ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): transfer = await project_transfer_service.get_project_transfer_or_404(db, transfer_id) if ctx.role == "pm" and ctx.project_id is not None: if transfer.from_project_id != ctx.project_id and transfer.to_project_id != ctx.project_id: raise ForbiddenError("شما به این رکورد انتقال دسترسی ندارید") return {"data": _to_transfer_out(transfer)} # --------------------------------------------------------------------------- # PATCH /inventory/project-transfers/{id} — Edit in-transit transfer (source PM only) # --------------------------------------------------------------------------- @router.patch("/inventory/project-transfers/{transfer_id}") async def update_transfer( transfer_id: uuid.UUID, payload: ProjectTransferUpdate, ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): transfer = await project_transfer_service.update_project_transfer( db=db, transfer_id=transfer_id, payload=payload, caller_project_id=ctx.project_id, caller_role=ctx.role, ) return {"data": _to_transfer_out(transfer)} # --------------------------------------------------------------------------- # DELETE /inventory/project-transfers/{id} — Cancel in-transit transfer (source PM only) # --------------------------------------------------------------------------- @router.delete("/inventory/project-transfers/{transfer_id}") async def delete_transfer( transfer_id: uuid.UUID, ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): res = await project_transfer_service.delete_project_transfer( db=db, transfer_id=transfer_id, caller_project_id=ctx.project_id, caller_role=ctx.role, ) return {"data": res} # --------------------------------------------------------------------------- # GET /pm/transfers/on-the-way — Incoming in-transit transfers for active project # --------------------------------------------------------------------------- @router.get("/pm/transfers/on-the-way") async def pm_incoming_transfers( ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): if ctx.role == "pm" and ctx.project_id is None: raise ForbiddenError("شما به هیچ پروژه‌ای متصل نیستید") transfers = await project_transfer_service.list_project_transfers( db=db, project_id=ctx.project_id, direction="incoming", status="in_transit", ) return {"data": [_to_transfer_out(t) for t in transfers]} # --------------------------------------------------------------------------- # POST /inventory/project-transfers/{id}/receive — Destination PM confirms arrival # --------------------------------------------------------------------------- @router.post("/inventory/project-transfers/{transfer_id}/receive") async def receive_transfer( transfer_id: uuid.UUID, payload: ProjectTransferReceive, ctx: ArrivalScope = Depends(arrivals_scope), db: AsyncSession = Depends(get_db), ): transfer = await project_transfer_service.receive_project_transfer( db=db, transfer_id=transfer_id, payload=payload, caller_project_id=ctx.project_id, caller_role=ctx.role, received_by=ctx.display_name, ) return {"data": _to_transfer_out(transfer)}