feat: AI Worker 平台 MVP v1.0.0 - 多租户/项目管理/AI Worker/任务编排/HITL审核/成本治理
This commit is contained in:
@@ -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,
|
||||
)
|
||||
Reference in New Issue
Block a user