From 05a97f30be163ab118394ca8329e7384692e6cb0 Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Wed, 12 Aug 2026 13:31:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20Worker=20=E5=B9=B3=E5=8F=B0=20MVP?= =?UTF-8?q?=20v1.0.0=20-=20=E5=A4=9A=E7=A7=9F=E6=88=B7/=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86/AI=20Worker/=E4=BB=BB=E5=8A=A1=E7=BC=96?= =?UTF-8?q?=E6=8E=92/HITL=E5=AE=A1=E6=A0=B8/=E6=88=90=E6=9C=AC=E6=B2=BB?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/project_service.py | 114 ++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 backend/app/services/project_service.py diff --git a/backend/app/services/project_service.py b/backend/app/services/project_service.py new file mode 100644 index 0000000..b1fc9e3 --- /dev/null +++ b/backend/app/services/project_service.py @@ -0,0 +1,114 @@ +"""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, + )