|
|
from app.api.v1.module_common.file.crud import FileCRUD
|
|
|
from app.api.v1.module_common.file.service import FileService
|
|
|
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, PageResultSchema
|
|
|
from app.core.exceptions import CustomException
|
|
|
|
|
|
from .crud import EnergyReportGenerationCRUD, EnergyReportGenerationLogCRUD
|
|
|
from .schema import (
|
|
|
EnergyReportGenerationCreateSchema,
|
|
|
EnergyReportGenerationLogCreateSchema,
|
|
|
EnergyReportGenerationLogOutSchema,
|
|
|
EnergyReportGenerationLogQueryParam,
|
|
|
EnergyReportGenerationOutSchema,
|
|
|
EnergyReportGenerationQueryParam,
|
|
|
EnergyReportGenerationStatsSchema,
|
|
|
EnergyReportGenerationUpdateSchema,
|
|
|
)
|
|
|
|
|
|
ENERGY_REPORT_GENERATION_BUSINESS_TYPE = "energy_report_generation"
|
|
|
|
|
|
GENERATION_LOG_ACTION_NAMES = {
|
|
|
"create_task": "创建生成任务",
|
|
|
"start_generation": "开始生成",
|
|
|
"generation_success": "已生成",
|
|
|
"generation_failed": "生成失败",
|
|
|
}
|
|
|
|
|
|
|
|
|
class EnergyReportGenerationService:
|
|
|
"""能源报告生成任务服务。"""
|
|
|
|
|
|
def __init__(self, auth: AuthSchema) -> None:
|
|
|
self.auth = auth
|
|
|
|
|
|
async def detail(self, id: int) -> EnergyReportGenerationOutSchema:
|
|
|
generation = await EnergyReportGenerationCRUD(self.auth).get_or_404(id=id)
|
|
|
return self._to_out_schema(generation)
|
|
|
|
|
|
async def page(
|
|
|
self,
|
|
|
page_no: int,
|
|
|
page_size: int,
|
|
|
search: EnergyReportGenerationQueryParam | 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 EnergyReportGenerationCRUD(self.auth).page(
|
|
|
offset=offset,
|
|
|
limit=page_size,
|
|
|
order_by=order_by or [{"id": "desc"}],
|
|
|
search=search_dict,
|
|
|
out_schema=EnergyReportGenerationOutSchema,
|
|
|
)
|
|
|
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: EnergyReportGenerationCreateSchema) -> EnergyReportGenerationOutSchema:
|
|
|
await self._validate_template(template_id=data.template_id, report_type=data.report_type)
|
|
|
await self._validate_file(file_id=data.file_id)
|
|
|
attachment_file = await self._validate_attachment_file(
|
|
|
attachment_file_id=data.attachment_file_id,
|
|
|
file_id=data.file_id,
|
|
|
)
|
|
|
await self._validate_generator(generator_id=data.generator_id)
|
|
|
create_data = data.model_dump()
|
|
|
create_data["attachment_url"] = attachment_file.file_url if attachment_file else None
|
|
|
generation = await EnergyReportGenerationCRUD(self.auth).create(data=create_data)
|
|
|
if data.file_id:
|
|
|
await self._bind_file(file_id=data.file_id, generation_id=generation.id)
|
|
|
if data.attachment_file_id:
|
|
|
await self._bind_file(file_id=data.attachment_file_id, generation_id=generation.id)
|
|
|
await EnergyReportGenerationLogService(self.auth).create_system_log(
|
|
|
report_generation_id=generation.id,
|
|
|
generation_name=generation.generation_name,
|
|
|
action_type="create_task",
|
|
|
description=f"创建生成任务:{generation.generation_name}",
|
|
|
)
|
|
|
return self._to_out_schema(generation)
|
|
|
|
|
|
async def update(self, id: int, data: EnergyReportGenerationUpdateSchema) -> EnergyReportGenerationOutSchema:
|
|
|
old_generation = await EnergyReportGenerationCRUD(self.auth).get_or_404(id=id, msg="更新失败,能源报告生成任务不存在")
|
|
|
await self._validate_template(template_id=data.template_id, report_type=data.report_type)
|
|
|
await self._validate_file(file_id=data.file_id, current_generation_id=id)
|
|
|
if data.file_id and old_generation.attachment_file_id and old_generation.attachment_file_id == data.file_id:
|
|
|
raise CustomException(msg="更新失败,生成源文档不能使用原附件文件")
|
|
|
if old_generation.file_id and old_generation.file_id == data.attachment_file_id:
|
|
|
raise CustomException(msg="更新失败,附件不能使用原生成源文档文件")
|
|
|
attachment_file = await self._validate_attachment_file(
|
|
|
attachment_file_id=data.attachment_file_id,
|
|
|
file_id=data.file_id,
|
|
|
current_generation_id=id,
|
|
|
)
|
|
|
await self._validate_generator(generator_id=data.generator_id)
|
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
|
update_data["attachment_url"] = attachment_file.file_url if attachment_file else None
|
|
|
generation = await EnergyReportGenerationCRUD(self.auth).update(id=id, data=update_data)
|
|
|
if old_generation.file_id and old_generation.file_id != data.file_id:
|
|
|
await self._delete_file_record(file_id=old_generation.file_id)
|
|
|
if data.file_id:
|
|
|
await self._bind_file(file_id=data.file_id, generation_id=id)
|
|
|
if data.attachment_file_id:
|
|
|
await self._bind_file(file_id=data.attachment_file_id, generation_id=id)
|
|
|
if old_generation.attachment_file_id and old_generation.attachment_file_id != data.attachment_file_id:
|
|
|
await self._delete_file_record(file_id=old_generation.attachment_file_id)
|
|
|
return self._to_out_schema(generation)
|
|
|
|
|
|
async def delete(self, ids: list[int]) -> None:
|
|
|
if len(ids) < 1:
|
|
|
raise CustomException(msg="删除失败,删除对象不能为空")
|
|
|
generations = await EnergyReportGenerationCRUD(self.auth).get_list(search={"id": ("in", ids)})
|
|
|
generation_map = {generation.id: generation for generation in generations}
|
|
|
for generation_id in ids:
|
|
|
if generation_id not in generation_map:
|
|
|
raise CustomException(msg="删除失败,能源报告生成任务不存在")
|
|
|
for generation in generations:
|
|
|
if generation.file_id:
|
|
|
await self._unbind_file(file_id=generation.file_id, generation_id=generation.id)
|
|
|
await EnergyReportGenerationCRUD(self.auth).delete(ids=ids)
|
|
|
|
|
|
async def _stats(self, search_dict: dict | None = None) -> EnergyReportGenerationStatsSchema:
|
|
|
records = await EnergyReportGenerationCRUD(self.auth).get_list(search=search_dict or {})
|
|
|
return EnergyReportGenerationStatsSchema(
|
|
|
submitted_count=len(records),
|
|
|
generating_count=len([record for record in records if record.status == 1]),
|
|
|
generated_count=len([record for record in records if record.status == 2]),
|
|
|
pending_count=len([record for record in records if record.status == 0]),
|
|
|
failed_count=len([record for record in records if record.status == 3]),
|
|
|
)
|
|
|
|
|
|
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, generation) -> EnergyReportGenerationOutSchema:
|
|
|
generation_out = EnergyReportGenerationOutSchema.model_validate(generation)
|
|
|
if generation_out.template:
|
|
|
generation_out.template_name = generation_out.template.template_name
|
|
|
if generation_out.file:
|
|
|
generation_out.file_url = generation_out.file.file_url
|
|
|
generation_out.original_name = generation_out.file.original_name
|
|
|
generation_out.report_file_url = generation_out.file_url
|
|
|
generation_out.report_file_original_name = generation_out.original_name
|
|
|
if generation_out.attachment_file:
|
|
|
generation_out.attachment_url = generation_out.attachment_file.file_url
|
|
|
generation_out.attachment_original_name = generation_out.attachment_file.original_name
|
|
|
generation_out.attachment_file_url = generation_out.attachment_url
|
|
|
generation_out.attachment_file_original_name = generation_out.attachment_original_name
|
|
|
if hasattr(generation, "generator") and generation.generator:
|
|
|
generation_out.generator_name = generation.generator.name
|
|
|
return generation_out
|
|
|
|
|
|
def _fill_out_dict(self, item: dict) -> dict:
|
|
|
template_info = item.get("template") or {}
|
|
|
file_info = item.get("file") or {}
|
|
|
attachment_file_info = item.get("attachment_file") or {}
|
|
|
generator_info = item.get("generator") or {}
|
|
|
item["file"] = file_info or None
|
|
|
item["attachment_file"] = attachment_file_info or None
|
|
|
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["report_file_url"] = item["file_url"]
|
|
|
item["report_file_original_name"] = item["original_name"]
|
|
|
item["attachment_url"] = attachment_file_info.get("file_url") or item.get("attachment_url")
|
|
|
item["attachment_original_name"] = attachment_file_info.get("original_name")
|
|
|
item["attachment_file_url"] = item["attachment_url"]
|
|
|
item["attachment_file_original_name"] = item["attachment_original_name"]
|
|
|
item["generator_name"] = generator_info.get("name")
|
|
|
return item
|
|
|
|
|
|
async def _validate_template(self, template_id: int, report_type: int) -> None:
|
|
|
template = await EnergyReportTemplateCRUD(self.auth).get(id=template_id)
|
|
|
if not template:
|
|
|
raise CustomException(msg="能源报告模板不存在")
|
|
|
if template.report_type != report_type:
|
|
|
raise CustomException(msg="报告类型与模板类型不一致")
|
|
|
|
|
|
async def _validate_file(self, file_id: int | None, current_generation_id: int | None = None) -> None:
|
|
|
if file_id is None:
|
|
|
return
|
|
|
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_GENERATION_BUSINESS_TYPE:
|
|
|
raise CustomException(msg="文件已绑定到其他业务类型")
|
|
|
if file_record.business_id and file_record.business_id != current_generation_id:
|
|
|
raise CustomException(msg="文件已绑定到其他能源报告生成任务")
|
|
|
|
|
|
async def _validate_attachment_file(
|
|
|
self,
|
|
|
attachment_file_id: int | None,
|
|
|
file_id: int | None,
|
|
|
current_generation_id: int | None = None,
|
|
|
):
|
|
|
if attachment_file_id is None:
|
|
|
return None
|
|
|
if file_id and attachment_file_id == file_id:
|
|
|
raise CustomException(msg="附件文件不能和生成源文档相同")
|
|
|
file_record = await FileCRUD(self.auth).get(id=attachment_file_id)
|
|
|
if not file_record:
|
|
|
raise CustomException(msg="附件文件记录不存在")
|
|
|
if file_record.business_type and file_record.business_type != ENERGY_REPORT_GENERATION_BUSINESS_TYPE:
|
|
|
raise CustomException(msg="附件文件已绑定到其他业务类型")
|
|
|
if file_record.business_id and file_record.business_id != current_generation_id:
|
|
|
raise CustomException(msg="附件文件已绑定到其他能源报告生成任务")
|
|
|
return file_record
|
|
|
|
|
|
async def _validate_generator(self, generator_id: int | None) -> None:
|
|
|
if generator_id is None:
|
|
|
return
|
|
|
generator = await UserCRUD(self.auth).get(id=generator_id)
|
|
|
if not generator:
|
|
|
raise CustomException(msg="生成人不存在")
|
|
|
|
|
|
async def _bind_file(self, file_id: int, generation_id: int) -> None:
|
|
|
await FileCRUD(self.auth).set(
|
|
|
ids=[file_id],
|
|
|
business_type=ENERGY_REPORT_GENERATION_BUSINESS_TYPE,
|
|
|
business_id=generation_id,
|
|
|
)
|
|
|
|
|
|
async def _unbind_file(self, file_id: int, generation_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_GENERATION_BUSINESS_TYPE and file_record.business_id == generation_id:
|
|
|
await FileCRUD(self.auth).set(ids=[file_id], business_id=None)
|
|
|
|
|
|
async def _delete_file_record(self, file_id: int) -> None:
|
|
|
await FileService.delete_service(auth=self.auth, ids=[file_id], delete_object=True)
|
|
|
|
|
|
|
|
|
class EnergyReportGenerationLogService:
|
|
|
"""能源报告生成操作记录服务。"""
|
|
|
|
|
|
def __init__(self, auth: AuthSchema) -> None:
|
|
|
self.auth = auth
|
|
|
|
|
|
async def detail(self, id: int) -> EnergyReportGenerationLogOutSchema:
|
|
|
log = await EnergyReportGenerationLogCRUD(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: EnergyReportGenerationLogQueryParam | None = None,
|
|
|
order_by: list[dict[str, str]] | None = None,
|
|
|
) -> PageResultSchema[EnergyReportGenerationLogOutSchema]:
|
|
|
search_dict = vars(search) if search else {}
|
|
|
page_result = await EnergyReportGenerationLogCRUD(self.auth).page(
|
|
|
offset=(page_no - 1) * page_size,
|
|
|
limit=page_size,
|
|
|
order_by=order_by or [{"id": "desc"}],
|
|
|
search=search_dict,
|
|
|
out_schema=EnergyReportGenerationLogOutSchema,
|
|
|
)
|
|
|
page_result.items = [self._fill_out_dict(item) for item in page_result.items]
|
|
|
return page_result
|
|
|
|
|
|
async def create(self, data: EnergyReportGenerationLogCreateSchema) -> EnergyReportGenerationLogOutSchema:
|
|
|
generation = await EnergyReportGenerationCRUD(self.auth).get_or_404(
|
|
|
id=data.report_generation_id,
|
|
|
msg="新增操作记录失败,能源报告生成任务不存在",
|
|
|
)
|
|
|
log = await self.create_system_log(
|
|
|
report_generation_id=generation.id,
|
|
|
generation_name=generation.generation_name,
|
|
|
action_type=data.action_type,
|
|
|
description=data.description or self._default_description(generation.generation_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 EnergyReportGenerationLogCRUD(self.auth).delete(ids=ids)
|
|
|
|
|
|
async def create_system_log(self, report_generation_id: int, generation_name: str, action_type: str, description: str | None = None):
|
|
|
action_name = GENERATION_LOG_ACTION_NAMES.get(action_type)
|
|
|
if not action_name:
|
|
|
raise CustomException(msg="操作类型不支持")
|
|
|
return await EnergyReportGenerationLogCRUD(self.auth).create(
|
|
|
data=EnergyReportGenerationLogCreateSchema(
|
|
|
report_generation_id=report_generation_id,
|
|
|
action_type=action_type, # type: ignore[arg-type]
|
|
|
description=description,
|
|
|
generation_name=generation_name,
|
|
|
action_name=action_name,
|
|
|
),
|
|
|
)
|
|
|
|
|
|
def _to_out_schema(self, log) -> EnergyReportGenerationLogOutSchema:
|
|
|
log_out = EnergyReportGenerationLogOutSchema.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 = GENERATION_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"] = GENERATION_LOG_ACTION_NAMES.get(item.get("action_type"), item.get("action_name"))
|
|
|
return item
|
|
|
|
|
|
def _default_description(self, generation_name: str, action_type: str) -> str:
|
|
|
action_name = GENERATION_LOG_ACTION_NAMES.get(action_type, action_type)
|
|
|
return f"{action_name}:{generation_name}"
|