"""Supplier-assignment inheritance for newly created spools (#2988). A new spool of a product that other spools already carry supplier assignments for should arrive knowing where it can be bought — whether it is created manually, via the API, or by the RFID auto-add. Only the source list is copied; ``is_purchase_source`` is deliberately reset, because where THIS spool was bought is not something the donor can know. """ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from backend.app.models.spool import Spool from backend.app.models.supplier import SpoolSupplier async def find_supplier_link_templates_for_product( db: AsyncSession, *, material: str | None, subtype: str | None, brand: str | None, color_name: str | None, ) -> list[dict]: """Return supplier-link field dicts from the newest matching spool. Product identity is the (material, subtype, brand, color_name) string tuple — the same key FilamentSkuSettings groups by. Archived spools count as donors: a product being out of stock doesn't change where it can be bought. """ if not material: return [] def _same(column, value): return column.is_(None) if value is None else column == value donor = await db.execute( select(Spool.id) .join(SpoolSupplier, SpoolSupplier.spool_id == Spool.id) .where( Spool.material == material, _same(Spool.subtype, subtype), _same(Spool.brand, brand), _same(Spool.color_name, color_name), ) .order_by(Spool.updated_at.desc()) .limit(1) ) donor_id = donor.scalars().first() if donor_id is None: return [] links = await db.execute(select(SpoolSupplier).where(SpoolSupplier.spool_id == donor_id)) return [ { "supplier_id": link.supplier_id, "supplier_article_number": link.supplier_article_number, "quoted_price_per_kg": link.quoted_price_per_kg, "is_purchase_source": False, } for link in links.scalars().all() ] async def apply_supplier_inheritance(db: AsyncSession, spool: Spool) -> None: """Copy supplier assignments onto a freshly created (flushed) spool. No-op when the spool already has assignments. The caller commits. """ existing = await db.execute(select(SpoolSupplier.id).where(SpoolSupplier.spool_id == spool.id).limit(1)) if existing.first() is not None: return templates = await find_supplier_link_templates_for_product( db, material=spool.material, subtype=spool.subtype, brand=spool.brand, color_name=spool.color_name, ) _attach(db, spool, templates) async def apply_supplier_inheritance_to_batch(db: AsyncSession, spools: list[Spool]) -> None: """Inheritance for a bulk create: one donor lookup for the whole batch. Every copy is built from the same payload, so the product tuple — and with it the answer — is identical for all of them. The caller commits. """ if not spools: return first = spools[0] templates = await find_supplier_link_templates_for_product( db, material=first.material, subtype=first.subtype, brand=first.brand, color_name=first.color_name, ) for spool in spools: _attach(db, spool, templates) def _attach(db: AsyncSession, spool: Spool, templates: list[dict]) -> None: for template in templates: db.add(SpoolSupplier(spool_id=spool.id, **template))