""" Utilidad puntual (no forma parte del pipeline recurrente): recorre las órdenes ya descargadas en SQLite para una tienda, trae el detalle completo de cada una desde Shopify, extrae SKU + nombre de line_items, deduplica por SKU, y crea en SAP los Items faltantes (grupo 108, artículo de venta + inventario). Uso: .venv/bin/python tools/cargar_items_faltantes.py config/stores/snobbycl.json .venv/bin/python tools/cargar_items_faltantes.py config/stores/snobbycl.json --apply """ import argparse import json import sys from pathlib import Path from typing import Dict, List sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from core.config import cargar_config_tienda from core.db import db_session from core.errors import SAPServiceLayerError from core.sap import SAPServiceLayerClient from core.shopify_client import ShopifyClient from domain.items import item_existe ITEMS_GROUP_CODE = 108 def obtener_shopify_order_ids(store_code: str) -> List[int]: with db_session() as conn: rows = conn.execute( "SELECT shopify_order_id FROM orders WHERE store_code = ? ORDER BY shopify_order_id", (store_code,), ).fetchall() return [r["shopify_order_id"] for r in rows] def extraer_skus(shopify: ShopifyClient, order_ids: List[int]) -> Dict[str, str]: """Devuelve {sku: nombre} deduplicado, recorriendo el detalle de cada orden.""" skus: Dict[str, str] = {} for idx, order_id in enumerate(order_ids, start=1): detalle = shopify.obtener_orden(order_id) for item in detalle.get("line_items", []): sku = (item.get("sku") or "").strip() nombre = item.get("name") or item.get("title") or sku if sku and sku not in skus: skus[sku] = nombre print(f" [{idx}/{len(order_ids)}] orden {order_id}: {len(detalle.get('line_items', []))} line_items") return skus def crear_item(sap: SAPServiceLayerClient, sku: str, nombre: str) -> None: payload = { "ItemCode": sku, "ItemName": nombre[:100], "ItemsGroupCode": ITEMS_GROUP_CODE, "SalesItem": "tYES", "InventoryItem": "tYES", "PurchaseItem": "tNO", } sap.forward("POST", "Items", json_data=payload) def main(store_config_path: str, dry_run: bool) -> None: store_config = cargar_config_tienda(store_config_path) store_code = store_config["store_code"] shopify = ShopifyClient.from_store_config(store_config["shopify"]) sap = SAPServiceLayerClient.from_store_config(store_config["sap_connection"]) order_ids = obtener_shopify_order_ids(store_code) print(f"Órdenes a revisar: {len(order_ids)}") skus = extraer_skus(shopify, order_ids) print(f"\nSKUs únicos encontrados: {len(skus)}\n") existentes, creados, errores = 0, 0, 0 for sku, nombre in skus.items(): if item_existe(sap, sku): existentes += 1 continue if dry_run: print(f"[DRY RUN] Crearía: {sku!r} - {nombre!r}") continue try: crear_item(sap, sku, nombre) creados += 1 print(f"Creado: {sku} - {nombre}") except SAPServiceLayerError as exc: errores += 1 print(f"ERROR creando {sku}: {exc.detail}") faltantes = len(skus) - existentes print(f"\nResumen: ya existían={existentes} | faltantes detectados={faltantes} | creados={creados} | errores={errores}") if dry_run: print("(dry-run: no se escribió nada en SAP, corré con --apply para crear los items)") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("store_config", help="Ruta al JSON de configuración de la tienda") parser.add_argument("--apply", action="store_true", help="Ejecuta el POST real (por defecto es dry-run)") args = parser.parse_args() main(args.store_config, dry_run=not args.apply)