feat: add document type root management
This commit is contained in:
@@ -18,6 +18,9 @@ from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import (
|
||||
DocumentUpdateDTO,
|
||||
DocumentTypeCreateDTO,
|
||||
DocumentTypeItemVO,
|
||||
DocumentTypeRootCreateDTO,
|
||||
DocumentTypeRootItemVO,
|
||||
DocumentTypeRootUpdateDTO,
|
||||
DocumentTypeUpdateDTO,
|
||||
DocumentUploadVO,
|
||||
)
|
||||
@@ -261,6 +264,32 @@ class DocumentController(BaseController):
|
||||
await self.DocumentService.DeleteDocumentType(Id=TypeId)
|
||||
return Result.success(message="文档类型已删除")
|
||||
|
||||
@self.router.get("/v3/document-type-roots", response_model=Result[list[DocumentTypeRootItemVO]])
|
||||
async def ListDocumentTypeRoots(
|
||||
entry_module_id: int | None = Query(None, description="按入口模块过滤一级大类"),
|
||||
):
|
||||
"""获取一级文档类型(业务大类)列表。"""
|
||||
Data = await self.DocumentService.ListDocumentTypeRoots(EntryModuleId=entry_module_id)
|
||||
return Result.success(data=Data)
|
||||
|
||||
@self.router.get("/v3/document-type-roots/{RootId}", response_model=Result[DocumentTypeRootItemVO])
|
||||
async def GetDocumentTypeRoot(RootId: int):
|
||||
"""获取一级文档类型(业务大类)详情。"""
|
||||
Data = await self.DocumentService.GetDocumentTypeRoot(Id=RootId)
|
||||
return Result.success(data=Data)
|
||||
|
||||
@self.router.post("/v3/document-type-roots", response_model=Result[DocumentTypeRootItemVO])
|
||||
async def CreateDocumentTypeRoot(Body: DocumentTypeRootCreateDTO):
|
||||
"""创建一级文档类型(业务大类)。"""
|
||||
Data = await self.DocumentService.CreateDocumentTypeRoot(Body=Body)
|
||||
return Result.success(data=Data, message="一级文档类型创建成功")
|
||||
|
||||
@self.router.put("/v3/document-type-roots/{RootId}", response_model=Result[DocumentTypeRootItemVO])
|
||||
async def UpdateDocumentTypeRoot(RootId: int, Body: DocumentTypeRootUpdateDTO):
|
||||
"""更新一级文档类型(业务大类)。"""
|
||||
Data = await self.DocumentService.UpdateDocumentTypeRoot(Id=RootId, Body=Body)
|
||||
return Result.success(data=Data, message="一级文档类型更新成功")
|
||||
|
||||
@self.router.get("/v2/system/queue/status", response_model=Result[QueueStatusVO])
|
||||
async def GetQueueStatus():
|
||||
"""获取文档处理队列状态。"""
|
||||
|
||||
@@ -125,6 +125,42 @@ class DocumentTypeItemVO(BaseModel):
|
||||
ruleSetIds: list[int] = Field(default_factory=list, description="关联的规则集ID")
|
||||
|
||||
|
||||
class DocumentTypeRootItemVO(BaseModel):
|
||||
"""一级文档类型(业务大类)列表项。"""
|
||||
|
||||
id: int = Field(..., description="一级分组ID")
|
||||
name: str = Field(..., description="一级分组名称")
|
||||
code: str = Field(..., description="一级分组编码")
|
||||
description: str | None = Field(None, description="描述")
|
||||
entryModuleId: int | None = Field(None, description="入口模块ID")
|
||||
entryModuleName: str | None = Field(None, description="入口模块名称")
|
||||
isEnabled: bool = Field(True, description="是否启用")
|
||||
childGroupCount: int = Field(0, description="下属二级分组数量")
|
||||
ruleSetCount: int = Field(0, description="汇总规则集数量")
|
||||
ruleSetIds: list[int] = Field(default_factory=list, description="汇总规则集ID")
|
||||
|
||||
|
||||
class DocumentTypeRootCreateDTO(BaseModel):
|
||||
"""创建一级文档类型(业务大类)。"""
|
||||
|
||||
code: str = Field(..., description="一级分组编码")
|
||||
name: str = Field(..., description="一级分组名称")
|
||||
description: str = Field("", description="描述")
|
||||
entryModuleId: int | None = Field(None, description="入口模块ID")
|
||||
isEnabled: bool = Field(True, description="是否启用")
|
||||
sortOrder: int = Field(0, description="排序")
|
||||
|
||||
|
||||
class DocumentTypeRootUpdateDTO(BaseModel):
|
||||
"""更新一级文档类型(业务大类)。"""
|
||||
|
||||
name: str | None = Field(None, description="一级分组名称")
|
||||
description: str | None = Field(None, description="描述")
|
||||
entryModuleId: int | None = Field(None, description="入口模块ID")
|
||||
isEnabled: bool | None = Field(None, description="是否启用")
|
||||
sortOrder: int | None = Field(None, description="排序")
|
||||
|
||||
|
||||
class DocumentTypeCreateDTO(BaseModel):
|
||||
"""文档类型创建请求。"""
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import Any, Dict, Optional
|
||||
import fitz
|
||||
from fastapi_common.fastapi_common_logger import logger
|
||||
from leaudit.converters import doc2pdf
|
||||
from sqlalchemy import select
|
||||
|
||||
from fastapi_admin.celery_app import celery_app
|
||||
from fastapi_admin.config import (
|
||||
|
||||
@@ -6,6 +6,9 @@ from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import (
|
||||
DocumentDetailVO,
|
||||
DocumentListPageVO,
|
||||
DocumentStatusItemVO,
|
||||
DocumentTypeRootCreateDTO,
|
||||
DocumentTypeRootItemVO,
|
||||
DocumentTypeRootUpdateDTO,
|
||||
DocumentUpdateDTO,
|
||||
DocumentTypeCreateDTO,
|
||||
DocumentTypeItemVO,
|
||||
@@ -138,3 +141,23 @@ class IDocumentService(ABC):
|
||||
async def DeleteDocumentType(self, Id: int) -> None:
|
||||
"""删除文档类型(软删除)。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def ListDocumentTypeRoots(self, EntryModuleId: int | None = None) -> list[DocumentTypeRootItemVO]:
|
||||
"""获取一级文档类型(业务大类)列表。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def GetDocumentTypeRoot(self, Id: int) -> DocumentTypeRootItemVO:
|
||||
"""获取单个一级文档类型(业务大类)。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def CreateDocumentTypeRoot(self, Body: DocumentTypeRootCreateDTO) -> DocumentTypeRootItemVO:
|
||||
"""创建一级文档类型(业务大类)。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def UpdateDocumentTypeRoot(self, Id: int, Body: DocumentTypeRootUpdateDTO) -> DocumentTypeRootItemVO:
|
||||
"""更新一级文档类型(业务大类)。"""
|
||||
...
|
||||
|
||||
@@ -26,6 +26,9 @@ from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import (
|
||||
DocumentListItemVO,
|
||||
DocumentListPageVO,
|
||||
DocumentStatusItemVO,
|
||||
DocumentTypeRootCreateDTO,
|
||||
DocumentTypeRootItemVO,
|
||||
DocumentTypeRootUpdateDTO,
|
||||
DocumentUpdateDTO,
|
||||
DocumentTypeCreateDTO,
|
||||
DocumentTypeItemVO,
|
||||
@@ -1185,6 +1188,97 @@ class DocumentServiceImpl(IDocumentService):
|
||||
await Session.execute(text("UPDATE leaudit_rule_type_bindings SET deleted_at = NOW() WHERE doc_type_id = :id AND deleted_at IS NULL"), {"id": Id})
|
||||
await Session.commit()
|
||||
|
||||
async def ListDocumentTypeRoots(self, EntryModuleId: int | None = None) -> list[DocumentTypeRootItemVO]:
|
||||
"""获取一级文档类型(业务大类)列表。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
rows = await self._queryDocumentTypeRoots(Session, EntryModuleId=EntryModuleId)
|
||||
return [self._toDocumentTypeRootVo(row) for row in rows]
|
||||
|
||||
async def GetDocumentTypeRoot(self, Id: int) -> DocumentTypeRootItemVO:
|
||||
"""获取一级文档类型(业务大类)详情。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
rows = await self._queryDocumentTypeRoots(Session, Ids=[Id])
|
||||
if not rows:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "一级文档类型不存在")
|
||||
return self._toDocumentTypeRootVo(rows[0])
|
||||
|
||||
async def CreateDocumentTypeRoot(self, Body: DocumentTypeRootCreateDTO) -> DocumentTypeRootItemVO:
|
||||
"""创建一级文档类型(业务大类)。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
await self._ensureDocumentTypeRootCodeUnique(Session, Body.code.strip(), None)
|
||||
root_id = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO leaudit_evaluation_point_groups
|
||||
(pid, name, code, description, document_type_id, entry_module_id, sort_order, is_enabled, created_at, updated_at)
|
||||
VALUES
|
||||
(0, :name, :code, :description, NULL, :entry_module_id, :sort_order, :is_enabled, NOW(), NOW())
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"name": Body.name.strip(),
|
||||
"code": Body.code.strip(),
|
||||
"description": Body.description.strip() or None,
|
||||
"entry_module_id": Body.entryModuleId,
|
||||
"sort_order": Body.sortOrder,
|
||||
"is_enabled": Body.isEnabled,
|
||||
},
|
||||
)
|
||||
).scalar_one()
|
||||
await Session.commit()
|
||||
return await self.GetDocumentTypeRoot(int(root_id))
|
||||
|
||||
async def UpdateDocumentTypeRoot(self, Id: int, Body: DocumentTypeRootUpdateDTO) -> DocumentTypeRootItemVO:
|
||||
"""更新一级文档类型(业务大类)。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
current = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM leaudit_evaluation_point_groups
|
||||
WHERE id = :id
|
||||
AND deleted_at IS NULL
|
||||
AND COALESCE(pid, 0) = 0
|
||||
"""
|
||||
),
|
||||
{"id": Id},
|
||||
)
|
||||
).first()
|
||||
if not current:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "一级文档类型不存在")
|
||||
|
||||
providedFields = set(getattr(Body, "model_fields_set", set()))
|
||||
sets: list[str] = []
|
||||
params: dict[str, object] = {"id": Id}
|
||||
if "name" in providedFields and Body.name is not None:
|
||||
sets.append("name = :name")
|
||||
params["name"] = Body.name.strip()
|
||||
if "description" in providedFields:
|
||||
sets.append("description = :description")
|
||||
params["description"] = Body.description.strip() if Body.description is not None else None
|
||||
if params["description"] == "":
|
||||
params["description"] = None
|
||||
if "entryModuleId" in providedFields:
|
||||
sets.append("entry_module_id = :entry_module_id")
|
||||
params["entry_module_id"] = Body.entryModuleId
|
||||
if "isEnabled" in providedFields and Body.isEnabled is not None:
|
||||
sets.append("is_enabled = :is_enabled")
|
||||
params["is_enabled"] = Body.isEnabled
|
||||
if "sortOrder" in providedFields and Body.sortOrder is not None:
|
||||
sets.append("sort_order = :sort_order")
|
||||
params["sort_order"] = Body.sortOrder
|
||||
if sets:
|
||||
sets.append("updated_at = NOW()")
|
||||
await Session.execute(
|
||||
text(f"UPDATE leaudit_evaluation_point_groups SET {', '.join(sets)} WHERE id = :id"),
|
||||
params,
|
||||
)
|
||||
await Session.commit()
|
||||
return await self.GetDocumentTypeRoot(Id)
|
||||
|
||||
async def _queryDocumentTypes(self, Session, Ids: list[int] | None = None, EntryModuleId: int | None = None):
|
||||
"""查询文档类型及其规则绑定。"""
|
||||
if Ids:
|
||||
@@ -1250,6 +1344,93 @@ class DocumentServiceImpl(IDocumentService):
|
||||
bindingsMap.setdefault(int(b[0]), []).append(int(b[1]))
|
||||
return rows, bindingsMap
|
||||
|
||||
async def _queryDocumentTypeRoots(self, Session, Ids: list[int] | None = None, EntryModuleId: int | None = None):
|
||||
filters = ["g.deleted_at IS NULL", "COALESCE(g.pid, 0) = 0"]
|
||||
params: dict[str, object] = {}
|
||||
if Ids:
|
||||
filters.append("g.id = ANY(:ids)")
|
||||
params["ids"] = Ids
|
||||
if EntryModuleId is not None:
|
||||
filters.append("g.entry_module_id = :entry_module_id")
|
||||
params["entry_module_id"] = EntryModuleId
|
||||
|
||||
where_clause = " AND ".join(filters)
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
f"""
|
||||
SELECT
|
||||
g.id,
|
||||
g.name,
|
||||
g.code,
|
||||
g.description,
|
||||
g.entry_module_id,
|
||||
em.name AS entry_module_name,
|
||||
g.is_enabled,
|
||||
COALESCE(child_stats.child_group_count, 0) AS child_group_count,
|
||||
COALESCE(rule_stats.rule_set_count, 0) AS rule_set_count,
|
||||
COALESCE(rule_stats.rule_set_ids, ARRAY[]::bigint[]) AS rule_set_ids
|
||||
FROM leaudit_evaluation_point_groups g
|
||||
LEFT JOIN leaudit_entry_modules em ON em.id = g.entry_module_id
|
||||
LEFT JOIN (
|
||||
SELECT pid, COUNT(*)::int AS child_group_count
|
||||
FROM leaudit_evaluation_point_groups
|
||||
WHERE deleted_at IS NULL AND COALESCE(pid, 0) <> 0
|
||||
GROUP BY pid
|
||||
) child_stats ON child_stats.pid = g.id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
child.pid,
|
||||
COUNT(DISTINCT rgb.rule_set_id)::int AS rule_set_count,
|
||||
ARRAY_AGG(DISTINCT rgb.rule_set_id) FILTER (WHERE rgb.rule_set_id IS NOT NULL) AS rule_set_ids
|
||||
FROM leaudit_evaluation_point_groups child
|
||||
LEFT JOIN leaudit_rule_group_bindings rgb
|
||||
ON rgb.group_id = child.id
|
||||
AND rgb.deleted_at IS NULL
|
||||
WHERE child.deleted_at IS NULL
|
||||
AND COALESCE(child.pid, 0) <> 0
|
||||
GROUP BY child.pid
|
||||
) rule_stats ON rule_stats.pid = g.id
|
||||
WHERE {where_clause}
|
||||
ORDER BY COALESCE(g.sort_order, 0) ASC, g.id ASC
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
).mappings().all()
|
||||
return rows
|
||||
|
||||
def _toDocumentTypeRootVo(self, row) -> DocumentTypeRootItemVO:
|
||||
rule_set_ids = [int(item) for item in (row.get("rule_set_ids") or []) if item is not None]
|
||||
return DocumentTypeRootItemVO(
|
||||
id=int(row["id"]),
|
||||
name=str(row["name"] or ""),
|
||||
code=str(row["code"] or ""),
|
||||
description=row.get("description"),
|
||||
entryModuleId=int(row["entry_module_id"]) if row.get("entry_module_id") is not None else None,
|
||||
entryModuleName=row.get("entry_module_name"),
|
||||
isEnabled=bool(row.get("is_enabled", True)),
|
||||
childGroupCount=int(row.get("child_group_count") or 0),
|
||||
ruleSetCount=int(row.get("rule_set_count") or 0),
|
||||
ruleSetIds=rule_set_ids,
|
||||
)
|
||||
|
||||
async def _ensureDocumentTypeRootCodeUnique(self, Session, Code: str, CurrentId: int | None) -> None:
|
||||
sql = """
|
||||
SELECT 1
|
||||
FROM leaudit_evaluation_point_groups
|
||||
WHERE deleted_at IS NULL
|
||||
AND COALESCE(pid, 0) = 0
|
||||
AND code = :code
|
||||
"""
|
||||
params: dict[str, object] = {"code": Code}
|
||||
if CurrentId is not None:
|
||||
sql += " AND id <> :current_id"
|
||||
params["current_id"] = CurrentId
|
||||
exists = (await Session.execute(text(sql), params)).first()
|
||||
if exists:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_409_CONFLICT, f"一级文档类型编码 {Code} 已存在")
|
||||
|
||||
async def _loadDocumentColumns(self, Session) -> set[str]:
|
||||
"""读取 leaudit_documents 当前真实列,兼容不同环境的渐进式字段上线。"""
|
||||
rows = (
|
||||
|
||||
Reference in New Issue
Block a user