# ================================================================ # services/procurement/app/main.py # ================================================================ """ Procurement Service - FastAPI Application Procurement planning, purchase order management, and supplier integration """ from fastapi import FastAPI, Request from sqlalchemy import text from app.core.config import settings from app.core.database import database_manager from shared.service_base import StandardFastAPIService class ProcurementService(StandardFastAPIService): """Procurement Service with standardized setup""" expected_migration_version = "00001" async def verify_migrations(self): """Verify database schema matches the latest migrations""" try: async with self.database_manager.get_session() as session: result = await session.execute(text("SELECT version_num FROM alembic_version")) version = result.scalar() if version != self.expected_migration_version: self.logger.error(f"Migration version mismatch: expected {self.expected_migration_version}, got {version}") raise RuntimeError(f"Migration version mismatch: expected {self.expected_migration_version}, got {version}") self.logger.info(f"Migration verification successful: {version}") except Exception as e: self.logger.error(f"Migration verification failed: {e}") raise def __init__(self): # Define expected database tables for health checks procurement_expected_tables = [ 'procurement_plans', 'procurement_requirements', 'purchase_orders', 'purchase_order_items', 'deliveries', 'delivery_items', 'supplier_invoices', 'replenishment_plans', 'replenishment_plan_items', 'inventory_projections', 'supplier_allocations', 'supplier_selection_history' ] super().__init__( service_name="procurement-service", app_name=settings.APP_NAME, description=settings.DESCRIPTION, version=settings.VERSION, api_prefix="", # Empty because RouteBuilder already includes /api/v1 database_manager=database_manager, expected_tables=procurement_expected_tables ) async def on_startup(self, app: FastAPI): """Custom startup logic for procurement service""" self.logger.info("Procurement Service starting up...") # Future: Initialize any background services if needed async def on_shutdown(self, app: FastAPI): """Custom shutdown logic for procurement service""" self.logger.info("Procurement Service shutting down...") def get_service_features(self): """Return procurement-specific features""" return [ "procurement_planning", "purchase_order_management", "delivery_tracking", "invoice_management", "supplier_integration", "local_production_support", "recipe_explosion" ] # Create service instance service = ProcurementService() # Create FastAPI app with standardized setup app = service.create_app() # Setup standard endpoints (health, readiness, metrics) service.setup_standard_endpoints() # Include routers from app.api.procurement_plans import router as procurement_plans_router from app.api.purchase_orders import router as purchase_orders_router from app.api import replenishment # Enhanced Replenishment Planning Routes from app.api import analytics # Procurement Analytics Routes from app.api import internal_demo from app.api import ml_insights # ML insights endpoint service.add_router(procurement_plans_router) service.add_router(purchase_orders_router) service.add_router(replenishment.router, tags=["replenishment"]) # RouteBuilder already includes full path service.add_router(analytics.router, tags=["analytics"]) # RouteBuilder already includes full path service.add_router(internal_demo.router) service.add_router(ml_insights.router) # ML insights endpoint @app.middleware("http") async def logging_middleware(request: Request, call_next): """Add request logging middleware""" import time start_time = time.time() response = await call_next(request) process_time = time.time() - start_time service.logger.info("HTTP request processed", method=request.method, url=str(request.url), status_code=response.status_code, process_time=round(process_time, 4)) return response if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=settings.DEBUG )