"""Supplier <-> Material catalog links (v2.5). GET/POST /suppliers/{id}/materials — manage a supplier's catalog. DELETE /suppliers/{id}/materials/{material_id} — unlink. GET /materials/{id}/suppliers — price-comparison data source. """ from fastapi import APIRouter, Depends, Query from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.auth import require_procurement_writer, require_role from app.db.engine import get_db from app.models.catalog import Material, MaterialCategory from app.models.procurement import Supplier, SupplierMaterial from app.schemas.procurement import SupplierMaterialCreate from app.services.supplier_material_service import link_supplier_material, unlink_supplier_material from app.utils.errors import NotFoundError from app.utils.pagination import paginate from app.utils.serialization import to_iso router = APIRouter(tags=["supplier-materials"]) _procurement_writer = require_procurement_writer() def link_to_dict(link: SupplierMaterial, material: Material | None = None, category: MaterialCategory | None = None, supplier: Supplier | None = None) -> dict: return { "id": str(link.id), "supplier_id": str(link.supplier_id), "material_id": str(link.material_id), "is_active": link.is_active, "last_quoted_price": float(link.last_quoted_price) if link.last_quoted_price is not None else None, "last_quoted_at": to_iso(link.last_quoted_at) if link.last_quoted_at else None, "created_at": to_iso(link.created_at), "material": { "id": str(material.id), "name": material.name, "category": {"id": str(category.id), "name": category.name} if category else None, } if material else None, "supplier": {"id": str(supplier.id), "name": supplier.name} if supplier else None, } @router.get("/suppliers/{supplier_id}/materials") async def list_supplier_materials( supplier_id, page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), ): supplier = await db.get(Supplier, supplier_id) if supplier is None: raise NotFoundError("تأمین‌کننده یافت نشد") stmt = ( select(SupplierMaterial) .where(SupplierMaterial.supplier_id == supplier_id) .order_by(SupplierMaterial.created_at) ) items, meta = await paginate(stmt, page, page_size, db) mat_ids = {l.material_id for l in items} materials = {m.id: m for m in (await db.execute(select(Material).where(Material.id.in_(mat_ids)))).scalars()} return {"data": [link_to_dict(l, materials.get(l.material_id), None) for l in items], "meta": meta} @router.post("/suppliers/{supplier_id}/materials", status_code=201) async def create_supplier_material( supplier_id, payload: SupplierMaterialCreate, db: AsyncSession = Depends(get_db), _auth=Depends(_procurement_writer), ): async with db.begin(): link = await link_supplier_material(db, supplier_id, payload.material_id) material = await db.get(Material, payload.material_id) return {"data": link_to_dict(link, material, None)} @router.delete("/suppliers/{supplier_id}/materials/{material_id}") async def delete_supplier_material( supplier_id, material_id, db: AsyncSession = Depends(get_db), _auth=Depends(_procurement_writer), ): async with db.begin(): await unlink_supplier_material(db, supplier_id, material_id) return {"data": {"supplier_id": str(supplier_id), "material_id": str(material_id), "deleted": True}} @router.get("/materials/{material_id}/suppliers") async def list_material_suppliers( material_id, page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), ): material = await db.get(Material, material_id) if material is None: raise NotFoundError("متریال یافت نشد") stmt = ( select(SupplierMaterial) .where(SupplierMaterial.material_id == material_id) .order_by(SupplierMaterial.last_quoted_at.desc().nulls_last()) ) items, meta = await paginate(stmt, page, page_size, db) sup_ids = {l.supplier_id for l in items} suppliers = {s.id: s for s in (await db.execute(select(Supplier).where(Supplier.id.in_(sup_ids)))).scalars()} return {"data": [link_to_dict(l, supplier=suppliers.get(l.supplier_id)) for l in items], "meta": meta}