"""Project service: CRUD + lifecycle management.""" from sqlalchemy.orm import Session from typing import List, Optional from app.models.project import Project from app.models.task import Task from app.models.cost import CostLog from app.models.artifact import Artifact from app.models.review import Review from app.schemas.project import ProjectCreate, ProjectUpdate, ProjectStats class ProjectService: @staticmethod def list_projects( db: Session, tenant_id: int, status: Optional[str] = None, skip: int = 0, limit: int = 100, ) -> List[Project]: q = db.query(Project).filter(Project.tenant_id == tenant_id) if status: q = q.filter(Project.status == status) return q.order_by(Project.created_at.desc()).offset(skip).limit(limit).all() @staticmethod def get_project(db: Session, tenant_id: int, project_id: int) -> Optional[Project]: return ( db.query(Project) .filter(Project.id == project_id, Project.tenant_id == tenant_id) .first() ) @staticmethod def create_project( db: Session, tenant_id: int, owner_id: int, req: ProjectCreate ) -> Project: project = Project( tenant_id=tenant_id, owner_id=owner_id, name=req.name, description=req.description, start_date=req.start_date, end_date=req.end_date, budget_limit_cents=req.budget_limit_cents, acceptance_criteria=req.acceptance_criteria, tags=req.tags, status="draft", ) db.add(project) db.commit() db.refresh(project) return project @staticmethod def update_project( db: Session, tenant_id: int, project_id: int, req: ProjectUpdate ) -> Project: project = ProjectService.get_project(db, tenant_id, project_id) if not project: raise ValueError(f"Project #{project_id} not found") for field, value in req.model_dump(exclude_unset=True).items(): setattr(project, field, value) db.commit() db.refresh(project) return project @staticmethod def delete_project(db: Session, tenant_id: int, project_id: int) -> bool: project = ProjectService.get_project(db, tenant_id, project_id) if not project: return False db.delete(project) db.commit() return True @staticmethod def get_stats(db: Session, tenant_id: int, project_id: int) -> ProjectStats: tasks = ( db.query(Task) .filter(Task.project_id == project_id, Task.tenant_id == tenant_id) .all() ) cost_logs = ( db.query(CostLog) .filter(CostLog.project_id == project_id, CostLog.tenant_id == tenant_id) .all() ) artifacts = ( db.query(Artifact) .filter(Artifact.project_id == project_id, Artifact.tenant_id == tenant_id) .count() ) pending_reviews = ( db.query(Review) .filter( Review.project_id == project_id, Review.tenant_id == tenant_id, Review.status == "pending", ) .count() ) total_cost = sum(c.cost_cents for c in cost_logs) return ProjectStats( total_tasks=len(tasks), pending_tasks=sum(1 for t in tasks if t.status == "pending"), in_progress_tasks=sum(1 for t in tasks if t.status == "in_progress"), review_tasks=sum(1 for t in tasks if t.status == "review"), done_tasks=sum(1 for t in tasks if t.status == "done"), total_cost_cents=total_cost, total_artifacts=artifacts, pending_reviews=pending_reviews, )