Files
mini-erp/mini-backend/main.py
T
GeorgeandClaude Sonnet 5 fef21898f9 Backport de mejoras genéricas de backend (desde cliente/tagumedica) + limpieza de datos de cliente en main
Mejoras genéricas traídas:
- core/crud_router.py: el PATCH genérico reenvía cualquier header B1S-* recibido del caller en vez de decidirlo el backend.
- routers/catalogs.py: nuevo endpoint /api/Catalogs/UdfValidValues (valores válidos de cualquier UDF vía Service Layer).
- routers/batch.py: /api/batch/stock (stock de un lote por almacén) y /api/batch/lot-source-warehouse (resuelve contra HANA de qué almacén salió un lote, repartiendo por origen).
- routers/inventory_transfer_requests.py + core/inventory_transfer_requests.py: CRUD genérico de Solicitudes de Transferencia de Inventario.
- routers/series.py + core/series.py: series activas de un tipo de documento.
- routers/user_tables.py: catálogo Code/Name genérico de tablas de usuario (mapeo USER_TABLES vacío, cada cliente completa el suyo).
- routers/reports.py / templates/reports/stickers.html: fix de márgenes/alto de página del reporte de etiquetas.
- mini-backend/test/ -> mini-backend/tools/ (rename organizativo).

Limpieza de datos de Tagumedica que ya estaban en el commit base de main (anteriores a la branch cliente/tagumedica, no relacionados con el backport):
- tools/sap-tagumedica/sl.http: eliminado (tenía CompanyDB y password reales).
- tools/ApiGateway/Login.yml: CompanyDB/host/password reemplazados por placeholders.
- templates/reports/stickers.html: título hardcodeado "TAGUMEDICA" reemplazado por "EMPRESA".
- menu_permissions.json: reemplazado por un ejemplo demo (usuarios/módulos ficticios) en vez de emails reales.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 14:18:42 -05:00

115 lines
4.1 KiB
Python

import os
import uuid
import uvicorn
import urllib3
import logging
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
from dotenv import load_dotenv
from logger_config import setup_logging
from context import set_request_id, get_request_id, set_current_user
from core.session import validate_and_refresh_session, get_employee_name, get_emp_id
from routers.auth import router as auth_router
from routers.business_partners import router as bp_router
from routers.items import router as items_router
from routers.sales import router as sales_router
from routers.purchases import router as purchases_router
from routers.catalogs import router as catalogs_router
from routers.batch import router as batch_router
from routers.permissions import router as permissions_router
from routers.apigateway import router as apigateway_router
from routers.reports import router as reports_router
from routers.series import router as series_router
from routers.user_tables import router as user_tables_router
from routers.inventory_transfer_requests import router as inventory_transfer_requests_router
# ============================================================================
load_dotenv(dotenv_path=".env")
logger = logging.getLogger(__name__)
LOG_ENVIRONMENT = os.getenv("LOG_ENVIRONMENT", "production")
# ============================================================================
@asynccontextmanager
async def lifespan(app: FastAPI):
setup_logging(environment=LOG_ENVIRONMENT)
yield
app = FastAPI(
title="SAP Business One Integration API",
description="API para integrar Socios de negocios con SAP Business One Service Layer.",
version="1.1.0",
lifespan=lifespan
)
app.include_router(auth_router)
app.include_router(bp_router)
app.include_router(items_router)
app.include_router(sales_router)
app.include_router(purchases_router)
app.include_router(catalogs_router)
app.include_router(batch_router)
app.include_router(permissions_router)
app.include_router(apigateway_router)
app.include_router(reports_router)
app.include_router(series_router)
app.include_router(user_tables_router)
app.include_router(inventory_transfer_requests_router)
# ============================================================================
@app.middleware("http")
async def correlation_id_middleware(request: Request, call_next):
req_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
set_request_id(req_id)
response = await call_next(request)
response.headers["X-Request-ID"] = get_request_id()
return response
# ============================================================================
@app.middleware("http")
async def session_middleware(request: Request, call_next):
if request.url.path == "/api/Login":
return await call_next(request)
if request.url.path.startswith("/api/"):
token = request.headers.get("X-Session-Token")
if not token:
logger.warning(f"Request sin token: {request.method} {request.url.path}")
return JSONResponse(
status_code=401,
content={"detail": "Se requiere autenticación. Inicie sesión primero."}
)
webUser = validate_and_refresh_session(token)
if not webUser:
logger.warning(f"Token inválido o expirado en: {request.method} {request.url.path}")
return JSONResponse(
status_code=401,
content={"detail": "Sesión expirada. Debe iniciar sesión nuevamente."}
)
set_current_user(webUser, get_employee_name(webUser), get_emp_id(webUser))
request.state.webUser = webUser
return await call_next(request)
# ============================================================================
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
urllib3.disable_warnings()
# ============================================================================
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="debug", access_log=True)