diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py index 39c8e11b..8195019c 100644 --- a/backend/app/api/v1/__init__.py +++ b/backend/app/api/v1/__init__.py @@ -12,6 +12,8 @@ from .module_system.position.controller import PositionRouter from .module_system.notice.controller import NoticeRouter from .module_system.params.controller import ParamsRouter from .module_system.dict.controller import DictRouter +from .module_system.ticket.controller import router as TicketRouter +from .module_system.version.controller import router as VersionRouter from .module_monitor.cache.controller import CacheRouter from .module_monitor.job.controller import JobRouter @@ -28,8 +30,6 @@ from .module_application.myapp.controller import MyAppRouter from .module_ai.mcp.controller import MCPRouter -from .module_resource.resource.controller import ResourceRouter - # 定义路由模块映射,按模块分组 SYSTEM_MODULES = [ @@ -43,6 +43,8 @@ SYSTEM_MODULES = [ {"router": NoticeRouter}, {"router": ParamsRouter}, {"router": DictRouter}, + {"router": TicketRouter}, + {"router": VersionRouter}, ] MONITOR_MODULES = [ diff --git a/backend/app/api/v1/module_system/ticket/__init__.py b/backend/app/api/v1/module_system/ticket/__init__.py new file mode 100644 index 00000000..19efa361 --- /dev/null +++ b/backend/app/api/v1/module_system/ticket/__init__.py @@ -0,0 +1 @@ +# 工单管理模块 \ No newline at end of file diff --git a/backend/app/api/v1/module_system/ticket/controller.py b/backend/app/api/v1/module_system/ticket/controller.py new file mode 100644 index 00000000..013c99ff --- /dev/null +++ b/backend/app/api/v1/module_system/ticket/controller.py @@ -0,0 +1,58 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from app.api.v1.module_system.ticket.schema import TicketCreate, TicketUpdate, TicketOut +from app.api.v1.module_system.ticket.service import ticket_service +from app.core.database import get_db + +router = APIRouter(prefix="/tickets", tags=["工单管理"]) + + +@router.post("/", response_model=TicketOut) +def create_ticket(ticket: TicketCreate, db: Session = Depends(get_db)): + """创建工单""" + return ticket_service.create_ticket(db, ticket) + + +@router.get("/{ticket_id}", response_model=TicketOut) +def get_ticket(ticket_id: int, db: Session = Depends(get_db)): + """获取工单详情""" + db_ticket = ticket_service.get_ticket(db, ticket_id) + if db_ticket is None: + raise HTTPException(status_code=404, detail="工单不存在") + return db_ticket + + +@router.get("/", response_model=list[TicketOut]) +def get_tickets(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + """获取工单列表""" + return ticket_service.get_tickets(db, skip=skip, limit=limit) + + +@router.put("/{ticket_id}", response_model=TicketOut) +def update_ticket(ticket_id: int, ticket: TicketUpdate, db: Session = Depends(get_db)): + """更新工单""" + db_ticket = ticket_service.update_ticket(db, ticket_id, ticket) + if db_ticket is None: + raise HTTPException(status_code=404, detail="工单不存在") + return db_ticket + + +@router.delete("/{ticket_id}", response_model=TicketOut) +def delete_ticket(ticket_id: int, db: Session = Depends(get_db)): + """删除工单""" + db_ticket = ticket_service.get_ticket(db, ticket_id) + if db_ticket is None: + raise HTTPException(status_code=404, detail="工单不存在") + return ticket_service.delete_ticket(db, ticket_id) + + +@router.get("/status/{status}", response_model=list[TicketOut]) +def get_tickets_by_status(status: str, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + """根据状态获取工单列表""" + return ticket_service.get_tickets_by_status(db, status=status, skip=skip, limit=limit) + + +@router.get("/assignee/{assignee_id}", response_model=list[TicketOut]) +def get_tickets_by_assignee(assignee_id: int, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + """根据指派用户获取工单列表""" + return ticket_service.get_tickets_by_assignee(db, assignee_id=assignee_id, skip=skip, limit=limit) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/ticket/crud.py b/backend/app/api/v1/module_system/ticket/crud.py new file mode 100644 index 00000000..7b56fabf --- /dev/null +++ b/backend/app/api/v1/module_system/ticket/crud.py @@ -0,0 +1,28 @@ +from sqlalchemy.orm import Session +from typing import List +from app.api.v1.module_system.ticket.model import Ticket +from app.api.v1.module_system.ticket.schema import TicketCreate, TicketUpdate +from app.core.base_crud import CRUDBase + + +class CRUDTicket(CRUDBase[Ticket, TicketCreate, TicketUpdate]): + """工单 CRUD 操作""" + + def get_ticket_by_id(self, db: Session, ticket_id: int) -> Ticket: + """根据ID获取工单""" + return db.query(self.model).filter(self.model.id == ticket_id).first() + + def get_tickets(self, db: Session, skip: int = 0, limit: int = 100) -> List[Ticket]: + """获取工单列表""" + return db.query(self.model).offset(skip).limit(limit).all() + + def get_tickets_by_status(self, db: Session, status: str, skip: int = 0, limit: int = 100) -> List[Ticket]: + """根据状态获取工单列表""" + return db.query(self.model).filter(self.model.status == status).offset(skip).limit(limit).all() + + def get_tickets_by_assignee(self, db: Session, assignee_id: int, skip: int = 0, limit: int = 100) -> List[Ticket]: + """根据指派用户获取工单列表""" + return db.query(self.model).filter(self.model.assignee_id == assignee_id).offset(skip).limit(limit).all() + + +ticket_crud = CRUDTicket(Ticket) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/ticket/model.py b/backend/app/api/v1/module_system/ticket/model.py new file mode 100644 index 00000000..12fdc033 --- /dev/null +++ b/backend/app/api/v1/module_system/ticket/model.py @@ -0,0 +1,30 @@ +from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey +from sqlalchemy.orm import relationship +from app.core.base_model import BaseModel +from app.api.v1.module_system.user.model import User +from datetime import datetime + + +class Ticket(BaseModel): + """工单模型""" + __tablename__ = "ticket" + + id = Column(Integer, primary_key=True, index=True, comment="工单ID") + + title = Column(String(255), nullable=False, comment="工单标题") + + status = Column(String(50), default="open", comment="工单状态(open, in_progress, resolved, closed)") + priority = Column(String(50), default="medium", comment="优先级(low, medium, high, urgent)") + type = Column(String(50), comment="工单类型(bug, feature, task)") + assignee_id = Column(Integer, ForeignKey("system_user.id"), comment="指派给用户ID") + reporter_id = Column(Integer, ForeignKey("system_user.id"), comment="报告人ID") + project = Column(String(100), comment="所属项目") + version = Column(String(50), comment="版本号") + + description = Column(Text, comment="工单描述") + created_at = Column(DateTime, default=datetime.now, comment="创建时间") + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间") + + # 关系 + assignee = relationship("User", foreign_keys=[assignee_id]) + reporter = relationship("User", foreign_keys=[reporter_id]) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/ticket/schema.py b/backend/app/api/v1/module_system/ticket/schema.py new file mode 100644 index 00000000..e3cc9739 --- /dev/null +++ b/backend/app/api/v1/module_system/ticket/schema.py @@ -0,0 +1,47 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import datetime + + +class TicketCreate(BaseModel): + """创建工单""" + title: str + description: Optional[str] = None + status: Optional[str] = "open" + priority: Optional[str] = "medium" + type: Optional[str] = "bug" + assignee_id: Optional[int] = None + reporter_id: int + project: Optional[str] = None + version: Optional[str] = None + + +class TicketUpdate(BaseModel): + """更新工单""" + title: Optional[str] = None + description: Optional[str] = None + status: Optional[str] = None + priority: Optional[str] = None + type: Optional[str] = None + assignee_id: Optional[int] = None + project: Optional[str] = None + version: Optional[str] = None + + +class TicketOut(BaseModel): + """工单输出""" + id: int + title: str + description: Optional[str] = None + status: str + priority: str + type: str + assignee_id: Optional[int] = None + reporter_id: int + project: Optional[str] = None + version: Optional[str] = None + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/app/api/v1/module_system/ticket/service.py b/backend/app/api/v1/module_system/ticket/service.py new file mode 100644 index 00000000..d7101d10 --- /dev/null +++ b/backend/app/api/v1/module_system/ticket/service.py @@ -0,0 +1,48 @@ +from sqlalchemy.orm import Session +from app.api.v1.module_system.ticket.crud import ticket_crud +from app.api.v1.module_system.ticket.schema import TicketCreate, TicketUpdate + + +class TicketService: + """工单服务类""" + + @staticmethod + def create_ticket(db: Session, ticket_create: TicketCreate): + """创建工单""" + return ticket_crud.create(db, obj_in=ticket_create) + + @staticmethod + def get_ticket(db: Session, ticket_id: int): + """获取工单""" + return ticket_crud.get(db, id=ticket_id) + + @staticmethod + def get_tickets(db: Session, skip: int = 0, limit: int = 100): + """获取工单列表""" + return ticket_crud.get_multi(db, skip=skip, limit=limit) + + @staticmethod + def update_ticket(db: Session, ticket_id: int, ticket_update: TicketUpdate): + """更新工单""" + db_ticket = ticket_crud.get(db, id=ticket_id) + if db_ticket: + return ticket_crud.update(db, db_obj=db_ticket, obj_in=ticket_update) + return None + + @staticmethod + def delete_ticket(db: Session, ticket_id: int): + """删除工单""" + return ticket_crud.remove(db, id=ticket_id) + + @staticmethod + def get_tickets_by_status(db: Session, status: str, skip: int = 0, limit: int = 100): + """根据状态获取工单列表""" + return ticket_crud.get_tickets_by_status(db, status=status, skip=skip, limit=limit) + + @staticmethod + def get_tickets_by_assignee(db: Session, assignee_id: int, skip: int = 0, limit: int = 100): + """根据指派用户获取工单列表""" + return ticket_crud.get_tickets_by_assignee(db, assignee_id=assignee_id, skip=skip, limit=limit) + + +ticket_service = TicketService() \ No newline at end of file diff --git a/backend/app/api/v1/module_system/version/__init__.py b/backend/app/api/v1/module_system/version/__init__.py new file mode 100644 index 00000000..f8a56ada --- /dev/null +++ b/backend/app/api/v1/module_system/version/__init__.py @@ -0,0 +1 @@ +# 版本管理模块 \ No newline at end of file diff --git a/backend/app/api/v1/module_system/version/controller.py b/backend/app/api/v1/module_system/version/controller.py new file mode 100644 index 00000000..27776340 --- /dev/null +++ b/backend/app/api/v1/module_system/version/controller.py @@ -0,0 +1,61 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from app.api.v1.module_system.version.schema import VersionCreate, VersionUpdate, VersionOut +from app.api.v1.module_system.version.service import version_service +from app.core.database import get_db + +router = APIRouter(prefix="/versions", tags=["版本管理"]) + + +@router.post("/", response_model=VersionOut) +def create_version(version: VersionCreate, db: Session = Depends(get_db)): + """创建版本""" + return version_service.create_version(db, version) + + +@router.get("/{version_id}", response_model=VersionOut) +def get_version(version_id: int, db: Session = Depends(get_db)): + """获取版本详情""" + db_version = version_service.get_version(db, version_id) + if db_version is None: + raise HTTPException(status_code=404, detail="版本不存在") + return db_version + + +@router.get("/", response_model=list[VersionOut]) +def get_versions(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + """获取版本列表""" + return version_service.get_versions(db, skip=skip, limit=limit) + + +@router.put("/{version_id}", response_model=VersionOut) +def update_version(version_id: int, version: VersionUpdate, db: Session = Depends(get_db)): + """更新版本""" + db_version = version_service.update_version(db, version_id, version) + if db_version is None: + raise HTTPException(status_code=404, detail="版本不存在") + return db_version + + +@router.delete("/{version_id}", response_model=VersionOut) +def delete_version(version_id: int, db: Session = Depends(get_db)): + """删除版本""" + db_version = version_service.get_version(db, version_id) + if db_version is None: + raise HTTPException(status_code=404, detail="版本不存在") + return version_service.delete_version(db, version_id) + + +@router.get("/number/{version_number}", response_model=VersionOut) +def get_version_by_number(version_number: str, db: Session = Depends(get_db)): + """根据版本号获取版本""" + db_version = version_service.get_version_by_number(db, version_number) + if db_version is None: + raise HTTPException(status_code=404, detail="版本不存在") + return db_version + + +@router.get("/status/{status}", response_model=list[VersionOut]) +def get_versions_by_status(status: str, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + """根据状态获取版本列表""" + return version_service.get_versions_by_status(db, status=status, skip=skip, limit=limit) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/version/crud.py b/backend/app/api/v1/module_system/version/crud.py new file mode 100644 index 00000000..278bebde --- /dev/null +++ b/backend/app/api/v1/module_system/version/crud.py @@ -0,0 +1,28 @@ +from sqlalchemy.orm import Session +from typing import List +from app.api.v1.module_system.version.model import Version +from app.api.v1.module_system.version.schema import VersionCreate, VersionUpdate +from app.core.base_crud import CRUDBase + + +class CRUDVersion(CRUDBase[Version, VersionCreate, VersionUpdate]): + """版本 CRUD 操作""" + + def get_version_by_id(self, db: Session, version_id: int) -> Version: + """根据ID获取版本""" + return db.query(self.model).filter(self.model.id == version_id).first() + + def get_versions(self, db: Session, skip: int = 0, limit: int = 100) -> List[Version]: + """获取版本列表""" + return db.query(self.model).offset(skip).limit(limit).all() + + def get_version_by_number(self, db: Session, version_number: str) -> Version: + """根据版本号获取版本""" + return db.query(self.model).filter(self.model.version_number == version_number).first() + + def get_versions_by_status(self, db: Session, status: str, skip: int = 0, limit: int = 100) -> List[Version]: + """根据状态获取版本列表""" + return db.query(self.model).filter(self.model.status == status).offset(skip).limit(limit).all() + + +version_crud = CRUDVersion(Version) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/version/model.py b/backend/app/api/v1/module_system/version/model.py new file mode 100644 index 00000000..b19203ff --- /dev/null +++ b/backend/app/api/v1/module_system/version/model.py @@ -0,0 +1,20 @@ +from sqlalchemy import Column, Integer, String, Text, DateTime +from app.core.base_model import BaseModel +from datetime import datetime + + +class Version(BaseModel): + """版本模型""" + __tablename__ = "version" + + id = Column(Integer, primary_key=True, index=True, comment="版本ID") + version_number = Column(String(50), nullable=False, comment="版本号") + title = Column(String(255), nullable=False, comment="版本标题") + release_notes = Column(Text, comment="发布说明") + status = Column(String(50), default="draft", comment="版本状态(draft, released, archived)") + project = Column(String(100), comment="所属项目") + released_at = Column(DateTime, comment="发布时间") + + description = Column(Text, comment="版本描述") + created_at = Column(DateTime, default=datetime.now, comment="创建时间") + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间") \ No newline at end of file diff --git a/backend/app/api/v1/module_system/version/schema.py b/backend/app/api/v1/module_system/version/schema.py new file mode 100644 index 00000000..0d0000dd --- /dev/null +++ b/backend/app/api/v1/module_system/version/schema.py @@ -0,0 +1,42 @@ +from pydantic import BaseModel +from typing import Optional +from datetime import datetime + + +class VersionCreate(BaseModel): + """创建版本""" + version_number: str + title: str + description: Optional[str] = None + release_notes: Optional[str] = None + status: Optional[str] = "draft" + project: Optional[str] = None + released_at: Optional[datetime] = None + + +class VersionUpdate(BaseModel): + """更新版本""" + version_number: Optional[str] = None + title: Optional[str] = None + description: Optional[str] = None + release_notes: Optional[str] = None + status: Optional[str] = None + project: Optional[str] = None + released_at: Optional[datetime] = None + + +class VersionOut(BaseModel): + """版本输出""" + id: int + version_number: str + title: str + description: Optional[str] = None + release_notes: Optional[str] = None + status: str + project: Optional[str] = None + released_at: Optional[datetime] = None + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/app/api/v1/module_system/version/service.py b/backend/app/api/v1/module_system/version/service.py new file mode 100644 index 00000000..b7a83f86 --- /dev/null +++ b/backend/app/api/v1/module_system/version/service.py @@ -0,0 +1,48 @@ +from sqlalchemy.orm import Session +from app.api.v1.module_system.version.crud import version_crud +from app.api.v1.module_system.version.schema import VersionCreate, VersionUpdate + + +class VersionService: + """版本服务类""" + + @staticmethod + def create_version(db: Session, version_create: VersionCreate): + """创建版本""" + return version_crud.create(db, obj_in=version_create) + + @staticmethod + def get_version(db: Session, version_id: int): + """获取版本""" + return version_crud.get(db, id=version_id) + + @staticmethod + def get_versions(db: Session, skip: int = 0, limit: int = 100): + """获取版本列表""" + return version_crud.get_multi(db, skip=skip, limit=limit) + + @staticmethod + def update_version(db: Session, version_id: int, version_update: VersionUpdate): + """更新版本""" + db_version = version_crud.get(db, id=version_id) + if db_version: + return version_crud.update(db, db_obj=db_version, obj_in=version_update) + return None + + @staticmethod + def delete_version(db: Session, version_id: int): + """删除版本""" + return version_crud.remove(db, id=version_id) + + @staticmethod + def get_version_by_number(db: Session, version_number: str): + """根据版本号获取版本""" + return version_crud.get_version_by_number(db, version_number=version_number) + + @staticmethod + def get_versions_by_status(db: Session, status: str, skip: int = 0, limit: int = 100): + """根据状态获取版本列表""" + return version_crud.get_versions_by_status(db, status=status, skip=skip, limit=limit) + + +version_service = VersionService() \ No newline at end of file