"""Dropshipping integration service. Provides read-only inventory access for external dropshipping platforms and webhook dispatching on stock/price/sale events. """ import logging from services.inventory_engine import get_stock_bulk logger = logging.getLogger(__name__) def resolve_tenant_by_api_key(master_conn, api_key: str): """Find tenant_id and db_name for a given dropshipping API key. Returns (tenant_id, db_name) or (None, None) if invalid. """ if not api_key: return None, None cur = master_conn.cursor() # tenant_config lives in each tenant DB, so we need to scan tenants cur.execute("SELECT id, db_name FROM tenants WHERE is_active = true") tenants = cur.fetchall() for tid, db_name in tenants: try: tcur = master_conn.cursor() # Use dblink or connect to tenant DB? Simpler: the blueprint # will pass tenant_conn directly after resolution. # Instead, we store a mapping in master DB for speed. # For now, return all candidates and let caller validate. pass except Exception: continue cur.close() return None, None def _get_dropshipping_key(tenant_conn): cur = tenant_conn.cursor() cur.execute("SELECT value FROM tenant_config WHERE key = 'dropshipping_api_key'") row = cur.fetchone() cur.close() return row[0] if row else None def validate_api_key(tenant_conn, api_key: str) -> bool: """Check if the provided API key matches the tenant's configured key.""" if not api_key: return False expected = _get_dropshipping_key(tenant_conn) return expected is not None and expected == api_key def get_inventory_list(tenant_conn, search: str = None, page: int = 1, per_page: int = 50): """Return inventory items with stock and price for dropshipping.""" offset = (max(page, 1) - 1) * per_page stock_map = get_stock_bulk(tenant_conn, branch_id=None) cur = tenant_conn.cursor() params = [] where = "WHERE is_active = true" if search: where += " AND (name ILIKE %s OR part_number ILIKE %s)" params.extend([f"%{search}%", f"%{search}%"]) cur.execute( f""" SELECT id, part_number, name, brand, price_1, price_2, price_3, image_url, unit, description FROM inventory {where} ORDER BY id DESC LIMIT %s OFFSET %s """, params + [per_page, offset], ) rows = cur.fetchall() # Count total cur.execute(f"SELECT COUNT(*) FROM inventory {where}", params) total = cur.fetchone()[0] cur.close() items = [] for r in rows: inv_id = r[0] items.append({ "id": inv_id, "sku": r[1], "name": r[2], "brand": r[3], "price_1": float(r[4]) if r[4] else None, "price_2": float(r[5]) if r[5] else None, "price_3": float(r[6]) if r[6] else None, "stock": stock_map.get(inv_id, 0), "image_url": r[7], "unit": r[8], "description": r[9], }) return {"items": items, "page": page, "per_page": per_page, "total": total} def get_inventory_by_sku(tenant_conn, sku: str): """Return a single inventory item by SKU/part_number.""" stock_map = get_stock_bulk(tenant_conn, branch_id=None) cur = tenant_conn.cursor() cur.execute( """ SELECT id, part_number, name, brand, price_1, price_2, price_3, image_url, unit, description FROM inventory WHERE part_number = %s AND is_active = true LIMIT 1 """, (sku,), ) row = cur.fetchone() cur.close() if not row: return None inv_id = row[0] return { "id": inv_id, "sku": row[1], "name": row[2], "brand": row[3], "price_1": float(row[4]) if row[4] else None, "price_2": float(row[5]) if row[5] else None, "price_3": float(row[6]) if row[6] else None, "stock": stock_map.get(inv_id, 0), "image_url": row[7], "unit": row[8], "description": row[9], } def get_stock_by_skus(tenant_conn, skus: list[str]) -> dict: """Return stock levels for a list of SKUs.""" stock_map = get_stock_bulk(tenant_conn, branch_id=None) cur = tenant_conn.cursor() cur.execute( """ SELECT id, part_number FROM inventory WHERE part_number = ANY(%s) AND is_active = true """, (skus,), ) rows = cur.fetchall() cur.close() result = {} for inv_id, sku in rows: result[sku] = stock_map.get(inv_id, 0) return result def get_webhook_targets(tenant_conn, event_type: str) -> list[str]: """Return active webhook URLs for a given event type.""" cur = tenant_conn.cursor() cur.execute( """ SELECT target_url FROM dropshipping_webhooks WHERE event_type = %s AND is_active = true """, (event_type,), ) urls = [r[0] for r in cur.fetchall()] cur.close() return urls