You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
13 KiB
Django/Jinja

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- coding: utf-8 -*-
import io
import pandas as pd
from fastapi import UploadFile
from app.core.base_schema import AuthSchema, BatchSetAvailable
from app.core.exceptions import CustomException
from app.core.logger import logger
from app.utils.excel_util import ExcelUtil
from .crud import {{ class_name }}CRUD
from .schema import (
{{ class_name }}CreateSchema,
{{ class_name }}UpdateSchema,
{{ class_name }}OutSchema,
{{ class_name }}QueryParam
)
class {{ class_name }}Service:
"""
{{ function_name }}服务层
"""
@classmethod
async def detail_service(cls, auth: AuthSchema, id: int) -> dict:
"""
详情
参数:
- auth: AuthSchema - 认证信息
- id: int - 数据ID
返回:
- dict - 数据详情
"""
obj = await {{ class_name }}CRUD(auth).get(id=id)
if not obj:
raise CustomException(msg="该数据不存在")
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
@classmethod
async def list_service(cls, auth: AuthSchema, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]:
"""
列表查询
参数:
- auth: AuthSchema - 认证信息
- search: {{ class_name }}QueryParam | None - 查询参数
- order_by: list[dict] | None - 排序参数
返回:
- list[dict] - 数据列表
"""
search_dict = search.__dict__ if search else None
obj_list = await {{ class_name }}CRUD(auth).list(search=search_dict, order_by=order_by)
return [{{ class_name }}OutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> dict:
"""
分页查询(数据库分页)
参数:
- auth: AuthSchema - 认证信息
- page_no: int - 页码
- page_size: int - 每页数量
- search: {{ class_name }}QueryParam | None - 查询参数
- order_by: list[dict] | None - 排序参数
返回:
- dict - 分页查询结果
"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{'{{ pk_column_name }}': 'asc'}]
offset = (page_no - 1) * page_size
result = await {{ class_name }}CRUD(auth).page(
offset=offset,
limit=page_size,
order_by=order_by_list,
search=search_dict,
out_schema={{ class_name }}OutSchema
)
return result
@classmethod
async def create_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> dict:
"""
创建
参数:
- auth: AuthSchema - 认证信息
- data: {{ class_name }}CreateSchema - 创建数据
返回:
- dict - 创建结果
"""
{% for column in columns %}
{% if column.is_unique and column.column_name != 'uuid' %}
obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
if obj:
raise CustomException(msg='创建失败,{{ column.column_comment }}已存在')
{% endif %}
{% endfor %}
obj = await {{ class_name }}CRUD(auth).create(data=data)
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
@classmethod
async def update_service(cls, auth: AuthSchema, id: int, data: {{ class_name }}UpdateSchema) -> dict:
"""
更新
参数:
- auth: AuthSchema - 认证信息
- id: int - 数据ID
- data: {{ class_name }}UpdateSchema - 更新数据
返回:
- dict - 更新结果
"""
# 检查数据是否存在
obj = await {{ class_name }}CRUD(auth).get(id=id)
if not obj:
raise CustomException(msg='更新失败,该数据不存在')
# 检查唯一性约束
{% for column in columns %}
{% if column.is_unique and column.column_name != 'uuid' %}
exist_obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
if exist_obj and getattr(exist_obj, '{{ pk_column_name }}') != id:
raise CustomException(msg='更新失败,{{ column.column_comment }}重复')
{% endif %}
{% endfor %}
obj = await {{ class_name }}CRUD(auth).update(id=id, data=data)
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
"""
删除
参数:
- auth: AuthSchema - 认证信息
- ids: list[int] - 数据ID列表
返回:
- None
"""
if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids:
obj = await {{ class_name }}CRUD(auth).get(id=id)
if not obj:
raise CustomException(msg=f'删除失败ID为{id}的数据不存在')
await {{ class_name }}CRUD(auth).delete(ids=ids)
@classmethod
async def set_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
"""
批量设置状态
参数:
- auth: AuthSchema - 认证信息
- data: BatchSetAvailable - 批量设置状态数据
返回:
- None
"""
await {{ class_name }}CRUD(auth).set(ids=data.ids, status=data.status)
@staticmethod
def batch_export_service(obj_list: list[dict]) -> bytes:
"""
批量导出
参数:
- obj_list: list[dict] - 数据列表
返回:
- bytes - 导出的Excel文件内容
"""
mapping_dict = {
{% for column in columns %}
{% if column.column_name not in ['uuid', 'tenant_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
'{{ column.column_name }}': '{{ column.column_comment }}',
{% endif %}
{% endfor %}
}
# 复制数据并转换状态
data = obj_list.copy()
for item in data:
# 处理状态
item["status"] = "启用" if item.get("status") == 0 else "停用"
# 处理创建者
creator_info = item.get("created_id")
if isinstance(creator_info, dict):
item["created_id"] = creator_info.get("name", "未知")
else:
item["created_id"] = "未知"
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
@classmethod
async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
"""
批量导入
参数:
- auth: AuthSchema - 认证信息
- file: UploadFile - 上传的Excel文件
- update_support: bool - 是否支持更新存在数据
返回:
- str - 导入结果信息
"""
header_dict = {
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
'{{ column.column_comment }}': '{{ column.column_name }}',
{% endif %}
{% endfor %}
}
try:
# 读取Excel文件
contents = await file.read()
df = pd.read_excel(io.BytesIO(contents))
await file.close()
if df.empty:
raise CustomException(msg="导入文件为空")
# 检查表头是否完整
missing_headers = [header for header in header_dict.keys() if header not in df.columns]
if missing_headers:
raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}")
# 重命名列名
df.rename(columns=header_dict, inplace=True)
# 验证必填字段(非主键且不允许为空的列)
{% for column in columns %}
{% if column.is_nullable is false and column.is_pk is false and column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
errors = []
missing_rows = df[df['{{ column.column_name }}'].isnull()].index.tolist()
if missing_rows:
field_name = [k for k,v in header_dict.items() if v == '{{ column.column_name }}'][0]
rows_str = "、".join([str(i+1) for i in missing_rows])
errors.append(f"{field_name}不能为空,第{rows_str}行")
if errors:
raise CustomException(msg=f"导入失败,以下行缺少必要字段:\n{'; '.join(errors)}")
{% endif %}
{% endfor %}
error_msgs = []
success_count = 0
count = 0
for _index, row in df.iterrows():
count += 1
try:
data = {
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
"{{ column.column_name }}": row['{{ column.column_name }}'],
{% endif %}
{% endfor %}
}
# 使用CreateSchema做校验后入库
create_schema = {{ class_name }}CreateSchema.model_validate(data)
# 检查唯一性约束
{% for column in columns %}
{% if column.is_unique and column.column_name not in ['uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
exists_obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=create_schema.{{ column.column_name }})
if exists_obj:
if update_support:
await {{ class_name }}CRUD(auth).update(id=getattr(exists_obj, '{{ pk_column_name }}'), data=create_schema)
success_count += 1
else:
error_msgs.append(f"第{count}行: {{ column.column_comment }} {create_schema.{{ column.column_name }}} 已存在")
continue
{% endif %}
{% endfor %}
await {{ class_name }}CRUD(auth).create(data=create_schema)
success_count += 1
except Exception as e:
error_msgs.append(f"第{count}行: {str(e)}")
continue
result = f"成功导入 {success_count} 条数据"
if error_msgs:
result += "\n错误信息:\n" + "\n".join(error_msgs)
return result
except Exception as e:
logger.error(f"批量导入失败: {str(e)}")
raise CustomException(msg=f"导入失败: {str(e)}")
@staticmethod
def import_template_download_service() -> bytes:
"""
下载导入模板
返回:
- bytes - Excel文件的二进制数据
"""
header_list = [
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
'{{ column.column_comment }}',
{% endif %}
{% endfor %}
]
selector_header_list = []
option_list = []
{% for column in columns %}
{% if (column.html_type == 'select' or column.html_type == 'radio') and column.dict_type and column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] and column.column_name != pk_column_name %}
selector_header_list.append('{{ column.column_comment }}')
option_list.append({'{{ column.column_comment }}': []})
{% endif %}
{% endfor %}
return ExcelUtil.get_excel_template(
header_list=header_list,
selector_header_list=selector_header_list,
option_list=option_list
)