Integración Shopify → SAP Business One (snobbycl y on-chile)
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>
This commit is contained in:
commit
901dd8763d
47 files changed
+4709
No files matched your search
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
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()
|
||||
Reference in new issue
Block a user