Files
ai-agent-admin/backend-fastapi/online_dev/report_manager/version_service.py
T

290 lines
11 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""报表版本管理服务"""
import copy
import json
import logging
from typing import Any, Dict, List, Optional
from sqlalchemy import select, update, func, and_
from sqlalchemy.ext.asyncio import AsyncSession
from online_dev.report_manager.model import ReportTemplate, ReportVersion, ReportDataset
from online_dev.report_manager.enums import ReportVersionState
from online_dev.report_manager.exceptions import ReportServiceException
from online_dev.report_manager.constants import default_snapshot, default_cells
from online_dev.report_manager.dataset_bridge import ReportDatasetBridge
logger = logging.getLogger(__name__)
class ReportVersionService:
@staticmethod
async def get(db: AsyncSession, version_id: str) -> ReportVersion:
stmt = select(ReportVersion).where(
ReportVersion.id == version_id,
ReportVersion.is_deleted == False,
)
ver = (await db.execute(stmt)).scalar_one_or_none()
if not ver:
raise ReportServiceException(f"版本不存在: {version_id}")
return ver
@staticmethod
async def list_by_template(db: AsyncSession, template_id: str) -> List[ReportVersion]:
stmt = (
select(ReportVersion)
.where(
ReportVersion.template_id == template_id,
ReportVersion.is_deleted == False,
)
.order_by(ReportVersion.version.desc())
)
return list((await db.execute(stmt)).scalars().all())
@staticmethod
async def get_active(db: AsyncSession, template_id: str) -> Optional[ReportVersion]:
stmt = select(ReportVersion).where(
ReportVersion.template_id == template_id,
ReportVersion.state == ReportVersionState.ACTIVE,
ReportVersion.is_deleted == False,
)
return (await db.execute(stmt)).scalar_one_or_none()
@staticmethod
async def get_designing_or_latest(db: AsyncSession, template_id: str) -> Optional[ReportVersion]:
stmt = select(ReportVersion).where(
ReportVersion.template_id == template_id,
ReportVersion.is_deleted == False,
).order_by(
ReportVersion.state.asc(),
ReportVersion.version.desc(),
)
versions = list((await db.execute(stmt)).scalars().all())
for v in versions:
if v.state == ReportVersionState.DESIGNING:
return v
return versions[0] if versions else None
@staticmethod
def _parse_json_field(value: Any, default: Any) -> Any:
if value is None:
return default
if isinstance(value, str):
try:
return json.loads(value)
except json.JSONDecodeError:
return default
return value
@staticmethod
async def save(db: AsyncSession, data: Dict[str, Any], user_id: str = None) -> Dict[str, Any]:
from online_dev.report_manager.service import ReportService
template_id = data.get("id")
tpl = await ReportService.get(db, template_id)
version_id = data.get("version_id")
save_type = data.get("type", 0)
snapshot = ReportVersionService._parse_json_field(data.get("snapshot"), default_snapshot())
cells = ReportVersionService._parse_json_field(data.get("cells"), default_cells())
query_list = ReportVersionService._parse_json_field(data.get("query_list"), [])
sort_list = ReportVersionService._parse_json_field(data.get("sort_list"), [])
column_list = ReportVersionService._parse_json_field(data.get("column_list"), [])
fence_list = ReportVersionService._parse_json_field(data.get("fence_list"), [])
convert_config = ReportVersionService._parse_json_field(data.get("convert_config"), {})
data_set_list = data.get("data_set_list") or []
if version_id:
version = await ReportVersionService.get(db, version_id)
if version.template_id != template_id:
raise ReportServiceException("版本与模板不匹配")
if version.state != ReportVersionState.DESIGNING:
raise ReportServiceException("只能编辑设计中版本")
else:
designing = await ReportVersionService.get_designing_or_latest(db, template_id)
if designing and designing.state == ReportVersionState.DESIGNING:
version = designing
else:
max_ver = (
await db.execute(
select(func.max(ReportVersion.version)).where(
ReportVersion.template_id == template_id,
ReportVersion.is_deleted == False,
)
)
).scalar() or 0
version = ReportVersion(
template_id=template_id,
version=max_ver + 1,
state=ReportVersionState.DESIGNING,
snapshot=default_snapshot(),
cells=default_cells(),
sys_creator_id=user_id,
sys_modifier_id=user_id,
)
db.add(version)
await db.flush()
version.snapshot = snapshot
version.cells = cells
version.query_list = query_list
version.sort_list = sort_list
version.column_list = column_list
version.fence_list = fence_list
version.convert_config = convert_config
version.sys_modifier_id = user_id
await ReportDatasetBridge.sync_datasets(db, version.id, data_set_list)
if save_type == 1:
stmt = update(ReportVersion).where(
ReportVersion.template_id == template_id,
ReportVersion.state == ReportVersionState.ACTIVE,
ReportVersion.is_deleted == False,
).values(state=ReportVersionState.ARCHIVED)
await db.execute(stmt)
version.state = ReportVersionState.ACTIVE
await db.flush()
max_ver = (
await db.execute(
select(func.max(ReportVersion.version)).where(
ReportVersion.template_id == template_id,
ReportVersion.is_deleted == False,
)
)
).scalar() or version.version
new_designing = ReportVersion(
template_id=template_id,
version=max_ver + 1,
state=ReportVersionState.DESIGNING,
snapshot=copy.deepcopy(version.snapshot),
cells=copy.deepcopy(version.cells),
query_list=copy.deepcopy(version.query_list),
sort_list=copy.deepcopy(version.sort_list),
column_list=copy.deepcopy(version.column_list),
fence_list=copy.deepcopy(version.fence_list),
convert_config=copy.deepcopy(version.convert_config),
sys_creator_id=user_id,
sys_modifier_id=user_id,
)
db.add(new_designing)
await db.flush()
await ReportDatasetBridge.copy_datasets(db, version.id, new_designing.id)
version = new_designing
else:
version.state = ReportVersionState.DESIGNING
await db.commit()
await db.refresh(version)
return {
"template_id": template_id,
"version_id": version.id,
"state": int(version.state),
}
@staticmethod
async def get_version_detail(db: AsyncSession, version_id: str) -> Dict[str, Any]:
version = await ReportVersionService.get(db, version_id)
datasets = await ReportDatasetBridge.list_by_version(db, version_id)
return {
"version": version,
"datasets": datasets,
}
@staticmethod
async def delete_version(db: AsyncSession, version_id: str) -> bool:
version = await ReportVersionService.get(db, version_id)
if version.state in (ReportVersionState.ACTIVE, ReportVersionState.ARCHIVED):
raise ReportServiceException("不能删除启用中或已归档版本")
count_stmt = select(func.count(ReportVersion.id)).where(
ReportVersion.template_id == version.template_id,
ReportVersion.is_deleted == False,
)
if (await db.execute(count_stmt)).scalar() <= 1:
raise ReportServiceException("不能删除最后一个版本")
version.is_deleted = True
await ReportDatasetBridge.delete_by_version(db, version_id)
await db.commit()
return True
@staticmethod
async def copy_version_content(
db: AsyncSession,
src_version_id: str,
new_template_id: str,
user_id: str = None,
) -> None:
src = await ReportVersionService.get(db, src_version_id)
new_ver = (
await db.execute(
select(ReportVersion).where(
ReportVersion.template_id == new_template_id,
ReportVersion.is_deleted == False,
)
)
).scalar_one_or_none()
if not new_ver:
return
new_ver.snapshot = src.snapshot
new_ver.cells = src.cells
new_ver.query_list = src.query_list
new_ver.sort_list = src.sort_list
new_ver.column_list = src.column_list
new_ver.fence_list = src.fence_list
new_ver.convert_config = src.convert_config
await ReportDatasetBridge.copy_datasets(db, src_version_id, new_ver.id)
await db.commit()
@staticmethod
async def duplicate_version(
db: AsyncSession,
src_version_id: str,
user_id: str = None,
) -> ReportVersion:
"""复制版本为新的「设计中」版本(同模板)。"""
src = await ReportVersionService.get(db, src_version_id)
template_id = src.template_id
await db.execute(
update(ReportVersion).where(
ReportVersion.template_id == template_id,
ReportVersion.state == ReportVersionState.DESIGNING,
ReportVersion.is_deleted == False,
).values(state=ReportVersionState.ARCHIVED)
)
max_ver = (
await db.execute(
select(func.max(ReportVersion.version)).where(
ReportVersion.template_id == template_id,
ReportVersion.is_deleted == False,
)
)
).scalar() or 0
new_ver = ReportVersion(
template_id=template_id,
version=max_ver + 1,
state=ReportVersionState.DESIGNING,
snapshot=copy.deepcopy(src.snapshot),
cells=copy.deepcopy(src.cells),
query_list=copy.deepcopy(src.query_list),
sort_list=copy.deepcopy(src.sort_list),
column_list=copy.deepcopy(src.column_list),
fence_list=copy.deepcopy(src.fence_list),
convert_config=copy.deepcopy(src.convert_config),
sys_creator_id=user_id,
sys_modifier_id=user_id,
)
db.add(new_ver)
await db.flush()
await ReportDatasetBridge.copy_datasets(db, src.id, new_ver.id)
await db.commit()
await db.refresh(new_ver)
return new_ver