feat:新增能源报告-审核相关接口
parent
2470c31f64
commit
4f0fdbcdd8
@ -0,0 +1,93 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Body, Depends, Path
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from app.common.response import ResponseSchema, SuccessResponse
|
||||||
|
from app.core.base_params import PaginationQueryParam
|
||||||
|
from app.core.base_schema import AuthSchema
|
||||||
|
from app.core.dependencies import AuthPermission
|
||||||
|
from app.core.router_class import OperationLogRoute
|
||||||
|
|
||||||
|
from .schema import (
|
||||||
|
EnergyReportReviewCreateSchema,
|
||||||
|
EnergyReportReviewOutSchema,
|
||||||
|
EnergyReportReviewPageOutSchema,
|
||||||
|
EnergyReportReviewQueryParam,
|
||||||
|
EnergyReportReviewUpdateSchema,
|
||||||
|
)
|
||||||
|
from .service import EnergyReportReviewService
|
||||||
|
|
||||||
|
EnergyReportReviewRouter = APIRouter(route_class=OperationLogRoute, prefix="/report-review", tags=["能源报告审核"])
|
||||||
|
|
||||||
|
|
||||||
|
@EnergyReportReviewRouter.get(
|
||||||
|
"/list",
|
||||||
|
summary="查询能源报告审核任务列表",
|
||||||
|
response_model=ResponseSchema[EnergyReportReviewPageOutSchema],
|
||||||
|
)
|
||||||
|
async def get_energy_report_review_list_controller(
|
||||||
|
page: Annotated[PaginationQueryParam, Depends()],
|
||||||
|
search: Annotated[EnergyReportReviewQueryParam, Depends()],
|
||||||
|
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:query"]))],
|
||||||
|
) -> JSONResponse:
|
||||||
|
result = await EnergyReportReviewService(auth).page(
|
||||||
|
page_no=page.page_no,
|
||||||
|
page_size=page.page_size,
|
||||||
|
search=search,
|
||||||
|
order_by=page.order_by or [{"id": "desc"}],
|
||||||
|
)
|
||||||
|
return SuccessResponse(data=result, msg="查询能源报告审核任务列表成功")
|
||||||
|
|
||||||
|
|
||||||
|
@EnergyReportReviewRouter.get(
|
||||||
|
"/detail/{id}",
|
||||||
|
summary="查询能源报告审核任务详情",
|
||||||
|
response_model=ResponseSchema[EnergyReportReviewOutSchema],
|
||||||
|
)
|
||||||
|
async def get_energy_report_review_detail_controller(
|
||||||
|
id: Annotated[int, Path(description="能源报告审核任务ID")],
|
||||||
|
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:detail"]))],
|
||||||
|
) -> JSONResponse:
|
||||||
|
result = await EnergyReportReviewService(auth).detail(id=id)
|
||||||
|
return SuccessResponse(data=result, msg="查询能源报告审核任务详情成功")
|
||||||
|
|
||||||
|
|
||||||
|
@EnergyReportReviewRouter.post(
|
||||||
|
"/create",
|
||||||
|
summary="创建能源报告审核任务",
|
||||||
|
response_model=ResponseSchema[EnergyReportReviewOutSchema],
|
||||||
|
)
|
||||||
|
async def create_energy_report_review_controller(
|
||||||
|
data: EnergyReportReviewCreateSchema,
|
||||||
|
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:create"]))],
|
||||||
|
) -> JSONResponse:
|
||||||
|
result = await EnergyReportReviewService(auth).create(data=data)
|
||||||
|
return SuccessResponse(data=result, msg="创建能源报告审核任务成功")
|
||||||
|
|
||||||
|
|
||||||
|
@EnergyReportReviewRouter.put(
|
||||||
|
"/update/{id}",
|
||||||
|
summary="修改能源报告审核任务",
|
||||||
|
response_model=ResponseSchema[EnergyReportReviewOutSchema],
|
||||||
|
)
|
||||||
|
async def update_energy_report_review_controller(
|
||||||
|
data: EnergyReportReviewUpdateSchema,
|
||||||
|
id: Annotated[int, Path(description="能源报告审核任务ID")],
|
||||||
|
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:update"]))],
|
||||||
|
) -> JSONResponse:
|
||||||
|
result = await EnergyReportReviewService(auth).update(id=id, data=data)
|
||||||
|
return SuccessResponse(data=result, msg="修改能源报告审核任务成功")
|
||||||
|
|
||||||
|
|
||||||
|
@EnergyReportReviewRouter.delete(
|
||||||
|
"/delete",
|
||||||
|
summary="删除能源报告审核任务",
|
||||||
|
response_model=ResponseSchema[None],
|
||||||
|
)
|
||||||
|
async def delete_energy_report_review_controller(
|
||||||
|
ids: Annotated[list[int], Body(description="ID列表")],
|
||||||
|
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:delete"]))],
|
||||||
|
) -> JSONResponse:
|
||||||
|
await EnergyReportReviewService(auth).delete(ids=ids)
|
||||||
|
return SuccessResponse(msg="删除能源报告审核任务成功")
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
from app.core.base_crud import CRUDBase
|
||||||
|
from app.core.base_schema import AuthSchema
|
||||||
|
|
||||||
|
from .model import EnergyReportReviewModel
|
||||||
|
from .schema import EnergyReportReviewCreateSchema, EnergyReportReviewUpdateSchema
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewCRUD(CRUDBase[EnergyReportReviewModel, EnergyReportReviewCreateSchema, EnergyReportReviewUpdateSchema]):
|
||||||
|
"""能源报告审核任务数据层。"""
|
||||||
|
|
||||||
|
def __init__(self, auth: AuthSchema) -> None:
|
||||||
|
super().__init__(model=EnergyReportReviewModel, auth=auth)
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
from sqlalchemy import ForeignKey, Integer, String, Text
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from app.core.base_model import ModelMixin, TenantMixin, UserMixin
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewModel(ModelMixin, TenantMixin, UserMixin):
|
||||||
|
"""能源报告审核任务模型。"""
|
||||||
|
|
||||||
|
__tablename__: str = "energy_report_review"
|
||||||
|
__table_args__: dict[str, str] = {"comment": "能源报告审核任务表"}
|
||||||
|
__loader_options__: list[str] = [
|
||||||
|
"template",
|
||||||
|
"file",
|
||||||
|
"reviewer",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"deleted_by",
|
||||||
|
"tenant_by",
|
||||||
|
]
|
||||||
|
|
||||||
|
task_name: Mapped[str] = mapped_column(String(128), nullable=False, comment="任务名称", index=True)
|
||||||
|
template_id: Mapped[int] = mapped_column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey("energy_report_template.id", ondelete="RESTRICT", onupdate="CASCADE"),
|
||||||
|
nullable=False,
|
||||||
|
index=True,
|
||||||
|
comment="能源报告模板ID",
|
||||||
|
)
|
||||||
|
file_id: Mapped[int] = mapped_column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey("common_file.id", ondelete="RESTRICT", onupdate="CASCADE"),
|
||||||
|
nullable=False,
|
||||||
|
index=True,
|
||||||
|
comment="审核文档文件ID",
|
||||||
|
)
|
||||||
|
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态 0:待审查 1:已审查 2:审查失败", index=True)
|
||||||
|
reviewer_id: Mapped[int | None] = mapped_column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey("sys_user.id", ondelete="SET NULL", onupdate="CASCADE"),
|
||||||
|
nullable=True,
|
||||||
|
index=True,
|
||||||
|
comment="审查人ID",
|
||||||
|
)
|
||||||
|
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="描述")
|
||||||
|
|
||||||
|
template = relationship("EnergyReportTemplateModel", lazy="selectin", foreign_keys=[template_id], uselist=False)
|
||||||
|
file = relationship("FileModel", lazy="selectin", foreign_keys=[file_id], uselist=False)
|
||||||
|
reviewer = relationship("UserModel", lazy="selectin", foreign_keys=[reviewer_id], uselist=False)
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from fastapi import Query
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
|
|
||||||
|
from app.api.v1.module_common.file.schema import FileOutSchema
|
||||||
|
from app.api.v1.module_energy.report_template.schema import EnergyReportTemplateOutSchema
|
||||||
|
from app.common.enums import QueueEnum
|
||||||
|
from app.core.base_params import BaseQueryParam, TenantByQueryParam, UserByQueryParam
|
||||||
|
from app.core.base_schema import BaseSchema, CommonSchema, TenantBySchema, UserBySchema
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewCreateSchema(BaseModel):
|
||||||
|
"""能源报告审核任务创建模型。"""
|
||||||
|
|
||||||
|
task_name: str = Field(..., min_length=1, max_length=128, description="任务名称")
|
||||||
|
template_id: int = Field(..., ge=1, description="能源报告模板ID")
|
||||||
|
file_id: int = Field(..., ge=1, description="上传文档文件ID")
|
||||||
|
status: int = Field(default=0, ge=0, le=2, description="状态 0:待审查 1:已审查 2:审查失败")
|
||||||
|
reviewer_id: int | None = Field(default=None, ge=1, description="审查人ID")
|
||||||
|
description: str | None = Field(default=None, max_length=500, description="描述")
|
||||||
|
|
||||||
|
@field_validator("task_name")
|
||||||
|
@classmethod
|
||||||
|
def validate_task_name(cls, value: str) -> str:
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
raise ValueError("任务名称不能为空")
|
||||||
|
return value
|
||||||
|
|
||||||
|
@field_validator("status")
|
||||||
|
@classmethod
|
||||||
|
def validate_status(cls, value: int) -> int:
|
||||||
|
if value not in {0, 1, 2}:
|
||||||
|
raise ValueError("状态仅支持 0(待审查)、1(已审查)、2(审查失败)")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewUpdateSchema(EnergyReportReviewCreateSchema):
|
||||||
|
"""能源报告审核任务更新模型。"""
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewOutSchema(EnergyReportReviewCreateSchema, BaseSchema, UserBySchema, TenantBySchema):
|
||||||
|
"""能源报告审核任务响应模型。"""
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
template: EnergyReportTemplateOutSchema | None = Field(default=None, description="能源报告模板")
|
||||||
|
file: FileOutSchema | None = Field(default=None, description="上传文档")
|
||||||
|
reviewer: CommonSchema | None = Field(default=None, description="审查人")
|
||||||
|
template_name: str | None = Field(default=None, description="模板名称")
|
||||||
|
reviewer_name: str | None = Field(default=None, description="审查人名称")
|
||||||
|
file_url: str | None = Field(default=None, description="文档访问地址")
|
||||||
|
original_name: str | None = Field(default=None, description="文档原始名称")
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewStatsSchema(BaseModel):
|
||||||
|
"""能源报告审核任务统计模型。"""
|
||||||
|
|
||||||
|
submitted_count: int = Field(default=0, description="提交任务数")
|
||||||
|
reviewed_count: int = Field(default=0, description="已审查任务数")
|
||||||
|
pending_count: int = Field(default=0, description="待审查任务数")
|
||||||
|
failed_count: int = Field(default=0, description="审查失败任务数")
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewPageOutSchema(BaseModel):
|
||||||
|
"""能源报告审核任务分页和统计响应模型。"""
|
||||||
|
|
||||||
|
page_no: int = Field(description="当前页码")
|
||||||
|
page_size: int = Field(description="每页数量")
|
||||||
|
total: int = Field(description="总数")
|
||||||
|
has_next: bool = Field(description="是否有下一页")
|
||||||
|
stats: EnergyReportReviewStatsSchema = Field(description="统计数据")
|
||||||
|
items: list[dict] = Field(default_factory=list, description="列表数据")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EnergyReportReviewQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
|
||||||
|
"""能源报告审核任务查询参数。"""
|
||||||
|
|
||||||
|
task_name: str | None = Query(None, description="任务名称")
|
||||||
|
status: int | None = Query(None, ge=0, le=2, description="状态 0:待审查 1:已审查 2:审查失败")
|
||||||
|
template_id: int | None = Query(None, ge=1, description="模板ID")
|
||||||
|
reviewer_id: int | None = Query(None, ge=1, description="审查人ID")
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if self.task_name:
|
||||||
|
self.task_name = (QueueEnum.like.value, self.task_name)
|
||||||
|
if isinstance(self.status, int):
|
||||||
|
self.status = (QueueEnum.eq.value, self.status)
|
||||||
|
if isinstance(self.template_id, int):
|
||||||
|
self.template_id = (QueueEnum.eq.value, self.template_id)
|
||||||
|
if isinstance(self.reviewer_id, int):
|
||||||
|
self.reviewer_id = (QueueEnum.eq.value, self.reviewer_id)
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
from app.api.v1.module_common.file.crud import FileCRUD
|
||||||
|
from app.api.v1.module_energy.report_template.crud import EnergyReportTemplateCRUD
|
||||||
|
from app.api.v1.module_system.user.crud import UserCRUD
|
||||||
|
from app.core.base_schema import AuthSchema
|
||||||
|
from app.core.exceptions import CustomException
|
||||||
|
|
||||||
|
from .crud import EnergyReportReviewCRUD
|
||||||
|
from .schema import (
|
||||||
|
EnergyReportReviewCreateSchema,
|
||||||
|
EnergyReportReviewOutSchema,
|
||||||
|
EnergyReportReviewQueryParam,
|
||||||
|
EnergyReportReviewStatsSchema,
|
||||||
|
EnergyReportReviewUpdateSchema,
|
||||||
|
)
|
||||||
|
|
||||||
|
ENERGY_REPORT_REVIEW_BUSINESS_TYPE = "energy_report_review"
|
||||||
|
|
||||||
|
|
||||||
|
class EnergyReportReviewService:
|
||||||
|
"""能源报告审核任务服务。"""
|
||||||
|
|
||||||
|
def __init__(self, auth: AuthSchema) -> None:
|
||||||
|
self.auth = auth
|
||||||
|
|
||||||
|
async def detail(self, id: int) -> EnergyReportReviewOutSchema:
|
||||||
|
review = await EnergyReportReviewCRUD(self.auth).get_or_404(id=id)
|
||||||
|
return self._to_out_schema(review)
|
||||||
|
|
||||||
|
async def page(
|
||||||
|
self,
|
||||||
|
page_no: int,
|
||||||
|
page_size: int,
|
||||||
|
search: EnergyReportReviewQueryParam | None = None,
|
||||||
|
order_by: list[dict[str, str]] | None = None,
|
||||||
|
) -> dict:
|
||||||
|
search_dict = vars(search) if search else None
|
||||||
|
stats = await self._stats(search_dict=self._build_stats_search(search_dict))
|
||||||
|
offset = (page_no - 1) * page_size
|
||||||
|
page_result = await EnergyReportReviewCRUD(self.auth).page(
|
||||||
|
offset=offset,
|
||||||
|
limit=page_size,
|
||||||
|
order_by=order_by or [{"id": "desc"}],
|
||||||
|
search=search_dict,
|
||||||
|
out_schema=EnergyReportReviewOutSchema,
|
||||||
|
)
|
||||||
|
items = [self._fill_out_dict(item) for item in page_result.items]
|
||||||
|
return {
|
||||||
|
"page_no": page_result.page_no,
|
||||||
|
"page_size": page_result.page_size,
|
||||||
|
"total": page_result.total,
|
||||||
|
"has_next": page_result.has_next,
|
||||||
|
"stats": stats.model_dump(),
|
||||||
|
"items": items,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def create(self, data: EnergyReportReviewCreateSchema) -> EnergyReportReviewOutSchema:
|
||||||
|
await self._validate_template(template_id=data.template_id)
|
||||||
|
await self._validate_file(file_id=data.file_id)
|
||||||
|
await self._validate_reviewer(reviewer_id=data.reviewer_id)
|
||||||
|
review = await EnergyReportReviewCRUD(self.auth).create(data=data)
|
||||||
|
await self._bind_file(file_id=data.file_id, review_id=review.id)
|
||||||
|
return self._to_out_schema(review)
|
||||||
|
|
||||||
|
async def update(self, id: int, data: EnergyReportReviewUpdateSchema) -> EnergyReportReviewOutSchema:
|
||||||
|
old_review = await EnergyReportReviewCRUD(self.auth).get_or_404(id=id, msg="更新失败,能源报告审核任务不存在")
|
||||||
|
await self._validate_template(template_id=data.template_id)
|
||||||
|
await self._validate_file(file_id=data.file_id, current_review_id=id)
|
||||||
|
await self._validate_reviewer(reviewer_id=data.reviewer_id)
|
||||||
|
review = await EnergyReportReviewCRUD(self.auth).update(id=id, data=data)
|
||||||
|
if old_review.file_id != data.file_id:
|
||||||
|
await self._unbind_file(file_id=old_review.file_id, review_id=id)
|
||||||
|
await self._bind_file(file_id=data.file_id, review_id=id)
|
||||||
|
return self._to_out_schema(review)
|
||||||
|
|
||||||
|
async def delete(self, ids: list[int]) -> None:
|
||||||
|
if len(ids) < 1:
|
||||||
|
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||||
|
reviews = await EnergyReportReviewCRUD(self.auth).get_list(search={"id": ("in", ids)})
|
||||||
|
review_map = {review.id: review for review in reviews}
|
||||||
|
for review_id in ids:
|
||||||
|
if review_id not in review_map:
|
||||||
|
raise CustomException(msg="删除失败,能源报告审核任务不存在")
|
||||||
|
for review in reviews:
|
||||||
|
await self._unbind_file(file_id=review.file_id, review_id=review.id)
|
||||||
|
await EnergyReportReviewCRUD(self.auth).delete(ids=ids)
|
||||||
|
|
||||||
|
async def _stats(self, search_dict: dict | None = None) -> EnergyReportReviewStatsSchema:
|
||||||
|
records = await EnergyReportReviewCRUD(self.auth).get_list(search=search_dict or {})
|
||||||
|
return EnergyReportReviewStatsSchema(
|
||||||
|
submitted_count=len(records),
|
||||||
|
reviewed_count=len([record for record in records if record.status == 1]),
|
||||||
|
pending_count=len([record for record in records if record.status == 0]),
|
||||||
|
failed_count=len([record for record in records if record.status == 2]),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _build_stats_search(self, search_dict: dict | None = None) -> dict:
|
||||||
|
if not search_dict:
|
||||||
|
return {}
|
||||||
|
stats_keys = {"created_time", "updated_time", "tenant_id", "created_id", "updated_id"}
|
||||||
|
return {key: value for key, value in search_dict.items() if key in stats_keys and value is not None}
|
||||||
|
|
||||||
|
def _to_out_schema(self, review) -> EnergyReportReviewOutSchema:
|
||||||
|
review_out = EnergyReportReviewOutSchema.model_validate(review)
|
||||||
|
if review_out.template:
|
||||||
|
review_out.template_name = review_out.template.template_name
|
||||||
|
if review_out.file:
|
||||||
|
review_out.file_url = review_out.file.file_url
|
||||||
|
review_out.original_name = review_out.file.original_name
|
||||||
|
if hasattr(review, "reviewer") and review.reviewer:
|
||||||
|
review_out.reviewer_name = review.reviewer.name
|
||||||
|
return review_out
|
||||||
|
|
||||||
|
def _fill_out_dict(self, item: dict) -> dict:
|
||||||
|
template_info = item.get("template") or {}
|
||||||
|
file_info = item.get("file") or {}
|
||||||
|
reviewer_info = item.get("reviewer") or {}
|
||||||
|
item["template_name"] = template_info.get("template_name")
|
||||||
|
item["file_url"] = file_info.get("file_url")
|
||||||
|
item["original_name"] = file_info.get("original_name")
|
||||||
|
item["reviewer_name"] = reviewer_info.get("name")
|
||||||
|
return item
|
||||||
|
|
||||||
|
async def _validate_template(self, template_id: int) -> None:
|
||||||
|
template = await EnergyReportTemplateCRUD(self.auth).get(id=template_id)
|
||||||
|
if not template:
|
||||||
|
raise CustomException(msg="能源报告模板不存在")
|
||||||
|
|
||||||
|
async def _validate_file(self, file_id: int, current_review_id: int | None = None) -> None:
|
||||||
|
file_record = await FileCRUD(self.auth).get(id=file_id)
|
||||||
|
if not file_record:
|
||||||
|
raise CustomException(msg="文件记录不存在")
|
||||||
|
if file_record.business_type and file_record.business_type != ENERGY_REPORT_REVIEW_BUSINESS_TYPE:
|
||||||
|
raise CustomException(msg="文件已绑定到其他业务类型")
|
||||||
|
if file_record.business_id and file_record.business_id != current_review_id:
|
||||||
|
raise CustomException(msg="文件已绑定到其他能源报告审核任务")
|
||||||
|
|
||||||
|
async def _validate_reviewer(self, reviewer_id: int | None) -> None:
|
||||||
|
if reviewer_id is None:
|
||||||
|
return
|
||||||
|
reviewer = await UserCRUD(self.auth).get(id=reviewer_id)
|
||||||
|
if not reviewer:
|
||||||
|
raise CustomException(msg="审查人不存在")
|
||||||
|
|
||||||
|
async def _bind_file(self, file_id: int, review_id: int) -> None:
|
||||||
|
await FileCRUD(self.auth).set(
|
||||||
|
ids=[file_id],
|
||||||
|
business_type=ENERGY_REPORT_REVIEW_BUSINESS_TYPE,
|
||||||
|
business_id=review_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _unbind_file(self, file_id: int, review_id: int) -> None:
|
||||||
|
file_record = await FileCRUD(self.auth).get(id=file_id)
|
||||||
|
if not file_record:
|
||||||
|
return
|
||||||
|
if file_record.business_type == ENERGY_REPORT_REVIEW_BUSINESS_TYPE and file_record.business_id == review_id:
|
||||||
|
await FileCRUD(self.auth).set(ids=[file_id], business_id=None)
|
||||||
Loading…
Reference in New Issue