"""Supplier ↔ material link service (v2.6/v2.7) — the direct edge of the catalog flow. Suppliers can supply any global material across categories. """ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.catalog import Material from app.models.procurement import Supplier, SupplierMaterial from app.utils.errors import NotFoundError, ValidationError async def link_supplier_material( db: AsyncSession, supplier_id, material_id, is_active: bool = True ) -> SupplierMaterial: """Upsert a supplier<->material link (idempotent). Caller is responsible for the surrounding transaction. """ supplier = await db.get(Supplier, supplier_id) if supplier is None: raise NotFoundError("تأمین‌کننده یافت نشد", field="supplier_id") material = await db.get(Material, material_id) if material is None: raise NotFoundError("متریال یافت نشد", field="material_id") link = await db.scalar( select(SupplierMaterial).where( SupplierMaterial.supplier_id == supplier_id, SupplierMaterial.material_id == material_id, ) ) if link is not None: if not link.is_active and is_active: link.is_active = True return link link = SupplierMaterial(supplier_id=supplier_id, material_id=material_id, is_active=is_active) db.add(link) await db.flush() return link async def unlink_supplier_material(db: AsyncSession, supplier_id, material_id) -> None: """Remove the link.""" link = await db.scalar( select(SupplierMaterial).where( SupplierMaterial.supplier_id == supplier_id, SupplierMaterial.material_id == material_id, ) ) if link is None: raise ValidationError("این متریال به این تأمین‌کننده متصل نیست", field="material_id") await db.delete(link) await db.flush()