5.9 KiB
Flujo de Proxy SAP
Función Central: forward_to_sapb1
Todas las operaciones CRUD pasan por esta función en core/sap.py, que actúa como proxy hacia SAP Service Layer. La mayoría de los routers (business_partners, items, sales, purchases, batch, parte de catalogs) no la llaman directamente: pasan por core/crud_router.py, una factory que arma el list/get/create/update de un recurso OData y delega en forward_to_sapb1 (ver Arquitectura). El proxy hacia el API Gateway de Crystal Reports (core/api_gateway.py, usado por routers/apigateway.py) sigue el mismo patrón de sesión-global + reintento en 401, mostrado más abajo bajo "Otros proxies".
flowchart TD
START([Inicio]) --> INPUT["method, endpoint_path,\njson_data, params, headers"]
INPUT --> BUILD["Construye URL:\nSAPB1_SL_URL / endpoint_path"]
BUILD --> HEADERS["Adjunta headers:\nCookie: B1SESSION=...\nContent-Type: application/json"]
HEADERS --> SEND[Envía request a SAP]
SEND --> CHECK{Respuesta}
CHECK -->|2xx| SUCCESS[Retorna Response al router]
CHECK -->|401| REFRESH["Force refresh:\nensure_sap_session()"]
CHECK -->|Timeout / Error| FAIL[ConnectionError]
REFRESH --> RETRY{Es reintento?}
RETRY -->|No| SEND2["Reintenta request\ncon nueva sesión"]
RETRY -->|Sí| ERROR["Extrae error.message.value\nde SAP y lanza HTTPException"]
SEND2 --> CHECK2{Respuesta}
CHECK2 -->|2xx| SUCCESS
CHECK2 -->|4xx/5xx| ERROR
SUCCESS --> END([Fin])
FAIL --> END
ERROR --> END
style ERROR fill:#f96,stroke:#a33
Diagrama de Secuencia — Operación CRUD Típica
Ejemplo con GET /api/Items:
sequenceDiagram
participant F as Frontend
participant MW as Middleware
participant R as router/items.py
participant C as core/sap.py
participant SAP as SAP Service Layer
F->>MW: GET /api/Items?$skip=0&$top=20\nX-Session-Token: [token]
MW->>MW: Valida sesión
MW->>R: request.state.emp_id = "123"
R->>C: forward_to_sapb1(\n GET, "Items?$skip=0&$top=20&$inlinecount=allpages")
C->>SAP: GET /b1s/v1/Items?$skip=0&$top=20\nCookie: B1SESSION=abc123
SAP-->>C: 200 - value: [...], odata.count: 150
C-->>R: Response
R-->>F: 200 - data: [...], total: 150
Re-autenticación Automática ante 401
Cuando SAP rechaza la sesión actual (401), el sistema renueva automáticamente la sesión de SAP y reintenta:
sequenceDiagram
participant R as Router
participant C as core/sap.py
participant SAP as SAP Service Layer
R->>C: forward_to_sapb1(GET, "Items?...")
C->>SAP: GET /b1s/v1/Items?...\nCookie: B1SESSION=abc123
SAP-->>C: 401 Unauthorized
Note over C: Sesión SAP expirada\n→ Re-autentica y reintenta
C->>SAP: POST /Login\nUserName: manager, Password: ..., CompanyDB: ...
SAP-->>C: 200 SessionId: xyz789
C->>C: Actualiza sap_session_store
C->>SAP: GET /b1s/v1/Items?...\nCookie: B1SESSION=xyz789
SAP-->>C: 200 - value: [...]
C-->>R: Response 200
Note over R,C: Si el reintento también falla (4xx/5xx)\n→ se extrae error.message.value\n→ se lanza HTTPException
Parámetros OData y Headers SAP Soportados
Query Parameters (OData)
| Parámetro | Uso | Ejemplo |
|---|---|---|
$skip |
Paginación — desplazamiento | $skip=20 |
$top |
Paginación — límite por página | $top=20 |
$select |
Selección de campos | $select=ItemCode,ItemName |
$filter |
Filtrado | $filter=ItemCode eq 'A001' |
$orderby |
Ordenamiento | $orderby=ItemCode asc |
$inlinecount |
Contar registros totales | $inlinecount=allpages |
Headers SAP
| Header | Función |
|---|---|
B1S-PageSize |
Tamaño de página personalizado |
B1S-CaseInsensitive |
Búsqueda sin distinción mayúsc/minúscula |
B1S-ReplaceCollectionsOnPatch |
Reemplazar colecciones completas en PATCH |
Manejo de Errores
| Situación | Comportamiento |
|---|---|
| SAP devuelve 2xx | Retorna Response al router (200 body, 201 body, 204 vacío) |
| SAP devuelve 401 | Re-autentica automáticamente y reintenta 1 vez |
| SAP devuelve 4xx/5xx | Extrae error.message.value y lanza HTTPException(status_code, detail) |
| Timeout de conexión | Lanza ConnectionError |
| Error de red | Lanza ConnectionError |
| Reintento también falla | Lanza HTTPException con el mensaje de error SAP |
| SAP no disponible | HTTPException(500) |
Otros proxies (fuera de Service Layer)
| Proxy | Módulo | Sesión | Consumido por |
|---|---|---|---|
API Gateway (Crystal Reports, rs/v1/*) |
core/api_gateway.py (forward_to_api_gateway) |
requests.Session con cookies, global, se re-loguea en 401 igual que el Service Layer |
routers/apigateway.py |
| HANA (SQL directo) | core/hana.py (hana_connection) |
Sin sesión persistente: abre y cierra una conexión hdbcli por request |
routers/reports.py |
core/hana.py no pasa por forward_to_sapb1 ni maneja OData: se usa para
reportes que necesitan una consulta SQL puntual contra HANA (por ejemplo,
armar el detalle de una guía de remisión para imprimir stickers) en vez de
paginar un recurso del Service Layer.