Pipeline de integración de órdenes de Shopify a SAP Service Layer:
Invoice de reserva, pago con tarjeta o transferencia por transacción,
solicitud de traslado, traslado de stock y entrega.
- Invoice igual a lo cobrado; fulfillments limitados a lo facturado
- Estados terminales integrated, timeout, ignored y cancelled
- Idempotencia contra SAP: adopta documentos existentes en vez de duplicar
- Reintentos de red en el cliente de Shopify
- Secretos fuera de los JSON de tienda (${VARIABLE} resuelta desde .env)
- Herramientas de consulta, integración puntual y carga de pruebas
- Documentación en docs/ (flujo, casos especiales, mapeo, configuración)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
69 lines
3.0 KiB
Python
69 lines
3.0 KiB
Python
"""
|
|
Dry-run del IncomingPayment: para una o más órdenes de una tienda descarga
|
|
detalle y transacciones de Shopify (GET), toma la Invoice de SAP si la orden
|
|
ya está integrada (GET) o una sintética si no, y muestra el JSON de pago que
|
|
se enviaría. NO escribe en SAP ni en la base de datos.
|
|
|
|
Uso: python -m tools.probar_pago <store_code> <shopify_order_id> [...]
|
|
"""
|
|
import json
|
|
import sys
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
from core.config import cargar_config_tienda
|
|
from core.db import db_session
|
|
from core.sap import SAPServiceLayerClient
|
|
from core.shopify_client import ShopifyClient
|
|
from domain.payment import PaymentConfigError, construir_incoming_payment, transacciones_efectivas
|
|
|
|
STORES_DIR = Path(__file__).resolve().parent.parent / "config" / "stores"
|
|
|
|
|
|
def _invoice(sap: SAPServiceLayerClient, store_code: str, order_id: int, detalle: dict) -> tuple[dict, str]:
|
|
with db_session() as conn:
|
|
row = conn.execute(
|
|
"""
|
|
SELECT d.sap_doc_entry FROM order_documents d JOIN orders o ON o.sys_id = d.order_sys_id
|
|
WHERE o.store_code = ? AND o.shopify_order_id = ? AND d.doc_type = 'INVOICE' AND d.status = 'created'
|
|
""",
|
|
(store_code, order_id),
|
|
).fetchone()
|
|
if row:
|
|
return sap.forward("GET", f"Invoices({row['sap_doc_entry']})").json(), f"Invoice real DocEntry {row['sap_doc_entry']}"
|
|
|
|
sintetica = {
|
|
"DocEntry": 0,
|
|
"CardCode": "C99998",
|
|
"CardName": "(Invoice sintética)",
|
|
"DocDate": detalle["created_at"][:10],
|
|
"DocumentLines": [{"Quantity": 1, "PriceAfterVAT": float(detalle["total_price"])}],
|
|
}
|
|
return sintetica, "Invoice sintética (orden aún sin Invoice en SAP)"
|
|
|
|
|
|
def main() -> None:
|
|
store_code, ids = sys.argv[1], [int(x) for x in sys.argv[2:]]
|
|
cfg = cargar_config_tienda(STORES_DIR / f"{store_code}.json")
|
|
shopify = ShopifyClient.from_store_config(cfg["shopify"])
|
|
sap = SAPServiceLayerClient.from_store_config(cfg["sap_connection"])
|
|
|
|
for order_id in ids:
|
|
print(f"\n===== Orden {order_id} =====")
|
|
detalle = shopify.obtener_orden(order_id)
|
|
transacciones = shopify.obtener_transacciones(order_id)
|
|
invoice, origen = _invoice(sap, store_code, order_id, detalle)
|
|
total = sum(Decimal(str(l["Quantity"])) * Decimal(str(l["PriceAfterVAT"])) for l in invoice["DocumentLines"])
|
|
|
|
print(f"{detalle.get('name')} | payment_gateway_names={detalle.get('payment_gateway_names')} | total_price={detalle.get('total_price')}")
|
|
print(f"{len(transacciones)} transacciones -> {len(transacciones_efectivas(transacciones))} efectivas | {origen} | total Invoice={total}")
|
|
try:
|
|
payload = construir_incoming_payment(detalle, transacciones, cfg, invoice, invoice["DocEntry"], float(total))
|
|
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
except PaymentConfigError as exc:
|
|
print(f"ERROR (no se crearía el pago): {exc}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|