primer commit
This commit is contained in:
1 parent
ab3c613165
commit
b2fb4e2d0a
18 files changed
+1291
-277
No files matched your search
@@ -12,24 +12,32 @@ graph TB
|
||||
subgraph "Core"
|
||||
SAP[core/sap.py\nSAP Session & Proxy]
|
||||
SES[core/session.py\nEmployee Sessions]
|
||||
HANA[core/hana.py\nConexión directa HANA]
|
||||
AG[core/api_gateway.py\nSesión y Proxy APIGateway]
|
||||
CRUD[core/crud_router.py\nFactory de routers CRUD OData]
|
||||
end
|
||||
|
||||
subgraph "Routers"
|
||||
A[auth.py\nLogin / Logout]
|
||||
BP[business_partners.py\nSocios de Negocio]
|
||||
IT[items.py\nArtículos]
|
||||
SL[sales.py\nFacturas / Pedidos]
|
||||
PR[purchases.py\nÓrdenes / Facturas Compra]
|
||||
SL[sales.py\nFacturas / Pedidos / Guías]
|
||||
PR[purchases.py\nÓrdenes / Facturas / Guías Compra]
|
||||
CT[catalogs.py\nCatálogos]
|
||||
BT[batch.py\nLotes]
|
||||
PM[permissions.py\nPermisos de Menú]
|
||||
APG[apigateway.py\nCrystal Reports]
|
||||
RP[reports.py\nReportes Jinja/HTML → PDF]
|
||||
end
|
||||
|
||||
MP[menu_permissions.json]
|
||||
TPL[templates/reports/*.html]
|
||||
end
|
||||
|
||||
subgraph "External"
|
||||
SAP_SL[SAP B1 Service Layer]
|
||||
SAP_AG[SAP B1 API Gateway\nrs/v1/*]
|
||||
SAP_HANA[(SAP HANA)]
|
||||
end
|
||||
|
||||
M --> C
|
||||
@@ -44,21 +52,42 @@ graph TB
|
||||
M --> CT
|
||||
M --> BT
|
||||
M --> PM
|
||||
M --> APG
|
||||
M --> RP
|
||||
|
||||
A --> SAP
|
||||
A --> SES
|
||||
BP --> SAP
|
||||
IT --> SAP
|
||||
SL --> SAP
|
||||
PR --> SAP
|
||||
CT --> SAP
|
||||
BT --> SAP
|
||||
BP --> CRUD
|
||||
IT --> CRUD
|
||||
SL --> CRUD
|
||||
PR --> CRUD
|
||||
CT --> CRUD
|
||||
BT --> CRUD
|
||||
CRUD --> SAP
|
||||
PM --> MP
|
||||
APG --> AG
|
||||
RP --> HANA
|
||||
RP --> TPL
|
||||
|
||||
SAP --> SAP_SL
|
||||
SES --> SAP
|
||||
AG --> SAP_AG
|
||||
HANA --> SAP_HANA
|
||||
```
|
||||
|
||||
`business_partners.py`, `items.py`, `sales.py`, `purchases.py`, `batch.py` y
|
||||
la mayor parte de `catalogs.py` ya no implementan cada endpoint a mano: usan
|
||||
`core/crud_router.py`, una factory (`make_crud_router`) que genera el
|
||||
`list/get/create/update` típico de un recurso OData (mismo bloque de
|
||||
`$skip`/`$top`/`$filter` y llamada a `forward_to_sapb1`) a partir de un
|
||||
nombre de recurso y unos flags (si soporta `$orderby`, si expone `POST`,
|
||||
headers extra para el `PATCH`, etc). Esto evita que un copy-paste entre
|
||||
recursos deje, por ejemplo, un `PATCH` apuntando al endpoint de otro
|
||||
recurso. Un router de documentos de mercadeo (ventas o compras) puede
|
||||
generar varios recursos a la vez iterando esa factory, por eso `sales.py` y
|
||||
`purchases.py` ya no son un único router sino un `APIRouter` que incluye uno
|
||||
por cada tipo de documento.
|
||||
|
||||
## Pipeline de Middleware
|
||||
|
||||
Las peticiones atraviesan los siguientes middleware en orden:
|
||||
@@ -70,7 +99,7 @@ flowchart LR
|
||||
CID --> SES_MW[session_middleware\nValida X-Session-Token]
|
||||
SES_MW --> ROUTER{Router}
|
||||
ROUTER -->|/api/Login| A[auth.py]
|
||||
ROUTER -->|Otros /api/*| SAP_CORE[core/sap.py]
|
||||
ROUTER -->|Otros /api/*| DEST[routers → core/sap.py, core/api_gateway.py o core/hana.py]
|
||||
```
|
||||
|
||||
### Detalle de Middleware
|
||||
@@ -79,71 +108,86 @@ flowchart LR
|
||||
| -------------------------- | ----------------------------------------------------------- |
|
||||
| `CORSMiddleware` | Permite peticiones desde cualquier origen |
|
||||
| `correlation_id_middleware` | Genera o extrae `X-Request-ID` para trazabilidad en logs |
|
||||
| `session_middleware` | Valida token de sesión, adjunta `emp_id` a `request.state` |
|
||||
| `session_middleware` | Valida token de sesión, adjunta el usuario (`webUser`) a `request.state` y al contexto de logging |
|
||||
|
||||
## Arquitectura de Sesiones
|
||||
|
||||
Existen **dos capas de sesión independientes**:
|
||||
Existen **tres capas de sesión/credenciales independientes**:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Employee["Employee Session"]
|
||||
T["Token aleatorio → emp_id"]
|
||||
T["Token aleatorio → webUser"]
|
||||
E["employee_sessions dict"]
|
||||
TK["active_tokens dict"]
|
||||
T --> TK
|
||||
TK --> E
|
||||
end
|
||||
|
||||
subgraph SAP["SAP B1 Session"]
|
||||
subgraph SAP["SAP B1 Service Layer Session"]
|
||||
S["sap_session_store"]
|
||||
M["Manager Credentials (.env)"]
|
||||
S -.->|Renueva en 401| M
|
||||
MC["Manager Credentials (.env: SAPB1_SL_*)"]
|
||||
S -.->|Renueva en 401| MC
|
||||
end
|
||||
|
||||
subgraph AG["SAP API Gateway Session"]
|
||||
AS["api_gateway_session (requests.Session)"]
|
||||
AC["Manager Credentials (.env: APIGATEWAY_*)"]
|
||||
AS -.->|Renueva en 401| AC
|
||||
end
|
||||
```
|
||||
|
||||
| Capa | Almacenamiento | Ámbito | Timeout | Renovación |
|
||||
| ------------- | ----------------- | -------- | ------------- | -------------------- |
|
||||
| SAP B1 | `sap_session_store` | Global | Gestionado SAP | Automática al recibir 401 |
|
||||
| Empleado | `active_tokens` | Por empleado | 30 min inactividad | Login manual |
|
||||
| Capa | Almacenamiento | Ámbito | Timeout | Renovación |
|
||||
| -------------- | -------------------- | ------------ | ------------------- | --------------------------- |
|
||||
| SAP Service Layer | `sap_session_store` (`core/sap.py`) | Global | Gestionado SAP | Automática al recibir 401 |
|
||||
| SAP API Gateway | `api_gateway_session` (`core/api_gateway.py`) | Global | Gestionado SAP | Automática al recibir 401 |
|
||||
| Empleado | `active_tokens` / `employee_sessions` (`core/session.py`) | Por empleado | 30 min inactividad | Login manual |
|
||||
|
||||
`core/hana.py` no mantiene sesión: abre una conexión nueva a HANA por
|
||||
petición (`hana_connection()`, context manager) y la cierra al terminar.
|
||||
|
||||
## Estructura de Rutas
|
||||
|
||||
| Método | Ruta | Router | Descripción |
|
||||
| -------- | --------------------------------- | -------------------- | ------------------------ |
|
||||
| `POST` | `/api/Login` | `auth.py` | Autenticar empleado |
|
||||
| `POST` | `/api/Logout` | `auth.py` | Cerrar sesión |
|
||||
| `GET` | `/api/BusinessPartners` | `business_partners` | Listar socios |
|
||||
| `GET` | `/api/BusinessPartners/{code}` | `business_partners` | Obtener socio |
|
||||
| `POST` | `/api/BusinessPartners` | `business_partners` | Crear socio |
|
||||
| `PATCH` | `/api/BusinessPartners/{code}` | `business_partners` | Actualizar socio |
|
||||
| `GET` | `/api/Items` | `items.py` | Listar artículos |
|
||||
| `GET` | `/api/Items/{code}` | `items.py` | Obtener artículo |
|
||||
| `POST` | `/api/Items` | `items.py` | Crear artículo |
|
||||
| `PATCH` | `/api/Items/{code}` | `items.py` | Actualizar artículo |
|
||||
| `GET` | `/api/Invoices` | `sales.py` | Listar facturas |
|
||||
| `GET` | `/api/Invoices/{key}` | `sales.py` | Obtener factura |
|
||||
| `POST` | `/api/Invoices` | `sales.py` | Crear factura |
|
||||
| `PATCH` | `/api/Invoices/{key}` | `sales.py` | Actualizar factura |
|
||||
| `GET` | `/api/Orders` | `sales.py` | Listar pedidos |
|
||||
| `GET` | `/api/Orders/{key}` | `sales.py` | Obtener pedido |
|
||||
| `POST` | `/api/Orders` | `sales.py` | Crear pedido |
|
||||
| `PATCH` | `/api/Orders/{key}` | `sales.py` | Actualizar pedido |
|
||||
| `GET` | `/api/PurchaseOrders` | `purchases.py` | Listar órdenes de compra |
|
||||
| `GET` | `/api/PurchaseOrders/{key}` | `purchases.py` | Obtener orden de compra |
|
||||
| `PATCH` | `/api/PurchaseOrders/{key}` | `purchases.py` | Actualizar orden compra |
|
||||
| `GET` | `/api/PurchaseInvoices` | `purchases.py` | Listar facturas compra |
|
||||
| `GET` | `/api/PurchaseInvoices/{key}` | `purchases.py` | Obtener factura compra |
|
||||
| `PATCH` | `/api/PurchaseInvoices/{key}` | `purchases.py` | Actualizar factura compra|
|
||||
| `GET` | `/api/Currencies` | `catalogs.py` | Listar monedas |
|
||||
| `GET` | `/api/BusinessPartnerGroups` | `catalogs.py` | Listar grupos de socios |
|
||||
| `GET` | `/api/Warehouses` | `catalogs.py` | Listar almacenes |
|
||||
| `GET` | `/api/ItemGroups` | `catalogs.py` | Listar grupos artículos |
|
||||
| `GET` | `/api/SalesTaxCodes` | `catalogs.py` | Listar códigos de IVA |
|
||||
| `GET` | `/api/BatchNumberDetails` | `batch.py` | Listar lotes |
|
||||
| `GET` | `/api/BatchNumberDetails/{id}` | `batch.py` | Obtener lote |
|
||||
| `PATCH` | `/api/BatchNumberDetails/{id}` | `batch.py` | Actualizar lote |
|
||||
| `GET` | `/api/user/menu` | `permissions.py` | Obtener menú permitido |
|
||||
| -------- | --------------------------------- | --------------------- | ------------------------ |
|
||||
| `POST` | `/api/Login` | `auth.py` | Autenticar empleado |
|
||||
| `POST` | `/api/Logout` | `auth.py` | Cerrar sesión |
|
||||
| `GET` | `/api/BusinessPartners` | `business_partners` | Listar socios |
|
||||
| `GET` | `/api/BusinessPartners/{code}` | `business_partners` | Obtener socio |
|
||||
| `POST` | `/api/BusinessPartners` | `business_partners` | Crear socio |
|
||||
| `PATCH` | `/api/BusinessPartners/{code}` | `business_partners` | Actualizar socio |
|
||||
| `GET` | `/api/Items` | `items.py` | Listar artículos |
|
||||
| `GET` | `/api/Items/{code}` | `items.py` | Obtener artículo |
|
||||
| `POST` | `/api/Items` | `items.py` | Crear artículo |
|
||||
| `PATCH` | `/api/Items/{code}` | `items.py` | Actualizar artículo |
|
||||
| `GET/POST/PATCH` | `/api/Invoices[/{key}]` | `sales.py` | Facturas de venta |
|
||||
| `GET/POST/PATCH` | `/api/Orders[/{key}]` | `sales.py` | Pedidos de venta |
|
||||
| `GET/POST/PATCH` | `/api/DeliveryNotes[/{key}]` | `sales.py` | Guías de remisión de venta |
|
||||
| `GET/PATCH` | `/api/PurchaseOrders[/{key}]` | `purchases.py` | Órdenes de compra |
|
||||
| `GET/PATCH` | `/api/PurchaseDeliveryNotes[/{key}]` | `purchases.py` | Guías de recepción de compra |
|
||||
| `GET/PATCH` | `/api/PurchaseInvoices[/{key}]` | `purchases.py` | Facturas de compra |
|
||||
| `GET` | `/api/Currencies` | `catalogs.py` | Listar monedas |
|
||||
| `GET` | `/api/BusinessPartnerGroups` | `catalogs.py` | Listar grupos de socios |
|
||||
| `GET` | `/api/ItemGroups` | `catalogs.py` | Listar grupos artículos |
|
||||
| `GET` | `/api/Warehouses` | `catalogs.py` | Listar almacenes |
|
||||
| `GET` | `/api/SalesTaxCodes` | `catalogs.py` | Listar códigos de IVA |
|
||||
| `GET` | `/api/FactoringIndicators` | `catalogs.py` | Listar indicadores de factoring |
|
||||
| `GET` | `/api/Sunat/BusinessPartners/DocumentType` | `catalogs.py` | Tipos de documento SUNAT (campo de usuario `EXX_TIPODOCU`) |
|
||||
| `GET` | `/api/BatchNumberDetails` | `batch.py` | Listar lotes |
|
||||
| `GET` | `/api/BatchNumberDetails/{id}` | `batch.py` | Obtener lote |
|
||||
| `PATCH` | `/api/BatchNumberDetails/{id}` | `batch.py` | Actualizar lote |
|
||||
| `GET` | `/api/user/menu` | `permissions.py` | Obtener menú permitido |
|
||||
| `GET` | `/api/apigateway/reports` | `apigateway.py` | Listar reportes Crystal Reports autorizados |
|
||||
| `GET` | `/api/apigateway/reports/{docCode}/parameters` | `apigateway.py` | Parámetros de un reporte Crystal Reports |
|
||||
| `POST` | `/api/apigateway/reports/{docCode}/export` | `apigateway.py` | Exportar reporte Crystal Reports a PDF |
|
||||
| `POST` | `/api/reports/stickers` | `reports.py` | Generar PDF/HTML de stickers de guía de remisión (consulta directa a HANA + plantilla Jinja + WeasyPrint) |
|
||||
|
||||
Los recursos servidos por `core/crud_router.py` (todos los de la tabla salvo
|
||||
`auth`, `permissions`, `apigateway` y `reports`) soportan además `skip`,
|
||||
`top`, `fields` y `filter` como query params, y algunos (`Invoices`,
|
||||
`Orders`, `DeliveryNotes`, `PurchaseOrders`, `PurchaseDeliveryNotes`,
|
||||
`PurchaseInvoices`, `BatchNumberDetails`) también `orderby`. Ver
|
||||
[API Reference](api-reference.md) para el detalle completo por endpoint.
|
||||
|
||||
## Dependencias del Proyecto
|
||||
|
||||
@@ -160,24 +204,44 @@ graph TD
|
||||
main --> catalogs
|
||||
main --> batch
|
||||
main --> permissions
|
||||
main --> apigateway_router[apigateway]
|
||||
main --> reports_router[reports]
|
||||
|
||||
auth --> sap_core
|
||||
auth --> session_core
|
||||
business_partners --> sap_core
|
||||
items --> sap_core
|
||||
sales --> sap_core
|
||||
purchases --> sap_core
|
||||
business_partners --> crud_router
|
||||
items --> crud_router
|
||||
sales --> crud_router
|
||||
purchases --> crud_router
|
||||
catalogs --> crud_router
|
||||
catalogs --> sap_core
|
||||
batch --> sap_core
|
||||
batch --> crud_router
|
||||
crud_router --> sap_core
|
||||
permissions --> menu_json[menu_permissions.json]
|
||||
apigateway_router --> api_gateway_core[api_gateway]
|
||||
reports_router --> hana_core[hana]
|
||||
reports_router --> templates_reports[templates/reports/*.html]
|
||||
```
|
||||
|
||||
## Configuración por Entorno
|
||||
|
||||
La aplicación carga siempre `.env` (un solo archivo, sin sufijos de entorno).
|
||||
`.env.aberio` se conserva como plantilla de referencia; para desplegar contra
|
||||
ese entorno, copiar su contenido dentro de `.env`.
|
||||
La aplicación carga siempre `mini-backend/.env` (un solo archivo por
|
||||
despliegue, sin sufijos de entorno) mediante `load_dotenv`. Ese archivo
|
||||
**no se versiona** (está en `.gitignore`); la plantilla de referencia con
|
||||
todas las variables necesarias y valores de ejemplo es
|
||||
[`mini-backend/.env.example`](../.env.example).
|
||||
|
||||
| Archivo | URL SAP | Company DB |
|
||||
| ------------------ | ------------------------------- | ----------------------- |
|
||||
| `.env` | `192.168.1.175:50000` | `SBO_PRUEBA_TAGUM_22` |
|
||||
| `.env.aberio` | `192.168.9.3:50000` | `PRUEBA_20260609` |
|
||||
Grupos de variables:
|
||||
|
||||
| Grupo | Variables | Usado por |
|
||||
| --------------------- | -------------------------------------------------------------------------- | --------------------- |
|
||||
| SAP Service Layer | `SAPB1_SL_URL`, `SAPB1_SL_USER`, `SAPB1_SL_PASSWORD`, `SAPB1_SL_COMPANYDB`, `SAPB1_SL_TIMEOUT`, `SAPB1_SL_VERIFY_SSL` | `core/sap.py`, `routers/auth.py` |
|
||||
| SAP API Gateway | `APIGATEWAY_URL`, `APIGATEWAY_USER`, `APIGATEWAY_PASSWORD`, `APIGATEWAY_COMPANYDB`, `APIGATEWAY_DBINSTANCE`, `APIGATEWAY_TIMEOUT` | `core/api_gateway.py` |
|
||||
| HANA (acceso directo) | `HANA_HOST`, `HANA_PORT`, `HANA_USER`, `HANA_PASSWORD`, `HANA_SCHEMA` | `core/hana.py`, `routers/reports.py` |
|
||||
| Logging | `LOG_ENVIRONMENT` (`production` = JSON estructurado, cualquier otro valor = texto legible en consola) | `main.py`, `logger_config.py` |
|
||||
|
||||
Al desplegar para un cliente nuevo (ver flujo de branches por cliente en
|
||||
`CLAUDE.md` de la raíz), se copia `.env.example` a `.env` dentro de
|
||||
`mini-backend/` y se completa con las credenciales reales de SAP Business
|
||||
One / HANA de ese cliente. Las credenciales reales nunca deben
|
||||
documentarse ni commitearse.
|
||||
Reference in new issue
Block a user