Files
mini-erp/mini-backend/main.py
T
GeorgeandClaude Sonnet 5 7e0a646853 Agregar flujo de inmovilización/liberación de lotes (Inmovilizar, Liberar y Confirmar Liberación Lote)
- Inmovilizar Lotes: selección múltiple de almacén origen con cantidad editable, genera Solicitud de Transferencia hacia un almacén de inmovilizados.
- Liberar Lotes: inversa de la anterior, con almacenes/campos de usuario propios (U_SIS_MOVINV, U_EXX_TIPOOPER, U_SIS_TIPDESP, U_SIS_MOTALM).
- Confirmar Liberación Lote: lista las Solicitudes abiertas pendientes (U_SIS_MOTALM=108) y, por cada lote, resuelve contra el historial de transferencias en HANA a qué almacén(es) debe volver, repartiendo la cantidad entre orígenes; permite editar cantidad, almacén destino, y agregar/quitar líneas antes de actualizar la Solicitud en SAP.
- Backend: nuevo endpoint /api/batch/lot-source-warehouse (HANA) para resolver el origen histórico de un lote; endpoint genérico /api/Catalogs/UdfValidValues (Service Layer) para valores válidos de un UDF; PATCH genérico ahora reenvía cualquier header B1S-* que mande el frontend en vez de decidirlos en el backend.
- Registro de rutas, menú y permisos para las tres pantallas nuevas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 13:08:58 -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)