Files
shopify_run_run/docs/esquema_base_datos.md
T

169 lines
7.1 KiB
Markdown
Raw Normal View History

# 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).
```mermaid
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.