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)