""" Integra órdenes puntuales de una tienda, de inicio a fin (Invoice -> Payment -> ITR -> StockTransfer -> Delivery), sin pasar por el ciclo del scheduler. Por defecto es DRY-RUN: valida cada orden y muestra el payload de la Invoice y del Payment (este último armado sobre un total equivalente) sin escribir en SAP ni en la base de datos. Con --apply inserta la orden en `orders` y corre el pipeline real para ese id (crea documentos en SAP). Uso: python -m tools.integrar_ordenes [...] # dry-run python -m tools.integrar_ordenes [...] --apply """ import json import sys from pathlib import Path from core.config import cargar_config_tienda from core.db import get_order_sys_id, insert_order_header, db_session from core.logger_manager import order_context from core.sap import SAPServiceLayerClient from core.shopify_client import ShopifyClient from domain.business_partner import buscar_rut_en_order from domain.inventory_transfer import resolver_location from domain.invoice import construir_invoice from domain.payment import PaymentConfigError, construir_incoming_payment from domain.rut import formatear_rut from pipeline.runner import ESTADOS_FINANCIEROS_FACTURABLES, analizar_y_procesar_orden STORES_DIR = Path(__file__).resolve().parent.parent / "config" / "stores" def _dry_run(cfg: dict, shopify: ShopifyClient, order_id: int) -> None: detalle = shopify.obtener_orden(order_id) print(f"{detalle.get('name')} | financial={detalle.get('financial_status')} fulfillment={detalle.get('fulfillment_status')}") if detalle.get("cancelled_at"): print(f"-> cancelled (anulada el {detalle['cancelled_at'][:10]}): no se crearía nada") return if detalle.get("financial_status") not in ESTADOS_FINANCIEROS_FACTURABLES or not detalle.get("fulfillments"): print("-> not_eligible (no está pagada o no tiene fulfillments): no se crearía nada") return envio = float(((detalle.get("total_shipping_price_set") or {}).get("shop_money") or {}).get("amount", 0) or 0) if envio > 0: print(f"-> error: costo de envío ({envio}) no soportado") return rut = buscar_rut_en_order(detalle) card_code = cfg["sap"]["generic_card_code"] if not rut else "(se resuelve en SAP por RUT)" print(f"RUT: {rut or 'sin RUT en note_attributes'} -> CardCode: {card_code}") for f in detalle["fulfillments"]: loc = resolver_location(cfg, f["location_id"]) print(f"fulfillment {f['id']} location {f['location_id']} -> {loc}") if rut: print("(orden con RUT: la Invoice real depende del BusinessPartner en SAP; se muestra con CardCode genérico)") transacciones = shopify.obtener_transacciones(order_id) payload = construir_invoice( detalle, cfg, cfg["sap"]["generic_card_code"], federal_tax_id=formatear_rut(rut) if rut else None, transactions=transacciones ) print("--- Invoice ---") print(json.dumps(payload, indent=2, ensure_ascii=False)) total = sum(l["Quantity"] * l["PriceAfterVAT"] for l in payload["DocumentLines"]) invoice_sim = {"CardCode": payload["CardCode"], "CardName": payload.get("CardName", ""), "DocDate": payload["DocDate"]} try: pago = construir_incoming_payment(detalle, transacciones, cfg, invoice_sim, 0, total) print("--- IncomingPayment (DocEntry de la Invoice = 0 en dry-run) ---") print(json.dumps(pago, indent=2, ensure_ascii=False)) except PaymentConfigError as exc: print(f"--- IncomingPayment: ERROR, no se crearía el pago: {exc}") def _apply(cfg: dict, shopify: ShopifyClient, order_id: int) -> None: store_code = cfg["store_code"] orden = shopify.obtener_orden(order_id) insert_order_header( store_code, { "shopify_order_id": orden["id"], "order_number": orden.get("name"), "tags": orden.get("tags"), "shopify_created_at": orden.get("created_at"), "financial_status": orden.get("financial_status"), "fulfillment_status": orden.get("fulfillment_status"), }, ) sys_id = get_order_sys_id(store_code, order_id) with db_session() as conn: row = dict(conn.execute("SELECT * FROM orders WHERE sys_id = ?", (sys_id,)).fetchone()) with order_context(store_code, order_id): ok = analizar_y_procesar_orden(cfg, row) with db_session() as conn: estado = conn.execute("SELECT status, last_error FROM orders WHERE sys_id = ?", (sys_id,)).fetchone() docs = conn.execute( "SELECT doc_type, status, sap_doc_num, error_message FROM order_documents WHERE order_sys_id = ? ORDER BY sequence", (sys_id,), ).fetchall() print(f"resultado={ok} status={estado['status']} last_error={estado['last_error']}") for d in docs: print(f" {d['doc_type']}: {d['status']} DocNum={d['sap_doc_num']} {d['error_message'] or ''}") def main() -> None: args = sys.argv[1:] apply = "--apply" in args args = [a for a in args if a != "--apply"] store_code, ids = args[0], [int(x) for x in args[1:]] cfg = cargar_config_tienda(STORES_DIR / f"{store_code}.json") shopify = ShopifyClient.from_store_config(cfg["shopify"]) print(f"MODO: {'APPLY (escribe en SAP)' if apply else 'DRY-RUN (no escribe nada)'} | tienda {store_code} | {len(ids)} órdenes") for order_id in ids: print(f"\n===== Orden {order_id} =====") (_apply if apply else (lambda c, s, o: _dry_run(c, s, o)))(cfg, shopify, order_id) if __name__ == "__main__": main()