fix:新增审核操作日志相关接口

master
HuangHuiKang 2 days ago
parent 5d52158c3e
commit 1d7d64cc19

@ -5,18 +5,21 @@ 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.base_schema import AuthSchema, PageResultSchema
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from .schema import (
EnergyReportReviewCreateSchema,
EnergyReportReviewLogCreateSchema,
EnergyReportReviewLogOutSchema,
EnergyReportReviewLogQueryParam,
EnergyReportReviewOutSchema,
EnergyReportReviewPageOutSchema,
EnergyReportReviewQueryParam,
EnergyReportReviewUpdateSchema,
)
from .service import EnergyReportReviewService
from .service import EnergyReportReviewLogService, EnergyReportReviewService
EnergyReportReviewRouter = APIRouter(route_class=OperationLogRoute, prefix="/report-review", tags=["能源报告审核"])
@ -91,3 +94,61 @@ async def delete_energy_report_review_controller(
) -> JSONResponse:
await EnergyReportReviewService(auth).delete(ids=ids)
return SuccessResponse(msg="删除能源报告审核任务成功")
@EnergyReportReviewRouter.get(
"/log/list",
summary="查询能源报告审核操作记录列表",
response_model=ResponseSchema[PageResultSchema[EnergyReportReviewLogOutSchema]],
)
async def get_energy_report_review_log_list_controller(
page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[EnergyReportReviewLogQueryParam, Depends()],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:query"]))],
) -> JSONResponse:
result = await EnergyReportReviewLogService(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(
"/log/detail/{id}",
summary="查询能源报告审核操作记录详情",
response_model=ResponseSchema[EnergyReportReviewLogOutSchema],
)
async def get_energy_report_review_log_detail_controller(
id: Annotated[int, Path(description="能源报告审核操作记录ID")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:query"]))],
) -> JSONResponse:
result = await EnergyReportReviewLogService(auth).detail(id=id)
return SuccessResponse(data=result, msg="查询能源报告审核操作记录详情成功")
@EnergyReportReviewRouter.post(
"/log/create",
summary="创建能源报告审核操作记录",
response_model=ResponseSchema[EnergyReportReviewLogOutSchema],
)
async def create_energy_report_review_log_controller(
data: EnergyReportReviewLogCreateSchema,
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:update"]))],
) -> JSONResponse:
result = await EnergyReportReviewLogService(auth).create(data=data)
return SuccessResponse(data=result, msg="创建能源报告审核操作记录成功")
@EnergyReportReviewRouter.delete(
"/log/delete",
summary="删除能源报告审核操作记录",
response_model=ResponseSchema[None],
)
async def delete_energy_report_review_log_controller(
ids: Annotated[list[int], Body(description="ID列表")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:report_review:delete"]))],
) -> JSONResponse:
await EnergyReportReviewLogService(auth).delete(ids=ids)
return SuccessResponse(msg="删除能源报告审核操作记录成功")

@ -1,8 +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
from .model import EnergyReportReviewLogModel, EnergyReportReviewModel
from .schema import (
EnergyReportReviewCreateSchema,
EnergyReportReviewLogCreateSchema,
EnergyReportReviewUpdateSchema,
)
class EnergyReportReviewCRUD(CRUDBase[EnergyReportReviewModel, EnergyReportReviewCreateSchema, EnergyReportReviewUpdateSchema]):
@ -10,3 +14,12 @@ class EnergyReportReviewCRUD(CRUDBase[EnergyReportReviewModel, EnergyReportRevie
def __init__(self, auth: AuthSchema) -> None:
super().__init__(model=EnergyReportReviewModel, auth=auth)
class EnergyReportReviewLogCRUD(
CRUDBase[EnergyReportReviewLogModel, EnergyReportReviewLogCreateSchema, EnergyReportReviewLogCreateSchema]
):
"""能源报告审核操作记录数据层。"""
def __init__(self, auth: AuthSchema) -> None:
super().__init__(model=EnergyReportReviewLogModel, auth=auth)

@ -47,3 +47,36 @@ class EnergyReportReviewModel(ModelMixin, TenantMixin, UserMixin):
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)
class EnergyReportReviewLogModel(ModelMixin, TenantMixin, UserMixin):
"""能源报告审核操作记录模型。"""
__tablename__: str = "energy_report_review_log"
__table_args__: dict[str, str] = {"comment": "能源报告审核操作记录表"}
__loader_options__: list[str] = [
"review",
"created_by",
"updated_by",
"deleted_by",
"tenant_by",
]
report_review_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("energy_report_review.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="能源报告审核任务ID",
)
task_name: Mapped[str] = mapped_column(String(128), nullable=False, comment="任务名称快照", index=True)
action_type: Mapped[str] = mapped_column(
String(32),
nullable=False,
index=True,
comment="操作类型:create_task/start_review/review_pass/review_reject",
)
action_name: Mapped[str] = mapped_column(String(64), nullable=False, comment="操作名称")
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="操作描述")
review = relationship("EnergyReportReviewModel", lazy="selectin", foreign_keys=[report_review_id], uselist=False)

@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import Literal
from fastapi import Query
from pydantic import BaseModel, ConfigDict, Field, field_validator
@ -92,3 +93,44 @@ class EnergyReportReviewQueryParam(BaseQueryParam, UserByQueryParam, TenantByQue
self.template_id = (QueueEnum.eq.value, self.template_id)
if isinstance(self.reviewer_id, int):
self.reviewer_id = (QueueEnum.eq.value, self.reviewer_id)
ReportReviewLogActionType = Literal["create_task", "start_review", "review_pass", "review_reject"]
class EnergyReportReviewLogCreateSchema(BaseModel):
"""能源报告审核操作记录创建模型。"""
report_review_id: int = Field(..., ge=1, description="能源报告审核任务ID")
action_type: ReportReviewLogActionType = Field(..., description="操作类型")
description: str | None = Field(default=None, max_length=500, description="操作描述")
task_name: str = Field(default="", description="任务名称,由后端填充")
action_name: str = Field(default="", description="操作名称,由后端填充")
class EnergyReportReviewLogOutSchema(EnergyReportReviewLogCreateSchema, BaseSchema, UserBySchema, TenantBySchema):
"""能源报告审核操作记录响应模型。"""
model_config = ConfigDict(from_attributes=True)
task_name: str = Field(description="任务名称")
action_name: str = Field(description="操作名称")
operator_id: int | None = Field(default=None, description="操作人ID")
operator_name: str | None = Field(default=None, description="操作人名称")
@dataclass
class EnergyReportReviewLogQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""能源报告审核操作记录查询参数。"""
report_review_id: int | None = Query(None, ge=1, description="能源报告审核任务ID")
task_name: str | None = Query(None, description="任务名称")
action_type: str | None = Query(None, description="操作类型")
def __post_init__(self) -> None:
if isinstance(self.report_review_id, int):
self.report_review_id = (QueueEnum.eq.value, self.report_review_id)
if self.task_name:
self.task_name = (QueueEnum.like.value, self.task_name)
if self.action_type:
self.action_type = (QueueEnum.eq.value, self.action_type)

@ -1,12 +1,15 @@
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.base_schema import AuthSchema, PageResultSchema
from app.core.exceptions import CustomException
from .crud import EnergyReportReviewCRUD
from .crud import EnergyReportReviewCRUD, EnergyReportReviewLogCRUD
from .schema import (
EnergyReportReviewCreateSchema,
EnergyReportReviewLogCreateSchema,
EnergyReportReviewLogOutSchema,
EnergyReportReviewLogQueryParam,
EnergyReportReviewOutSchema,
EnergyReportReviewQueryParam,
EnergyReportReviewStatsSchema,
@ -15,6 +18,13 @@ from .schema import (
ENERGY_REPORT_REVIEW_BUSINESS_TYPE = "energy_report_review"
REVIEW_LOG_ACTION_NAMES = {
"create_task": "创建审查任务",
"start_review": "开始审查",
"review_pass": "审核通过",
"review_reject": "审核不通过",
}
class EnergyReportReviewService:
"""能源报告审核任务服务。"""
@ -59,6 +69,12 @@ class EnergyReportReviewService:
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)
await EnergyReportReviewLogService(self.auth).create_system_log(
report_review_id=review.id,
task_name=review.task_name,
action_type="create_task",
description=f"创建审查任务:{review.task_name}",
)
return self._to_out_schema(review)
async def update(self, id: int, data: EnergyReportReviewUpdateSchema) -> EnergyReportReviewOutSchema:
@ -154,3 +170,83 @@ class EnergyReportReviewService:
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)
class EnergyReportReviewLogService:
"""能源报告审核操作记录服务。"""
def __init__(self, auth: AuthSchema) -> None:
self.auth = auth
async def detail(self, id: int) -> EnergyReportReviewLogOutSchema:
log = await EnergyReportReviewLogCRUD(self.auth).get_or_404(id=id, msg="操作记录不存在")
return self._to_out_schema(log)
async def page(
self,
page_no: int,
page_size: int,
search: EnergyReportReviewLogQueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> PageResultSchema[EnergyReportReviewLogOutSchema]:
search_dict = vars(search) if search else {}
page_result = await EnergyReportReviewLogCRUD(self.auth).page(
offset=(page_no - 1) * page_size,
limit=page_size,
order_by=order_by or [{"id": "desc"}],
search=search_dict,
out_schema=EnergyReportReviewLogOutSchema,
)
page_result.items = [self._fill_out_dict(item) for item in page_result.items]
return page_result
async def create(self, data: EnergyReportReviewLogCreateSchema) -> EnergyReportReviewLogOutSchema:
review = await EnergyReportReviewCRUD(self.auth).get_or_404(
id=data.report_review_id,
msg="新增操作记录失败,能源报告审核任务不存在",
)
log = await self.create_system_log(
report_review_id=review.id,
task_name=review.task_name,
action_type=data.action_type,
description=data.description or self._default_description(review.task_name, data.action_type),
)
return self._to_out_schema(log)
async def delete(self, ids: list[int]) -> None:
if len(ids) < 1:
raise CustomException(msg="删除失败,删除对象不能为空")
await EnergyReportReviewLogCRUD(self.auth).delete(ids=ids)
async def create_system_log(self, report_review_id: int, task_name: str, action_type: str, description: str | None = None):
action_name = REVIEW_LOG_ACTION_NAMES.get(action_type)
if not action_name:
raise CustomException(msg="操作类型不支持")
return await EnergyReportReviewLogCRUD(self.auth).create(
data=EnergyReportReviewLogCreateSchema(
report_review_id=report_review_id,
action_type=action_type, # type: ignore[arg-type]
description=description,
task_name=task_name,
action_name=action_name,
),
)
def _to_out_schema(self, log) -> EnergyReportReviewLogOutSchema:
log_out = EnergyReportReviewLogOutSchema.model_validate(log)
log_out.operator_id = log_out.created_id
if getattr(log, "created_by", None):
log_out.operator_name = log.created_by.name
log_out.action_name = REVIEW_LOG_ACTION_NAMES.get(log.action_type, log.action_type)
return log_out
def _fill_out_dict(self, item: dict) -> dict:
created_by = item.get("created_by") or {}
item["operator_id"] = item.get("created_id")
item["operator_name"] = created_by.get("name")
item["action_name"] = REVIEW_LOG_ACTION_NAMES.get(item.get("action_type"), item.get("action_name"))
return item
def _default_description(self, task_name: str, action_type: str) -> str:
action_name = REVIEW_LOG_ACTION_NAMES.get(action_type, action_type)
return f"{action_name}{task_name}"

@ -1,8 +1,10 @@
from app.api.v1.module_common.file.crud import FileCRUD
from app.api.v1.module_common.file.crud import FileCRUD
from app.core.base_schema import AuthSchema
from app.core.exceptions import CustomException
from sqlalchemy import select
from .crud import EnergyReportTemplateCRUD
from .model import EnergyReportTemplateModel
from .schema import (
EnergyReportTemplateCreateSchema,
EnergyReportTemplateOutSchema,
@ -14,7 +16,7 @@ ENERGY_REPORT_TEMPLATE_BUSINESS_TYPE = "energy_report_template"
class EnergyReportTemplateService:
"""能源报告模板服务。"""
"""鑳芥簮鎶ュ憡妯℃澘鏈嶅姟銆?""
def __init__(self, auth: AuthSchema) -> None:
self.auth = auth
@ -53,7 +55,7 @@ class EnergyReportTemplateService:
return self._to_out_schema(template)
async def update(self, id: int, data: EnergyReportTemplateUpdateSchema) -> EnergyReportTemplateOutSchema:
old_template = await EnergyReportTemplateCRUD(self.auth).get_or_404(id=id, msg="更新失败,能源报告模板不存在")
old_template = await EnergyReportTemplateCRUD(self.auth).get_or_404(id=id, msg="鏇存柊澶辫触锛岃兘婧愭姤鍛婃ā鏉夸笉瀛樺湪")
await self._validate_unique_name(template_name=data.template_name, exclude_id=id)
await self._validate_file(file_id=data.file_id, current_template_id=id)
template = await EnergyReportTemplateCRUD(self.auth).update(id=id, data=data)
@ -64,12 +66,12 @@ class EnergyReportTemplateService:
async def delete(self, ids: list[int]) -> None:
if len(ids) < 1:
raise CustomException(msg="删除失败,删除对象不能为空")
raise CustomException(msg="鍒犻櫎澶辫触锛屽垹闄ゅ璞′笉鑳戒负绌?)
templates = await EnergyReportTemplateCRUD(self.auth).get_list(search={"id": ("in", ids)})
template_map = {template.id: template for template in templates}
for template_id in ids:
if template_id not in template_map:
raise CustomException(msg="删除失败,能源报告模板不存在")
raise CustomException(msg="鍒犻櫎澶辫触锛岃兘婧愭姤鍛婃ā鏉夸笉瀛樺湪")
for template in templates:
await self._unbind_file(file_id=template.file_id, template_id=template.id)
await EnergyReportTemplateCRUD(self.auth).delete(ids=ids)
@ -85,16 +87,16 @@ class EnergyReportTemplateService:
async def _validate_unique_name(self, template_name: str, exclude_id: int | None = None) -> None:
template = await EnergyReportTemplateCRUD(self.auth).get(template_name=template_name)
if template and template.id != exclude_id:
raise CustomException(msg="能源报告模板名称已存在")
raise CustomException(msg="鑳芥簮鎶ュ憡妯℃澘鍚嶇О宸插瓨鍦?)
async def _validate_file(self, file_id: int, current_template_id: int | None = None) -> None:
file_record = await FileCRUD(self.auth).get(id=file_id)
if not file_record:
raise CustomException(msg="文件记录不存在")
raise CustomException(msg="鏂囦欢璁板綍涓嶅瓨鍦?)
if file_record.business_type and file_record.business_type != ENERGY_REPORT_TEMPLATE_BUSINESS_TYPE:
raise CustomException(msg="文件已绑定到其他业务类型")
raise CustomException(msg="鏂囦欢宸茬粦瀹氬埌鍏朵粬涓氬姟绫诲瀷")
if file_record.business_id and file_record.business_id != current_template_id:
raise CustomException(msg="文件已绑定到其他能源报告模板")
raise CustomException(msg="鏂囦欢宸茬粦瀹氬埌鍏朵粬鑳芥簮鎶ュ憡妯℃澘")
async def _bind_file(self, file_id: int, template_id: int) -> None:
await FileCRUD(self.auth).set(

@ -16,7 +16,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.api.v1.module_common.file.model import FileModel
from app.api.v1.module_energy.chapter.model import EnergyReportChapterModel
from app.api.v1.module_energy.report_review.model import EnergyReportReviewModel
from app.api.v1.module_energy.report_review.model import EnergyReportReviewLogModel, EnergyReportReviewModel
from app.api.v1.module_energy.report_template.model import EnergyReportTemplateModel
from app.api.v1.module_energy.variable.model import EnergyReportVariableModel
from app.api.v1.module_platform.email.model import EmailConfigModel, EmailTemplateModel
@ -86,6 +86,7 @@ class InitializeData:
EnergyReportChapterModel,
EnergyReportVariableModel,
EnergyReportReviewModel,
EnergyReportReviewLogModel,
# ── 日志表(追加写入) ──
LoginLogModel,
OperationLogModel,

Loading…
Cancel
Save