131 lines
5.9 KiB
Markdown
131 lines
5.9 KiB
Markdown
# 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](architecture.md)). 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".
|
|
|
|
```mermaid
|
|
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`:
|
|
|
|
```mermaid
|
|
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:
|
|
|
|
```mermaid
|
|
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.
|