feat: add document type CRUD with inline rule set binding
- GET/POST /api/document-types, GET/PUT/DELETE /api/document-types/{id}
- DocumentTypeItemVO extended with description, entryModuleId,
isEnabled, ruleSetIds
- Create/Update DTOs accept ruleSetIds array for automatic
leaudit_rule_type_bindings sync (full replace on update)
- Soft delete cascades to rule_type_bindings
This commit is contained in:
@@ -22,7 +22,9 @@ from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import (
|
||||
DocumentHistoryVersionVO,
|
||||
DocumentListItemVO,
|
||||
DocumentListPageVO,
|
||||
DocumentTypeCreateDTO,
|
||||
DocumentTypeItemVO,
|
||||
DocumentTypeUpdateDTO,
|
||||
DocumentUploadVO,
|
||||
)
|
||||
from fastapi_modules.fastapi_leaudit.models import LeauditDocument, LeauditDocumentFile
|
||||
@@ -459,38 +461,188 @@ class DocumentServiceImpl(IDocumentService):
|
||||
async def ListDocumentTypes(self, Ids: list[int] | None = None) -> list[DocumentTypeItemVO]:
|
||||
"""获取文档类型列表。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
if Ids:
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, name, code
|
||||
FROM leaudit_document_types
|
||||
WHERE deleted_at IS NULL AND id = ANY(:ids)
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
"""
|
||||
),
|
||||
{"ids": Ids},
|
||||
)
|
||||
).mappings().all()
|
||||
else:
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, name, code
|
||||
FROM leaudit_document_types
|
||||
WHERE deleted_at IS NULL
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
"""
|
||||
)
|
||||
)
|
||||
).mappings().all()
|
||||
rows, bindingsMap = await self._queryDocumentTypes(Session, Ids)
|
||||
return [
|
||||
DocumentTypeItemVO(id=int(r["id"]), name=str(r["name"] or ""), code=str(r["code"] or ""))
|
||||
DocumentTypeItemVO(
|
||||
id=int(r["id"]), name=str(r["name"] or ""), code=str(r["code"] or ""),
|
||||
description=r.get("description"), entryModuleId=r.get("entry_module_id"),
|
||||
isEnabled=bool(r.get("is_enabled", True)),
|
||||
ruleSetIds=bindingsMap.get(int(r["id"]), []),
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
async def GetDocumentType(self, Id: int) -> DocumentTypeItemVO:
|
||||
"""获取文档类型详情。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
rows, bindingsMap = await self._queryDocumentTypes(Session, [Id])
|
||||
if not rows:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "文档类型不存在")
|
||||
r = rows[0]
|
||||
return DocumentTypeItemVO(
|
||||
id=int(r["id"]), name=str(r["name"] or ""), code=str(r["code"] or ""),
|
||||
description=r.get("description"), entryModuleId=r.get("entry_module_id"),
|
||||
isEnabled=bool(r.get("is_enabled", True)),
|
||||
ruleSetIds=bindingsMap.get(int(r["id"]), []),
|
||||
)
|
||||
|
||||
async def CreateDocumentType(self, Body: DocumentTypeCreateDTO) -> DocumentTypeItemVO:
|
||||
"""创建文档类型。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
existing = (
|
||||
await Session.execute(
|
||||
text("SELECT 1 FROM leaudit_document_types WHERE code = :code AND deleted_at IS NULL LIMIT 1"),
|
||||
{"code": Body.code.strip()},
|
||||
)
|
||||
).first()
|
||||
if existing:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_409_CONFLICT, f"文档类型编码 {Body.code} 已存在")
|
||||
|
||||
row = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO leaudit_document_types (code, name, description, entry_module_id, is_enabled, sort_order, created_at, updated_at)
|
||||
VALUES (:code, :name, :description, :entry_module_id, :is_enabled, :sort_order, NOW(), NOW())
|
||||
RETURNING id
|
||||
"""
|
||||
),
|
||||
{
|
||||
"code": Body.code.strip(),
|
||||
"name": Body.name.strip(),
|
||||
"description": Body.description.strip() or None,
|
||||
"entry_module_id": Body.entryModuleId,
|
||||
"is_enabled": Body.isEnabled,
|
||||
"sort_order": Body.sortOrder,
|
||||
},
|
||||
)
|
||||
).scalar_one()
|
||||
await self._syncRuleBindings(Session, int(row), Body.ruleSetIds, "default")
|
||||
await Session.commit()
|
||||
|
||||
return await self.GetDocumentType(int(row))
|
||||
|
||||
async def UpdateDocumentType(self, Id: int, Body: DocumentTypeUpdateDTO) -> DocumentTypeItemVO:
|
||||
"""更新文档类型。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
current = (
|
||||
await Session.execute(
|
||||
text("SELECT id FROM leaudit_document_types WHERE id = :id AND deleted_at IS NULL"),
|
||||
{"id": Id},
|
||||
)
|
||||
).first()
|
||||
if not current:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "文档类型不存在")
|
||||
|
||||
sets: list[str] = []
|
||||
params: dict[str, object] = {"id": Id}
|
||||
if Body.name is not None:
|
||||
sets.append("name = :name")
|
||||
params["name"] = Body.name.strip()
|
||||
if Body.description is not None:
|
||||
sets.append("description = :description")
|
||||
params["description"] = Body.description.strip() or None
|
||||
if Body.entryModuleId is not None:
|
||||
sets.append("entry_module_id = :entry_module_id")
|
||||
params["entry_module_id"] = Body.entryModuleId
|
||||
if Body.isEnabled is not None:
|
||||
sets.append("is_enabled = :is_enabled")
|
||||
params["is_enabled"] = Body.isEnabled
|
||||
if 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_document_types SET {', '.join(sets)} WHERE id = :id"), params)
|
||||
|
||||
if Body.ruleSetIds is not None:
|
||||
await self._syncRuleBindings(Session, Id, Body.ruleSetIds, "default")
|
||||
|
||||
await Session.commit()
|
||||
return await self.GetDocumentType(Id)
|
||||
|
||||
async def DeleteDocumentType(self, Id: int) -> None:
|
||||
"""软删除文档类型。"""
|
||||
async with GetAsyncSession() as Session:
|
||||
current = (
|
||||
await Session.execute(
|
||||
text("SELECT 1 FROM leaudit_document_types WHERE id = :id AND deleted_at IS NULL"),
|
||||
{"id": Id},
|
||||
)
|
||||
).first()
|
||||
if not current:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "文档类型不存在")
|
||||
await Session.execute(text("UPDATE leaudit_document_types SET deleted_at = NOW() WHERE id = :id"), {"id": Id})
|
||||
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 _queryDocumentTypes(self, Session, Ids: list[int] | None = None):
|
||||
"""查询文档类型及其规则绑定。"""
|
||||
if Ids:
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, code, name, description, entry_module_id, is_enabled, sort_order
|
||||
FROM leaudit_document_types
|
||||
WHERE deleted_at IS NULL AND id = ANY(:ids)
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
"""
|
||||
),
|
||||
{"ids": Ids},
|
||||
)
|
||||
).mappings().all()
|
||||
else:
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, code, name, description, entry_module_id, is_enabled, sort_order
|
||||
FROM leaudit_document_types
|
||||
WHERE deleted_at IS NULL
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
"""
|
||||
)
|
||||
)
|
||||
).mappings().all()
|
||||
|
||||
allIds = [int(r["id"]) for r in rows]
|
||||
bindingsMap: dict[int, list[int]] = {i: [] for i in allIds}
|
||||
if allIds:
|
||||
bindingRows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT doc_type_id, rule_set_id
|
||||
FROM leaudit_rule_type_bindings
|
||||
WHERE doc_type_id = ANY(:ids) AND deleted_at IS NULL AND is_active = true
|
||||
ORDER BY priority DESC
|
||||
"""
|
||||
),
|
||||
{"ids": allIds},
|
||||
)
|
||||
).fetchall()
|
||||
for b in bindingRows:
|
||||
bindingsMap.setdefault(int(b[0]), []).append(int(b[1]))
|
||||
return rows, bindingsMap
|
||||
|
||||
async def _syncRuleBindings(self, Session, DocTypeId: int, RuleSetIds: list[int], Region: str = "default") -> None:
|
||||
"""全量替换规则绑定。"""
|
||||
await Session.execute(
|
||||
text("UPDATE leaudit_rule_type_bindings SET deleted_at = NOW() WHERE doc_type_id = :id AND deleted_at IS NULL"),
|
||||
{"id": DocTypeId},
|
||||
)
|
||||
for idx, ruleSetId in enumerate(RuleSetIds):
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO leaudit_rule_type_bindings (doc_type_id, rule_set_id, binding_mode, priority, region, is_active, created_at, updated_at)
|
||||
VALUES (:doc_type_id, :rule_set_id, 'explicit', :priority, :region, true, NOW(), NOW())
|
||||
"""
|
||||
),
|
||||
{"doc_type_id": DocTypeId, "rule_set_id": ruleSetId, "priority": 100 - idx, "region": Region},
|
||||
)
|
||||
|
||||
|
||||
async def _find_latest_version_candidate(
|
||||
session,
|
||||
|
||||
Reference in New Issue
Block a user