"""Feed importer: normalize -> validate -> upsert. NEVER auto-publishes products: new products are created with status 'imported' only. Invalid items (missing sku/title, non-positive cost, negative inventory) are skipped and logged to audit_logs. """ from decimal import Decimal from app.engines.suppliers.base import build_adapter from app.models import AuditLog, Product, SupplierProduct def validate(item): problems = [] if not (item.get("sku") or "").strip(): problems.append("missing sku") if not (item.get("title") or "").strip(): problems.append("missing title") try: cost = float(item.get("cost") or 0) if cost <= 0: problems.append("non-positive cost") except (TypeError, ValueError): problems.append("invalid cost") try: inventory = int(float(item.get("inventory") or 0)) if inventory < 0: problems.append("negative inventory") except (TypeError, ValueError): problems.append("invalid inventory") return problems def import_feed(db, supplier, feed_type=None, data_or_url=""): """Import a supplier feed into products + supplier_products. Returns summary dict: {supplier, received, imported, updated, skipped: [...]} """ adapter = build_adapter(supplier, data_or_url=data_or_url) items = adapter.get_products() summary = { "supplier": supplier.name, "supplier_id": str(supplier.id), "received": len(items), "imported": 0, "updated": 0, "skipped": [], } for item in items: problems = validate(item) if problems: reason = "; ".join(problems) summary["skipped"].append({"sku": item.get("sku"), "reason": reason}) db.add( AuditLog( actor="importer", action="import_skipped", entity="product", entity_id=item.get("sku"), detail={"supplier": supplier.name, "reason": reason}, ) ) continue product = db.query(Product).filter(Product.sku == item["sku"]).first() if product is None: product = Product(sku=item["sku"], status="imported") db.add(product) summary["imported"] += 1 else: summary["updated"] += 1 # set all required fields BEFORE flushing (title is NOT NULL in schema) product.title = item["title"] product.description = item.get("description") or product.description product.category = item.get("category") or product.category product.brand = item.get("brand") or product.brand product.cost = Decimal(str(item["cost"])) if item.get("image_url"): images = list(product.images or []) if item["image_url"] not in images: images.append(item["image_url"]) product.images = images db.flush() # product.id available now sp = ( db.query(SupplierProduct) .filter( SupplierProduct.supplier_id == supplier.id, SupplierProduct.supplier_sku == item["sku"], ) .first() ) if sp is None: sp = SupplierProduct( supplier_id=supplier.id, supplier_sku=item["sku"], product_id=product.id, ) db.add(sp) db.flush() else: sp.product_id = product.id sp.supplier_cost = Decimal(str(item["cost"])) sp.supplier_inventory = int(item.get("inventory") or 0) sp.shipping_cost = Decimal(str(item.get("shipping_cost") or 0)) sp.shipping_time = item.get("shipping_time") db.commit() return summary