from dataclasses import dataclass from commerce.models import Product, ProductBinding, SourceSystem @dataclass(frozen=True) class ProductMatch: product: Product | None method: str | None @property def matched(self) -> bool: return self.product is not None def _normalized(value: str | None) -> str: return (value or "").strip() def match_product( *, source: SourceSystem, external_id: str | None = None, external_sku: str | None = None, barcode: str | None = None, ) -> ProductMatch: """Resolve a source product using explicit bindings before safe identifiers.""" external_id = _normalized(external_id) external_sku = _normalized(external_sku) barcode = _normalized(barcode) if external_id: binding = ( ProductBinding.objects.select_related("product") .filter(source=source, external_id=external_id) .first() ) if binding: return ProductMatch(binding.product, "external_id") if external_sku: bindings = list( ProductBinding.objects.select_related("product").filter( source=source, external_sku__iexact=external_sku )[:2] ) if len(bindings) == 1: return ProductMatch(bindings[0].product, "external_sku") products = list(Product.objects.filter(sku__iexact=external_sku)[:2]) if len(products) == 1: return ProductMatch(products[0], "canonical_sku") if barcode: products = list(Product.objects.filter(barcode=barcode)[:2]) if len(products) == 1: return ProductMatch(products[0], "barcode") return ProductMatch(None, None)