Files
ERP/backend/app/routers/supplier_materials.py
T
MMD 0ea0322967 Revert 253c922: restore the PM/RBAC work to the integration
Reverting the production revert so the pm branch can merge cleanly on
top of the restored RBAC base. Conflicts against newer integration work
resolved by combining:
- App.tsx: v7's bot-draft wizard bypass + login returnUrl kept alongside
  the supervisor gating (no-project block screen, panel scoping)
- LoginPage: returnUrl navigation kept, supervisor routing added
- migration.sql: bot_drafts table kept, unified-projects ALTERs restored
2026-10-01 15:24:56 +03:30

110 lines
4.4 KiB
Python

"""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_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_role("admin", "procurement_manager")
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}