Files
reseller-platform/backend/app/engines/importer.py

116 lines
3.8 KiB
Python

"""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