from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, delete from datetime import datetime from app.database import get_db from app.models import Commune, TariffParams, Household, Vote, CommuneContent, AdminUser, admin_commune_table from app.schemas import ( CommuneCreate, CommuneUpdate, CommuneOut, TariffParamsUpdate, TariffParamsOut, PublishCurveRequest, ) from app.services.auth_service import get_current_admin, require_super_admin from app.engine.pricing import HouseholdData, compute_p0 router = APIRouter() @router.get("/", response_model=list[CommuneOut]) async def list_communes(db: AsyncSession = Depends(get_db)): result = await db.execute(select(Commune).where(Commune.is_active == True)) return result.scalars().all() @router.post("/", response_model=CommuneOut) async def create_commune( data: CommuneCreate, db: AsyncSession = Depends(get_db), admin: AdminUser = Depends(require_super_admin), ): existing = await db.execute(select(Commune).where(Commune.slug == data.slug)) if existing.scalar_one_or_none(): raise HTTPException(status_code=400, detail="Slug déjà utilisé") commune = Commune(name=data.name, slug=data.slug, description=data.description) db.add(commune) await db.flush() params = TariffParams(commune_id=commune.id) db.add(params) await db.commit() await db.refresh(commune) return commune @router.get("/{slug}", response_model=CommuneOut) async def get_commune(slug: str, db: AsyncSession = Depends(get_db)): result = await db.execute(select(Commune).where(Commune.slug == slug)) commune = result.scalar_one_or_none() if not commune: raise HTTPException(status_code=404, detail="Commune introuvable") return commune @router.put("/{slug}", response_model=CommuneOut) async def update_commune( slug: str, data: CommuneUpdate, db: AsyncSession = Depends(get_db), admin: AdminUser = Depends(get_current_admin), ): result = await db.execute(select(Commune).where(Commune.slug == slug)) commune = result.scalar_one_or_none() if not commune: raise HTTPException(status_code=404, detail="Commune introuvable") if data.name is not None: commune.name = data.name if data.description is not None: commune.description = data.description if data.is_active is not None: commune.is_active = data.is_active if data.vote_deadline is not None: commune.vote_deadline = data.vote_deadline await db.commit() await db.refresh(commune) return commune @router.delete("/{slug}") async def delete_commune( slug: str, db: AsyncSession = Depends(get_db), admin: AdminUser = Depends(require_super_admin), ): result = await db.execute(select(Commune).where(Commune.slug == slug)) commune = result.scalar_one_or_none() if not commune: raise HTTPException(status_code=404, detail="Commune introuvable") # Delete related data in order await db.execute(delete(Vote).where(Vote.commune_id == commune.id)) await db.execute(delete(Household).where(Household.commune_id == commune.id)) await db.execute(delete(TariffParams).where(TariffParams.commune_id == commune.id)) await db.execute(delete(CommuneContent).where(CommuneContent.commune_id == commune.id)) await db.execute(delete(admin_commune_table).where(admin_commune_table.c.commune_id == commune.id)) await db.delete(commune) await db.commit() return {"detail": f"Commune '{slug}' supprimée"} @router.get("/{slug}/params", response_model=TariffParamsOut) async def get_params(slug: str, db: AsyncSession = Depends(get_db)): result = await db.execute( select(TariffParams).join(Commune).where(Commune.slug == slug) ) params = result.scalar_one_or_none() if not params: raise HTTPException(status_code=404, detail="Paramètres introuvables") return params @router.put("/{slug}/params", response_model=TariffParamsOut) async def update_params( slug: str, data: TariffParamsUpdate, db: AsyncSession = Depends(get_db), admin: AdminUser = Depends(get_current_admin), ): result = await db.execute( select(TariffParams).join(Commune).where(Commune.slug == slug) ) params = result.scalar_one_or_none() if not params: raise HTTPException(status_code=404, detail="Paramètres introuvables") for field, value in data.model_dump(exclude_unset=True).items(): setattr(params, field, value) await db.commit() await db.refresh(params) return params @router.post("/{slug}/params/publish", response_model=TariffParamsOut) async def publish_curve( slug: str, data: PublishCurveRequest, db: AsyncSession = Depends(get_db), admin: AdminUser = Depends(get_current_admin), ): """Admin publishes a Bézier curve as the commune's reference.""" result = await db.execute( select(TariffParams).join(Commune).where(Commune.slug == slug) ) params = result.scalar_one_or_none() if not params: raise HTTPException(status_code=404, detail="Paramètres introuvables") # Compute p0 for this curve hh_result = await db.execute( select(Household).join(Commune).where(Commune.slug == slug) ) households_db = hh_result.scalars().all() households = [ HouseholdData(volume_m3=h.volume_m3, status=h.status, price_paid_eur=h.price_paid_eur) for h in households_db ] p0 = compute_p0( households, recettes=params.recettes, abop=params.abop, abos=params.abos, vinf=data.vinf, vmax=params.vmax, pmax=params.pmax, a=data.a, b=data.b, c=data.c, d=data.d, e=data.e, ) params.published_vinf = data.vinf params.published_a = data.a params.published_b = data.b params.published_c = data.c params.published_d = data.d params.published_e = data.e params.published_p0 = p0 params.published_at = datetime.utcnow() await db.commit() await db.refresh(params) return params