feat:新增能源报告模板添加标注接口
parent
3f961146ed
commit
2470c31f64
@ -1,8 +1,13 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1.module_energy.chapter.controller import ChapterRouter
|
||||
from app.api.v1.module_energy.config.controller import TemplateConfigRouter
|
||||
from app.api.v1.module_energy.report_template.controller import EnergyReportTemplateRouter
|
||||
from app.api.v1.module_energy.variable.controller import VariableRouter
|
||||
|
||||
energy_router = APIRouter()
|
||||
|
||||
energy_router.include_router(EnergyReportTemplateRouter)
|
||||
|
||||
energy_router.include_router(TemplateConfigRouter)
|
||||
energy_router.include_router(ChapterRouter)
|
||||
energy_router.include_router(VariableRouter)
|
||||
|
||||
@ -0,0 +1 @@
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
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, PageResultSchema
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.router_class import OperationLogRoute
|
||||
|
||||
from .schema import ChapterCreateSchema, ChapterOutSchema, ChapterQueryParam, ChapterUpdateSchema
|
||||
from .service import ChapterService
|
||||
|
||||
ChapterRouter = APIRouter(route_class=OperationLogRoute, prefix="/chapter", tags=["能源报告章节"])
|
||||
|
||||
|
||||
@ChapterRouter.get(
|
||||
"/list",
|
||||
summary="查询章节列表",
|
||||
response_model=ResponseSchema[PageResultSchema[ChapterOutSchema]],
|
||||
)
|
||||
async def get_chapter_list_controller(
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[ChapterQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:chapter:query"]))],
|
||||
) -> JSONResponse:
|
||||
result = await ChapterService(auth).page(
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
search=search,
|
||||
order_by=page.order_by or [{"chapter_order": "asc"}, {"id": "asc"}],
|
||||
)
|
||||
return SuccessResponse(data=result, msg="查询章节列表成功")
|
||||
|
||||
|
||||
@ChapterRouter.get(
|
||||
"/list_by_template",
|
||||
summary="查询模板下全部章节(不分页)",
|
||||
response_model=ResponseSchema[list[ChapterOutSchema]],
|
||||
)
|
||||
async def get_chapter_list_by_template_controller(
|
||||
template_id: Annotated[int, Query(ge=1, description="模板ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:chapter:query"]))],
|
||||
) -> JSONResponse:
|
||||
result = await ChapterService(auth).list_by_template(template_id=template_id)
|
||||
return SuccessResponse(data=result, msg="查询模板章节成功")
|
||||
|
||||
|
||||
@ChapterRouter.get(
|
||||
"/detail/{id}",
|
||||
summary="查询章节详情",
|
||||
response_model=ResponseSchema[ChapterOutSchema],
|
||||
)
|
||||
async def get_chapter_detail_controller(
|
||||
id: Annotated[int, Path(description="章节ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:chapter:detail"]))],
|
||||
) -> JSONResponse:
|
||||
result = await ChapterService(auth).detail(id=id)
|
||||
return SuccessResponse(data=result, msg="查询章节详情成功")
|
||||
|
||||
|
||||
@ChapterRouter.post(
|
||||
"/create",
|
||||
summary="创建章节",
|
||||
response_model=ResponseSchema[ChapterOutSchema],
|
||||
)
|
||||
async def create_chapter_controller(
|
||||
data: ChapterCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:chapter:create"]))],
|
||||
) -> JSONResponse:
|
||||
result = await ChapterService(auth).create(data=data)
|
||||
return SuccessResponse(data=result, msg="创建章节成功")
|
||||
|
||||
|
||||
@ChapterRouter.put(
|
||||
"/update/{id}",
|
||||
summary="修改章节",
|
||||
response_model=ResponseSchema[ChapterOutSchema],
|
||||
)
|
||||
async def update_chapter_controller(
|
||||
data: ChapterUpdateSchema,
|
||||
id: Annotated[int, Path(description="章节ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:chapter:update"]))],
|
||||
) -> JSONResponse:
|
||||
result = await ChapterService(auth).update(id=id, data=data)
|
||||
return SuccessResponse(data=result, msg="修改章节成功")
|
||||
|
||||
|
||||
@ChapterRouter.delete(
|
||||
"/delete",
|
||||
summary="删除章节",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def delete_chapter_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:chapter:delete"]))],
|
||||
) -> JSONResponse:
|
||||
await ChapterService(auth).delete(ids=ids)
|
||||
return SuccessResponse(msg="删除章节成功")
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.core.base_schema import AuthSchema
|
||||
|
||||
from .model import EnergyReportChapterModel
|
||||
from .schema import ChapterCreateSchema, ChapterUpdateSchema
|
||||
|
||||
|
||||
class ChapterCRUD(CRUDBase[EnergyReportChapterModel, ChapterCreateSchema, ChapterUpdateSchema]):
|
||||
"""能源报告章节数据层。"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
super().__init__(model=EnergyReportChapterModel, auth=auth)
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
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 EnergyReportChapterModel(ModelMixin, TenantMixin, UserMixin):
|
||||
"""能源报告章节模型。"""
|
||||
|
||||
__tablename__: str = "energy_report_chapter"
|
||||
__table_args__: dict[str, str] = {"comment": "能源报告章节标注表"}
|
||||
__loader_options__: list[str] = [
|
||||
"template",
|
||||
"rule",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"deleted_by",
|
||||
"tenant_by",
|
||||
]
|
||||
|
||||
template_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("energy_report_template.id", ondelete="CASCADE", onupdate="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="能源报告模板ID",
|
||||
)
|
||||
chapter_name: Mapped[str] = mapped_column(String(128), nullable=False, comment="章节名称", index=True)
|
||||
chapter_order: Mapped[int] = mapped_column(Integer, default=999, nullable=False, comment="章节排序")
|
||||
rule_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("rule_list.id", ondelete="RESTRICT", onupdate="CASCADE"),
|
||||
nullable=False,
|
||||
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)
|
||||
rule = relationship("RuleModel", lazy="selectin", foreign_keys=[rule_id], uselist=False)
|
||||
@ -0,0 +1,69 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
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 ChapterCreateSchema(BaseModel):
|
||||
"""章节创建模型。"""
|
||||
|
||||
template_id: int = Field(..., ge=1, description="能源报告模板ID")
|
||||
chapter_name: str = Field(..., min_length=1, max_length=128, description="章节名称")
|
||||
chapter_order: int = Field(default=999, ge=0, description="章节排序")
|
||||
rule_id: int = Field(..., ge=1, description="关联规则ID")
|
||||
description: str | None = Field(default=None, max_length=500, description="描述")
|
||||
|
||||
@field_validator("chapter_name")
|
||||
@classmethod
|
||||
def validate_chapter_name(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if not value:
|
||||
raise ValueError("章节名称不能为空")
|
||||
return value
|
||||
|
||||
|
||||
class ChapterUpdateSchema(BaseModel):
|
||||
"""章节更新模型。"""
|
||||
|
||||
chapter_name: str = Field(..., min_length=1, max_length=128, description="章节名称")
|
||||
chapter_order: int = Field(default=999, ge=0, description="章节排序")
|
||||
rule_id: int = Field(..., ge=1, description="关联规则ID")
|
||||
description: str | None = Field(default=None, max_length=500, description="描述")
|
||||
|
||||
@field_validator("chapter_name")
|
||||
@classmethod
|
||||
def validate_chapter_name(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if not value:
|
||||
raise ValueError("章节名称不能为空")
|
||||
return value
|
||||
|
||||
|
||||
class ChapterOutSchema(ChapterCreateSchema, BaseSchema, UserBySchema, TenantBySchema):
|
||||
"""章节响应模型。"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
rule_name: str | None = Field(default=None, description="关联规则名称")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChapterQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
|
||||
"""章节查询参数。"""
|
||||
|
||||
template_id: int | None = Query(None, ge=1, description="模板ID")
|
||||
chapter_name: str | None = Query(None, description="章节名称")
|
||||
rule_id: int | None = Query(None, ge=1, description="规则ID")
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.template_id, int):
|
||||
self.template_id = (QueueEnum.eq.value, self.template_id)
|
||||
if self.chapter_name:
|
||||
self.chapter_name = (QueueEnum.like.value, self.chapter_name)
|
||||
if isinstance(self.rule_id, int):
|
||||
self.rule_id = (QueueEnum.eq.value, self.rule_id)
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
from app.api.v1.module_rule.rule.crud import RuleCRUD
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
|
||||
from .crud import ChapterCRUD
|
||||
from .schema import ChapterOutSchema, ChapterQueryParam, ChapterCreateSchema, ChapterUpdateSchema
|
||||
|
||||
|
||||
class ChapterService:
|
||||
"""能源报告章节服务。"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
self.auth = auth
|
||||
|
||||
async def detail(self, id: int) -> ChapterOutSchema:
|
||||
chapter = await ChapterCRUD(self.auth).get_or_404(id=id)
|
||||
return self._to_out_schema(chapter)
|
||||
|
||||
async def list_by_template(self, template_id: int) -> list[ChapterOutSchema]:
|
||||
chapters = await ChapterCRUD(self.auth).get_list(
|
||||
search={"template_id": template_id},
|
||||
order_by=[{"chapter_order": "asc"}, {"id": "asc"}],
|
||||
)
|
||||
return [self._to_out_schema(chapter) for chapter in chapters]
|
||||
|
||||
async def page(
|
||||
self,
|
||||
page_no: int,
|
||||
page_size: int,
|
||||
search: ChapterQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> dict:
|
||||
offset = (page_no - 1) * page_size
|
||||
page_result = await ChapterCRUD(self.auth).page(
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order_by or [{"chapter_order": "asc"}, {"id": "asc"}],
|
||||
search=vars(search) if search else None,
|
||||
out_schema=ChapterOutSchema,
|
||||
)
|
||||
for item in page_result.items:
|
||||
rule_info = item.get("rule") or {}
|
||||
item["rule_name"] = rule_info.get("name")
|
||||
return page_result
|
||||
|
||||
async def create(self, data: ChapterCreateSchema) -> ChapterOutSchema:
|
||||
await self._validate_rule(rule_id=data.rule_id)
|
||||
chapter = await ChapterCRUD(self.auth).create(data=data)
|
||||
return self._to_out_schema(chapter)
|
||||
|
||||
async def update(self, id: int, data: ChapterUpdateSchema) -> ChapterOutSchema:
|
||||
await ChapterCRUD(self.auth).get_or_404(id=id, msg="更新失败,章节标注不存在")
|
||||
await self._validate_rule(rule_id=data.rule_id)
|
||||
chapter = await ChapterCRUD(self.auth).update(id=id, data=data)
|
||||
return self._to_out_schema(chapter)
|
||||
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
chapters = await ChapterCRUD(self.auth).get_list(search={"id": ("in", ids)})
|
||||
chapter_map = {chapter.id: chapter for chapter in chapters}
|
||||
for chapter_id in ids:
|
||||
if chapter_id not in chapter_map:
|
||||
raise CustomException(msg="删除失败,章节标注不存在")
|
||||
await ChapterCRUD(self.auth).delete(ids=ids)
|
||||
|
||||
def _to_out_schema(self, chapter) -> ChapterOutSchema:
|
||||
chapter_out = ChapterOutSchema.model_validate(chapter)
|
||||
if hasattr(chapter, "rule") and chapter.rule:
|
||||
chapter_out.rule_name = chapter.rule.name
|
||||
return chapter_out
|
||||
|
||||
async def _validate_rule(self, rule_id: int) -> None:
|
||||
rule = await RuleCRUD(self.auth).get(id=rule_id)
|
||||
if not rule:
|
||||
raise CustomException(msg="关联规则不存在")
|
||||
@ -0,0 +1 @@
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.common.response import ResponseSchema, SuccessResponse
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.router_class import OperationLogRoute
|
||||
|
||||
from .schema import TemplateConfigSaveSchema
|
||||
from .service import TemplateConfigService
|
||||
|
||||
TemplateConfigRouter = APIRouter(route_class=OperationLogRoute, prefix="/attachment-template/config", tags=["能源报告模板配置"])
|
||||
|
||||
|
||||
@TemplateConfigRouter.get(
|
||||
"/detail",
|
||||
summary="查询模板章节规则和变量关联配置",
|
||||
response_model=ResponseSchema[dict],
|
||||
)
|
||||
async def get_template_config_detail_controller(
|
||||
template_id: Annotated[int, Query(ge=1, description="模板ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:attachment_template:detail"]))],
|
||||
) -> JSONResponse:
|
||||
result = await TemplateConfigService(auth).detail(template_id=template_id)
|
||||
return SuccessResponse(data=result, msg="查询模板配置成功")
|
||||
|
||||
|
||||
@TemplateConfigRouter.post(
|
||||
"/save",
|
||||
summary="统一保存模板章节规则和变量关联配置",
|
||||
response_model=ResponseSchema[dict],
|
||||
)
|
||||
async def save_template_config_controller(
|
||||
data: TemplateConfigSaveSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:attachment_template:update"]))],
|
||||
) -> JSONResponse:
|
||||
result = await TemplateConfigService(auth).save(data=data)
|
||||
return SuccessResponse(data=result, msg="保存模板配置成功")
|
||||
@ -0,0 +1,82 @@
|
||||
from app.api.v1.module_energy.chapter.crud import ChapterCRUD
|
||||
from app.api.v1.module_energy.report_template.crud import EnergyReportTemplateCRUD
|
||||
from app.api.v1.module_energy.variable.service import VariableService
|
||||
from app.api.v1.module_rule.rule.crud import RuleCRUD
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
|
||||
from .schema import TemplateChapterRuleSchema, TemplateConfigSaveSchema
|
||||
|
||||
|
||||
class TemplateConfigService:
|
||||
"""能源报告模板配置服务。"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
self.auth = auth
|
||||
|
||||
async def save(self, data: TemplateConfigSaveSchema) -> dict:
|
||||
await self._validate_template(template_id=data.template_id)
|
||||
await self._validate_rules(chapters=data.chapters)
|
||||
await self._save_chapters(template_id=data.template_id, chapters=data.chapters)
|
||||
variable_result = await VariableService(self.auth).save(data=data)
|
||||
return {
|
||||
"template_id": data.template_id,
|
||||
"chapters": await self._list_chapters(template_id=data.template_id),
|
||||
"variables": variable_result,
|
||||
}
|
||||
|
||||
async def detail(self, template_id: int) -> dict:
|
||||
await self._validate_template(template_id=template_id)
|
||||
return {
|
||||
"template_id": template_id,
|
||||
"chapters": await self._list_chapters(template_id=template_id),
|
||||
"variables": await VariableService(self.auth).list_by_template(template_id=template_id),
|
||||
}
|
||||
|
||||
async def _save_chapters(self, template_id: int, chapters: list[TemplateChapterRuleSchema]) -> None:
|
||||
for chapter in chapters:
|
||||
chapter_data = chapter.model_dump(exclude={"id"})
|
||||
chapter_data["template_id"] = template_id
|
||||
if chapter.id:
|
||||
old_chapter = await ChapterCRUD(self.auth).get(id=chapter.id, template_id=template_id)
|
||||
if not old_chapter:
|
||||
raise CustomException(msg=f"章节规则标注不存在或不属于当前模板: {chapter.id}")
|
||||
await ChapterCRUD(self.auth).update(id=chapter.id, data=chapter_data)
|
||||
else:
|
||||
await ChapterCRUD(self.auth).create(data=chapter_data)
|
||||
|
||||
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_rules(self, chapters: list[TemplateChapterRuleSchema]) -> None:
|
||||
rule_ids = list(dict.fromkeys(chapter.rule_id for chapter in chapters))
|
||||
if not rule_ids:
|
||||
return
|
||||
rules = await RuleCRUD(self.auth).get_list(search={"id": ("in", rule_ids)})
|
||||
exists_ids = {rule.id for rule in rules}
|
||||
invalid_ids = [rule_id for rule_id in rule_ids if rule_id not in exists_ids]
|
||||
if invalid_ids:
|
||||
raise CustomException(msg=f"关联规则不存在: {invalid_ids}")
|
||||
|
||||
async def _list_chapters(self, template_id: int) -> list[dict]:
|
||||
chapters = await ChapterCRUD(self.auth).get_list(
|
||||
search={"template_id": template_id},
|
||||
order_by=[{"chapter_order": "asc"}, {"id": "asc"}],
|
||||
)
|
||||
result: list[dict] = []
|
||||
for chapter in chapters:
|
||||
rule_name = chapter.rule.name if hasattr(chapter, "rule") and chapter.rule else None
|
||||
result.append(
|
||||
{
|
||||
"id": chapter.id,
|
||||
"template_id": chapter.template_id,
|
||||
"chapter_name": chapter.chapter_name,
|
||||
"chapter_order": chapter.chapter_order,
|
||||
"rule_id": chapter.rule_id,
|
||||
"rule_name": rule_name,
|
||||
"description": chapter.description,
|
||||
}
|
||||
)
|
||||
return result
|
||||
@ -0,0 +1 @@
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.common.response import ResponseSchema, SuccessResponse
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.router_class import OperationLogRoute
|
||||
|
||||
from .schema import VariableSaveSchema
|
||||
from .service import VariableService
|
||||
|
||||
VariableRouter = APIRouter(route_class=OperationLogRoute, prefix="/variable", tags=["能源报告变量关联"])
|
||||
|
||||
|
||||
@VariableRouter.get(
|
||||
"/list",
|
||||
summary="查询模板的变量-章节关联列表",
|
||||
response_model=ResponseSchema[list[dict]],
|
||||
)
|
||||
async def get_variable_list_controller(
|
||||
template_id: Annotated[int, Query(ge=1, description="模板ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:variable:query"]))],
|
||||
) -> JSONResponse:
|
||||
result = await VariableService(auth).list_by_template(template_id=template_id)
|
||||
return SuccessResponse(data=result, msg="查询变量关联列表成功")
|
||||
|
||||
|
||||
@VariableRouter.get(
|
||||
"/chapter/{chapter_id}",
|
||||
summary="查询章节标注详情和变量勾选状态",
|
||||
response_model=ResponseSchema[dict],
|
||||
)
|
||||
async def get_variable_list_by_chapter_controller(
|
||||
chapter_id: Annotated[int, Path(ge=1, description="章节标注ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:variable:query"]))],
|
||||
) -> JSONResponse:
|
||||
result = await VariableService(auth).list_by_chapter(chapter_id=chapter_id)
|
||||
return SuccessResponse(data=result, msg="查询章节标注变量配置成功")
|
||||
|
||||
|
||||
@VariableRouter.post(
|
||||
"/save",
|
||||
summary="批量保存变量-章节关联",
|
||||
response_model=ResponseSchema[list[dict]],
|
||||
)
|
||||
async def save_variable_controller(
|
||||
data: VariableSaveSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:variable:save"]))],
|
||||
) -> JSONResponse:
|
||||
result = await VariableService(auth).save(data=data)
|
||||
return SuccessResponse(data=result, msg="保存变量关联成功")
|
||||
|
||||
|
||||
@VariableRouter.delete(
|
||||
"/delete",
|
||||
summary="删除某个变量的全部关联",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def delete_variable_controller(
|
||||
template_id: Annotated[int, Query(ge=1, description="模板ID")],
|
||||
variable_name: Annotated[str, Query(description="变量名称")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_energy:variable:delete"]))],
|
||||
) -> JSONResponse:
|
||||
await VariableService(auth).delete_by_variable(template_id=template_id, variable_name=variable_name)
|
||||
return SuccessResponse(msg="删除变量关联成功")
|
||||
@ -0,0 +1,25 @@
|
||||
from sqlalchemy import delete
|
||||
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
|
||||
from .model import EnergyReportVariableModel
|
||||
|
||||
|
||||
class VariableCRUD(CRUDBase[EnergyReportVariableModel, dict, dict]):
|
||||
"""能源报告变量关联数据层。"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
super().__init__(model=EnergyReportVariableModel, auth=auth)
|
||||
|
||||
async def hard_delete(self, ids: list[int]) -> None:
|
||||
try:
|
||||
pk = self._get_pk_col()
|
||||
sql = self._tenant_dml_where(delete(self.model).where(pk.in_(ids)))
|
||||
await self.db.execute(sql)
|
||||
await self.db.flush()
|
||||
except CustomException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f"删除变量关联失败: {e!s}")
|
||||
@ -0,0 +1,30 @@
|
||||
from sqlalchemy import ForeignKey, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.base_model import ModelMixin, TenantMixin, UserMixin
|
||||
|
||||
|
||||
class EnergyReportVariableModel(ModelMixin, TenantMixin, UserMixin):
|
||||
"""能源报告变量-章节关联模型。"""
|
||||
|
||||
__tablename__: str = "energy_report_variable"
|
||||
__table_args__ = (UniqueConstraint("tenant_id", "template_id", "variable_name", "chapter_name"), {"comment": "能源报告变量章节关联表"})
|
||||
__loader_options__: list[str] = [
|
||||
"template",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"deleted_by",
|
||||
"tenant_by",
|
||||
]
|
||||
|
||||
template_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("energy_report_template.id", ondelete="CASCADE", onupdate="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment="能源报告模板ID",
|
||||
)
|
||||
variable_name: Mapped[str] = mapped_column(String(128), nullable=False, comment="变量名称")
|
||||
chapter_name: Mapped[str] = mapped_column(String(128), nullable=False, comment="章节名称", index=True)
|
||||
|
||||
template = relationship("EnergyReportTemplateModel", lazy="selectin", foreign_keys=[template_id], uselist=False)
|
||||
@ -0,0 +1,53 @@
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
class VariableItemSchema(BaseModel):
|
||||
"""单个变量-章节关联项。"""
|
||||
|
||||
variable_name: str = Field(..., min_length=1, max_length=128, description="变量名称")
|
||||
chapter_names: list[str] = Field(default_factory=list, description="关联章节名称列表")
|
||||
|
||||
@field_validator("variable_name")
|
||||
@classmethod
|
||||
def validate_required(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if not value:
|
||||
raise ValueError("必填字段不能为空")
|
||||
return value
|
||||
|
||||
@field_validator("chapter_names")
|
||||
@classmethod
|
||||
def validate_chapter_names(cls, values: list[str]) -> list[str]:
|
||||
result: list[str] = []
|
||||
for value in values:
|
||||
chapter_name = value.strip()
|
||||
if not chapter_name:
|
||||
raise ValueError("章节名称不能为空")
|
||||
result.append(chapter_name)
|
||||
return result
|
||||
|
||||
|
||||
class VariableSaveSchema(BaseModel):
|
||||
"""批量保存变量-章节关联。"""
|
||||
|
||||
template_id: int = Field(..., ge=1, description="模板ID")
|
||||
variables: list[VariableItemSchema] = Field(default_factory=list, description="变量列表")
|
||||
|
||||
|
||||
class VariableOutSchema(BaseModel):
|
||||
"""变量-章节关联响应模型。"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(description="主键ID")
|
||||
template_id: int = Field(description="模板ID")
|
||||
variable_name: str = Field(description="变量名称")
|
||||
chapter_name: str = Field(description="章节名称")
|
||||
|
||||
|
||||
class VariableGroupOutSchema(BaseModel):
|
||||
"""按变量分组的响应模型。"""
|
||||
|
||||
variable_name: str = Field(description="变量名称")
|
||||
chapter_names: list[str] = Field(default_factory=list, description="关联章节名称列表")
|
||||
chapters: list[dict] = Field(default_factory=list, description="关联章节详情列表")
|
||||
@ -0,0 +1,135 @@
|
||||
from app.api.v1.module_energy.chapter.crud import ChapterCRUD
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
|
||||
from .crud import VariableCRUD
|
||||
from .schema import VariableGroupOutSchema, VariableSaveSchema
|
||||
|
||||
|
||||
class VariableService:
|
||||
"""能源报告变量关联服务。"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
self.auth = auth
|
||||
|
||||
async def list_by_template(self, template_id: int) -> list[dict]:
|
||||
records = await VariableCRUD(self.auth).get_list(
|
||||
search={"template_id": template_id},
|
||||
order_by=[{"variable_name": "asc"}, {"id": "asc"}],
|
||||
)
|
||||
chapter_map = await self._chapter_summary_map(template_id=template_id)
|
||||
group_map: dict[str, VariableGroupOutSchema] = {}
|
||||
for record in records:
|
||||
key = record.variable_name
|
||||
if key not in group_map:
|
||||
group_map[key] = VariableGroupOutSchema(
|
||||
variable_name=record.variable_name,
|
||||
chapter_names=[],
|
||||
chapters=[],
|
||||
)
|
||||
group_map[key].chapter_names.append(record.chapter_name)
|
||||
group_map[key].chapters.append(
|
||||
chapter_map.get(record.chapter_name, {"chapter_name": record.chapter_name})
|
||||
)
|
||||
return [group.model_dump() for group in group_map.values()]
|
||||
|
||||
async def list_by_chapter(self, chapter_id: int) -> dict:
|
||||
chapter = await ChapterCRUD(self.auth).get_or_404(id=chapter_id, msg="章节标注不存在")
|
||||
records = await VariableCRUD(self.auth).get_list(
|
||||
search={"template_id": chapter.template_id},
|
||||
order_by=[{"variable_name": "asc"}, {"id": "asc"}],
|
||||
)
|
||||
rule_name = chapter.rule.name if hasattr(chapter, "rule") and chapter.rule else None
|
||||
variable_map: dict[str, dict] = {}
|
||||
for record in records:
|
||||
if record.variable_name not in variable_map:
|
||||
variable_map[record.variable_name] = {
|
||||
"variable_name": record.variable_name,
|
||||
"chapter_names": [],
|
||||
"checked": False,
|
||||
}
|
||||
variable_map[record.variable_name]["chapter_names"].append(record.chapter_name)
|
||||
|
||||
for variable in variable_map.values():
|
||||
variable["chapter_names"] = self._unique_names(variable["chapter_names"])
|
||||
variable["checked"] = chapter.chapter_name in variable["chapter_names"]
|
||||
|
||||
return {
|
||||
"chapter_annotation": {
|
||||
"id": chapter.id,
|
||||
"chapter_name": chapter.chapter_name,
|
||||
"rule_id": chapter.rule_id,
|
||||
"rule_name": rule_name,
|
||||
},
|
||||
"variables": list(variable_map.values()),
|
||||
}
|
||||
|
||||
async def save(self, data: VariableSaveSchema) -> list[dict]:
|
||||
chapter_names = self._collect_chapter_names(data)
|
||||
await self._validate_chapters(template_id=data.template_id, chapter_names=chapter_names)
|
||||
|
||||
existing = await VariableCRUD(self.auth).get_list(search={"template_id": data.template_id})
|
||||
if existing:
|
||||
await VariableCRUD(self.auth).hard_delete(ids=[record.id for record in existing])
|
||||
|
||||
for variable in data.variables:
|
||||
for chapter_name in self._unique_names(variable.chapter_names):
|
||||
await VariableCRUD(self.auth).create(
|
||||
data={
|
||||
"template_id": data.template_id,
|
||||
"variable_name": variable.variable_name,
|
||||
"chapter_name": chapter_name,
|
||||
}
|
||||
)
|
||||
|
||||
return await self.list_by_template(template_id=data.template_id)
|
||||
|
||||
async def delete_by_variable(self, template_id: int, variable_name: str) -> None:
|
||||
records = await VariableCRUD(self.auth).get_list(
|
||||
search={"template_id": template_id, "variable_name": variable_name},
|
||||
)
|
||||
if not records:
|
||||
raise CustomException(msg="变量关联不存在")
|
||||
await VariableCRUD(self.auth).hard_delete(ids=[record.id for record in records])
|
||||
|
||||
def _collect_chapter_names(self, data: VariableSaveSchema) -> list[str]:
|
||||
chapter_names: list[str] = []
|
||||
for variable in data.variables:
|
||||
chapter_names.extend(variable.chapter_names)
|
||||
return self._unique_names(chapter_names)
|
||||
|
||||
def _unique_names(self, names: list[str]) -> list[str]:
|
||||
return list(dict.fromkeys(name.strip() for name in names if name.strip()))
|
||||
|
||||
async def _validate_chapters(self, template_id: int, chapter_names: list[str]) -> None:
|
||||
if not chapter_names:
|
||||
return
|
||||
chapters = await ChapterCRUD(self.auth).get_list(search={"template_id": template_id})
|
||||
exists_names = {chapter.chapter_name for chapter in chapters}
|
||||
invalid_names = [chapter_name for chapter_name in chapter_names if chapter_name not in exists_names]
|
||||
if invalid_names:
|
||||
raise CustomException(msg=f"章节不存在或不属于当前模板: {invalid_names}")
|
||||
|
||||
async def _chapter_summary_map(self, template_id: int) -> dict[str, dict]:
|
||||
chapters = await ChapterCRUD(self.auth).get_list(
|
||||
search={"template_id": template_id},
|
||||
order_by=[{"chapter_order": "asc"}, {"id": "asc"}],
|
||||
)
|
||||
result: dict[str, dict] = {}
|
||||
for chapter in chapters:
|
||||
if chapter.chapter_name not in result:
|
||||
result[chapter.chapter_name] = {
|
||||
"chapter_name": chapter.chapter_name,
|
||||
"annotation_ids": [],
|
||||
"rules": [],
|
||||
}
|
||||
rule_name = chapter.rule.name if hasattr(chapter, "rule") and chapter.rule else None
|
||||
result[chapter.chapter_name]["annotation_ids"].append(chapter.id)
|
||||
result[chapter.chapter_name]["rules"].append(
|
||||
{
|
||||
"chapter_annotation_id": chapter.id,
|
||||
"rule_id": chapter.rule_id,
|
||||
"rule_name": rule_name,
|
||||
}
|
||||
)
|
||||
return result
|
||||
Loading…
Reference in New Issue