feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user