from typing import Annotated, Optional from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.exc import IntegrityError from sqlmodel import Session, select from app.db import SessionDep from app.dependencies import ( CurrentUser, get_round_in_organization, get_route_in_organization, oauth2_scheme, require_elevated_superuser, ) from app.models.purchase_round import ( PurchaseRound, PurchaseRoundCreate, PurchaseRoundPublic, PurchaseRoundStatus, PurchaseRoundUpdate, ) from app.models.route import Route from app.models.wine_offering import WineOffering router = APIRouter(prefix="/purchase-rounds", tags=["purchase-rounds"]) def _commit_or_conflict(session: Session) -> None: try: session.commit() except IntegrityError as exc: session.rollback() if "ck_purchase_round_dates_required_unless_draft" in str(getattr(exc, "orig", exc)): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="opens_at, order_deadline_at and pickup_at are required unless status is draft", ) from exc raise @router.post("", response_model=PurchaseRoundPublic, status_code=status.HTTP_201_CREATED) def create_purchase_round( payload: PurchaseRoundCreate, session: SessionDep, current_user: CurrentUser ) -> PurchaseRound: get_route_in_organization(session, payload.route_id, current_user.organization_id) purchase_round = PurchaseRound.model_validate(payload) purchase_round.status = PurchaseRoundStatus.DRAFT # nye runder starter altid som kladde session.add(purchase_round) _commit_or_conflict(session) session.refresh(purchase_round) return purchase_round @router.get("", response_model=list[PurchaseRoundPublic]) def list_purchase_rounds( session: SessionDep, current_user: CurrentUser, route_id: Optional[int] = None, status_: Optional[PurchaseRoundStatus] = Query(default=None, alias="status"), limit: int = Query(default=100, le=500, gt=0), offset: int = Query(default=0, ge=0), ) -> list[PurchaseRound]: statement = select(PurchaseRound).join(Route).where(Route.organization_id == current_user.organization_id) if route_id is not None: statement = statement.where(PurchaseRound.route_id == route_id) if status_ is not None: statement = statement.where(PurchaseRound.status == status_) statement = statement.order_by(PurchaseRound.id.desc()).offset(offset).limit(limit) return list(session.exec(statement).all()) @router.get("/{round_id}", response_model=PurchaseRoundPublic) def get_purchase_round(round_id: int, session: SessionDep, current_user: CurrentUser) -> PurchaseRound: return get_round_in_organization(session, round_id, current_user.organization_id) @router.patch("/{round_id}", response_model=PurchaseRoundPublic) def update_purchase_round( round_id: int, payload: PurchaseRoundUpdate, session: SessionDep, current_user: CurrentUser ) -> PurchaseRound: purchase_round = get_round_in_organization(session, round_id, current_user.organization_id) for field, value in payload.model_dump(exclude_unset=True).items(): setattr(purchase_round, field, value) session.add(purchase_round) _commit_or_conflict(session) session.refresh(purchase_round) return purchase_round @router.delete("/{round_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_purchase_round( round_id: int, session: SessionDep, current_user: CurrentUser, token: Annotated[str, Depends(oauth2_scheme)], ) -> None: purchase_round = get_round_in_organization(session, round_id, current_user.organization_id) if purchase_round.status == PurchaseRoundStatus.OPEN: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="An open purchase round cannot be deleted") if purchase_round.status == PurchaseRoundStatus.CLOSED: require_elevated_superuser(current_user, token) session.delete(purchase_round) session.commit() @router.post("/{source_round_id}/copy", response_model=PurchaseRoundPublic, status_code=status.HTTP_201_CREATED) def copy_purchase_round(source_round_id: int, session: SessionDep, current_user: CurrentUser) -> PurchaseRound: source = get_round_in_organization(session, source_round_id, current_user.organization_id) new_round = PurchaseRound( name=source.name, status=PurchaseRoundStatus.DRAFT, opens_at=source.opens_at, order_deadline_at=source.order_deadline_at, pickup_at=source.pickup_at, eur_dkk_rate=source.eur_dkk_rate, intro_text=source.intro_text, pickup_info_text=source.pickup_info_text, route_id=source.route_id, ) session.add(new_round) session.flush() # for at få new_round.id til vinudbuddene nedenfor for offering in source.wine_offerings: session.add( WineOffering( name=offering.name, price=offering.price, is_organic=offering.is_organic, category_id=offering.category_id, purchase_round_id=new_round.id, ) ) _commit_or_conflict(session) session.refresh(new_round) return new_round