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>
7.1 KiB
Esquema de base de datos (SQLite)
Archivo: data/shopify_sap.db. Definido y accedido desde core/db.py. Tres
tablas: orders (cabecera), order_fulfillments (un fulfillment de
Shopify) y order_documents (un documento SAP requerido/creado).
erDiagram
orders ||--o{ order_fulfillments : "tiene"
orders ||--o{ order_documents : "tiene"
order_fulfillments ||--o{ order_documents : "tiene (si aplica)"
orders {
int sys_id PK
string store_code
int shopify_order_id
string order_number
string tags
string payment_gateway_names
string shopify_created_at
string time_insert
string time_update
string financial_status
string fulfillment_status
string card_code
string status
string last_error
}
order_fulfillments {
int sys_id PK
int order_sys_id FK
int shopify_fulfillment_id
string location_id
bool is_store
string warehouse_code
string time_insert
}
order_documents {
int sys_id PK
int order_sys_id FK
int fulfillment_sys_id FK
string doc_type
int sequence
bool required
string status
int sap_doc_entry
int sap_doc_num
int base_doc_entry
string error_message
int attempts
string time_update
}
orders
Una fila por orden de Shopify (UNIQUE(store_code, shopify_order_id), así
el mismo id de Shopify puede repetirse entre tiendas distintas).
| Columna | Tipo | Descripción |
|---|---|---|
sys_id |
INTEGER PK | id interno, autoincremental |
store_code |
TEXT | qué tienda (config/stores/<store_code>.json) |
shopify_order_id |
INTEGER | id de Shopify |
order_number |
TEXT | campo name de Shopify (ej. "#7863") |
tags |
TEXT | tal cual viene de Shopify, separado por coma |
payment_gateway_names |
TEXT | medio(s) de pago, unidos con " + " si hay más de uno |
shopify_created_at |
TEXT | fecha de creación real en Shopify — dispara el timeout |
time_insert |
TEXT | cuándo se descargó/insertó por primera vez (auditoría) |
time_update |
TEXT | última vez que se tocó esta orden — define el orden de la cola (ver status) |
financial_status |
TEXT | último valor conocido de Shopify (puede cambiar entre corridas) |
fulfillment_status |
TEXT | ídem — null/partial/fulfilled |
card_code |
TEXT | CardCode de SAP ya resuelto (se cachea, no se vuelve a resolver) |
status |
TEXT | ver tabla de estados abajo |
last_error |
TEXT | último mensaje de error (ya diagnosticado, no el crudo de SAP si se reconoció) |
Valores de status
| Valor | Terminal | Significado |
|---|---|---|
pending |
No | Cabecera descargada, aún no analizada |
not_eligible |
No | No está paid, o no tiene fulfillments todavía |
in_progress |
No | Tiene al menos un fulfillment procesado, pero fulfillment_status != "fulfilled" (pueden llegar más) |
error |
No | El último intento falló en algún documento; se reintenta en la próxima rotación |
integrated |
Sí | Todos los documentos requeridos están created y fulfillment_status == "fulfilled" |
timeout |
Sí | Pasaron más de timeout_days desde shopify_created_at sin llegar a integrated |
cancelled |
Sí | La orden tiene cancelled_at en Shopify (anulada). El pipeline la marca solo, con la fecha y el cancel_reason en last_error; nunca crea documentos en SAP |
ignored |
Sí | Marcada a mano para que el pipeline no la procese nunca (ej. pedidos de prueba). No se asigna automáticamente |
Solo integrated, timeout, ignored y cancelled son terminales —
get_pending_orders y mark_orders_timeout excluyen esos cuatro, todo lo demás
se sigue reintentando.
order_fulfillments
Una fila por fulfillment de Shopify (UNIQUE(order_sys_id, shopify_fulfillment_id)). Una orden puede tener varios (envío parcial).
| Columna | Tipo | Descripción |
|---|---|---|
sys_id |
INTEGER PK | |
order_sys_id |
FK → orders.sys_id |
|
shopify_fulfillment_id |
INTEGER | id del fulfillment en Shopify |
location_id |
TEXT | location_id de Shopify de ese fulfillment |
is_store |
INTEGER (bool) | snapshot de config.sap.locations[location_id].is_store al momento de procesarlo |
warehouse_code |
TEXT | bodega SAP resuelta para ese location_id |
time_insert |
TEXT | cuándo se detectó este fulfillment |
order_documents
Una fila por documento SAP requerido (exista o no todavía). Documentos a
nivel orden (INVOICE, INCOMING_PAYMENT) tienen fulfillment_sys_id = NULL; los de nivel fulfillment (INVENTORY_TRANSFER_REQUEST,
STOCK_TRANSFER, DELIVERY) lo tienen seteado.
| Columna | Tipo | Descripción |
|---|---|---|
sys_id |
INTEGER PK | |
order_sys_id |
FK → orders.sys_id |
|
fulfillment_sys_id |
FK → order_fulfillments.sys_id, nullable |
NULL para documentos de orden |
doc_type |
TEXT | INVOICE, INCOMING_PAYMENT, INVENTORY_TRANSFER_REQUEST, STOCK_TRANSFER, DELIVERY |
sequence |
INTEGER | orden de creación (10/20/30/40/50 — incrementos de 10 para insertar tipos nuevos sin reordenar) |
required |
INTEGER (bool) | si este documento aplica para esta orden/fulfillment |
status |
TEXT | pending / created / error |
sap_doc_entry / sap_doc_num |
INTEGER | identificadores reales en SAP, una vez creado |
base_doc_entry |
INTEGER | (reservado, no usado todavía — el BaseEntry real se arma al vuelo en domain/* a partir del documento base) |
error_message |
TEXT | mensaje ya diagnosticado (ver domain/documents.py::diagnosticar_error) |
attempts |
INTEGER | cuántas veces falló |
time_update |
TEXT | última vez que se tocó esta fila |
Valores de status
| Valor | Significado |
|---|---|
pending |
Fila creada (el documento es requerido), todavía no se intentó |
created |
Ya existe en SAP (sap_doc_entry/sap_doc_num poblados) — en un reintento, se relee con GET en vez de volver a crear |
error |
Falló el último intento — se reintenta en la próxima corrida |
Funciones de core/db.py por tabla
orders: insert_order_header, get_last_shopify_order_id,
get_order_sys_id, mark_orders_timeout, get_pending_orders,
set_order_status, set_order_card_code, update_order_snapshot.
order_fulfillments: ensure_order_fulfillment (insert-si-no-existe,
por shopify_fulfillment_id).
order_documents: ensure_order_document (insert-si-no-existe, por
doc_type + fulfillment_sys_id), get_order_document,
mark_document_created, mark_document_error.
Nota: precisión de time_update
Todos los UPDATE/DEFAULT de timestamp usan
strftime('%Y-%m-%d %H:%M:%f','now') (milisegundos), no datetime('now')
(que solo tiene precisión de segundo). Esto es necesario para que la
rotación justa de get_pending_orders (ORDER BY time_update ASC)
funcione bien cuando el pipeline toca varias órdenes rápido dentro del
mismo segundo.