feat: support contract template replace and delete
This commit is contained in:
@@ -15,6 +15,7 @@ from fastapi_modules.fastapi_leaudit.domian.Dto.contractTemplateDto import (
|
||||
ContractTemplateCreateDTO,
|
||||
ContractTemplateListQueryDTO,
|
||||
ContractTemplateSearchQueryDTO,
|
||||
ContractTemplateUpdateDTO,
|
||||
)
|
||||
from fastapi_modules.fastapi_leaudit.domian.vo.contractTemplateVo import (
|
||||
ContractTemplateCategoryVO,
|
||||
@@ -225,6 +226,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
(sql,) = self._bind_expanding(sql, params)
|
||||
row = (await session.execute(sql, params)).mappings().first()
|
||||
|
||||
if not row:
|
||||
@@ -423,26 +425,215 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
raise LeauditException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, "合同模板创建成功但详情读取失败")
|
||||
return ContractTemplateCreateVO(**detail.model_dump())
|
||||
|
||||
async def UpdateTemplate(
|
||||
self,
|
||||
TemplateId: int,
|
||||
Body: ContractTemplateUpdateDTO,
|
||||
File: UploadFile,
|
||||
PdfFile: UploadFile | None,
|
||||
CurrentUserId: int,
|
||||
) -> ContractTemplateCreateVO:
|
||||
if File is None or not File.filename:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "替换模板主文件不能为空")
|
||||
|
||||
fileContent = await File.read()
|
||||
if not fileContent:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "替换模板主文件内容不能为空")
|
||||
|
||||
normalizedCode = (Body.template_code or "").strip()
|
||||
normalizedTitle = (Body.title or "").strip()
|
||||
if not normalizedCode:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "模板编码不能为空")
|
||||
if not normalizedTitle:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "模板标题不能为空")
|
||||
|
||||
fileExt = Path(File.filename).suffix.lstrip(".").lower()
|
||||
if fileExt not in {"doc", "docx", "pdf"}:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "当前仅支持上传 DOC、DOCX、PDF 模板")
|
||||
mimeType = File.content_type or mimetypes.guess_type(File.filename)[0] or "application/octet-stream"
|
||||
|
||||
pdfContent: bytes | None = None
|
||||
pdfMimeType: str | None = None
|
||||
pdfFileName: str | None = None
|
||||
if PdfFile and PdfFile.filename:
|
||||
pdfContent = await PdfFile.read()
|
||||
if not pdfContent:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "预览 PDF 文件内容不能为空")
|
||||
pdfExt = Path(PdfFile.filename).suffix.lstrip(".").lower()
|
||||
if pdfExt != "pdf":
|
||||
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "预览文件仅支持 PDF")
|
||||
pdfMimeType = PdfFile.content_type or "application/pdf"
|
||||
pdfFileName = PdfFile.filename
|
||||
|
||||
async with GetAsyncSession() as session:
|
||||
await self._ensureContractTemplateSchema(session)
|
||||
currentUser = await self._getCurrentUserContext(CurrentUserId, session)
|
||||
resolvedTenantCode, resolvedTenantName, resolvedRegion = self._resolve_upload_scope(currentUser, Body.region, Body.tenant_code)
|
||||
|
||||
params: dict[str, Any] = {"template_id": TemplateId}
|
||||
scope_filters = self._build_template_scope_filters(currentUser, params, requestedRegion=None, writable=True)
|
||||
existing_sql = text(
|
||||
f"""
|
||||
SELECT id
|
||||
FROM contract_templates t
|
||||
WHERE t.id = :template_id
|
||||
AND t.deleted_at IS NULL
|
||||
AND {' AND '.join(scope_filters)}
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
(existing_sql,) = self._bind_expanding(existing_sql, params)
|
||||
existingRow = (await session.execute(existing_sql, params)).mappings().first()
|
||||
if not existingRow:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "合同模板不存在或无权更新")
|
||||
|
||||
categoryRow = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, name
|
||||
FROM contract_categories
|
||||
WHERE id = :category_id
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"category_id": Body.category_id},
|
||||
)
|
||||
).mappings().first()
|
||||
if not categoryRow:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "合同模板分类不存在")
|
||||
|
||||
duplicateRow = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id
|
||||
FROM contract_templates
|
||||
WHERE (
|
||||
tenant_code = :tenant_code
|
||||
OR (
|
||||
(tenant_code IS NULL OR BTRIM(tenant_code) = '')
|
||||
AND region = :region
|
||||
)
|
||||
)
|
||||
AND template_code = :template_code
|
||||
AND id <> :template_id
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{
|
||||
"tenant_code": resolvedTenantCode,
|
||||
"region": resolvedRegion,
|
||||
"template_code": normalizedCode,
|
||||
"template_id": TemplateId,
|
||||
},
|
||||
)
|
||||
).mappings().first()
|
||||
if duplicateRow:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_409_CONFLICT, f"当前租户已存在模板编码 {normalizedCode}")
|
||||
|
||||
categoryName = str(categoryRow["name"] or "未分类")
|
||||
objectKey = OssPathUtils.BuildContractTemplateKey(
|
||||
Region=resolvedRegion,
|
||||
CategoryName=categoryName,
|
||||
TemplateCode=normalizedCode,
|
||||
FileRole="source",
|
||||
FileName=File.filename,
|
||||
)
|
||||
filePath = await self.OssService.UploadBytes(
|
||||
ObjectKey=objectKey,
|
||||
Content=fileContent,
|
||||
ContentType=mimeType,
|
||||
)
|
||||
|
||||
pdfPath: str | None = None
|
||||
if pdfContent is not None and pdfFileName:
|
||||
pdfObjectKey = OssPathUtils.BuildContractTemplateKey(
|
||||
Region=resolvedRegion,
|
||||
CategoryName=categoryName,
|
||||
TemplateCode=normalizedCode,
|
||||
FileRole="preview",
|
||||
FileName=pdfFileName,
|
||||
)
|
||||
pdfPath = await self.OssService.UploadBytes(
|
||||
ObjectKey=pdfObjectKey,
|
||||
Content=pdfContent,
|
||||
ContentType=pdfMimeType or "application/pdf",
|
||||
)
|
||||
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE contract_templates
|
||||
SET template_code = :template_code,
|
||||
title = :title,
|
||||
category_id = :category_id,
|
||||
tenant_code = :tenant_code,
|
||||
tenant_name = :tenant_name,
|
||||
region = :region,
|
||||
description = :description,
|
||||
file_path = :file_path,
|
||||
pdf_file_path = :pdf_file_path,
|
||||
file_format = :file_format,
|
||||
original_file_name = :original_file_name,
|
||||
mime_type = :mime_type,
|
||||
file_size = :file_size,
|
||||
pdf_file_size = :pdf_file_size,
|
||||
is_featured = :is_featured,
|
||||
updated_by = :updated_by,
|
||||
updated_at = NOW()
|
||||
WHERE id = :template_id
|
||||
AND deleted_at IS NULL
|
||||
"""
|
||||
),
|
||||
{
|
||||
"template_id": TemplateId,
|
||||
"template_code": normalizedCode,
|
||||
"title": normalizedTitle,
|
||||
"category_id": Body.category_id,
|
||||
"tenant_code": resolvedTenantCode,
|
||||
"tenant_name": resolvedTenantName,
|
||||
"region": resolvedRegion,
|
||||
"description": (Body.description or "").strip() or None,
|
||||
"file_path": filePath,
|
||||
"pdf_file_path": pdfPath,
|
||||
"file_format": fileExt,
|
||||
"original_file_name": File.filename,
|
||||
"mime_type": mimeType,
|
||||
"file_size": len(fileContent),
|
||||
"pdf_file_size": len(pdfContent) if pdfContent is not None else None,
|
||||
"is_featured": Body.is_featured,
|
||||
"updated_by": CurrentUserId,
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
detail = await self.GetTemplateDetail(TemplateId, CurrentUserId)
|
||||
if not detail:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, "合同模板更新成功但详情读取失败")
|
||||
return ContractTemplateCreateVO(**detail.model_dump())
|
||||
|
||||
async def DeleteTemplate(self, TemplateId: int, CurrentUserId: int) -> None:
|
||||
async with GetAsyncSession() as session:
|
||||
await self._ensureContractTemplateSchema(session)
|
||||
currentUser = await self._getCurrentUserContext(CurrentUserId, session)
|
||||
params: dict[str, Any] = {"template_id": TemplateId}
|
||||
scope_filters = self._build_template_scope_filters(currentUser, params, requestedRegion=None, writable=True)
|
||||
sql = text(
|
||||
f"""
|
||||
SELECT id
|
||||
FROM contract_templates t
|
||||
WHERE t.id = :template_id
|
||||
AND t.deleted_at IS NULL
|
||||
AND {' AND '.join(scope_filters)}
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
(sql,) = self._bind_expanding(sql, params)
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
f"""
|
||||
SELECT id
|
||||
FROM contract_templates t
|
||||
WHERE t.id = :template_id
|
||||
AND t.deleted_at IS NULL
|
||||
AND {' AND '.join(scope_filters)}
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
await session.execute(sql, params)
|
||||
).mappings().first()
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "合同模板不存在或无权删除")
|
||||
@@ -859,8 +1050,8 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
current_tenant_code = str(currentUser.get("tenant_code") or "").strip() or None
|
||||
current_tenant_name = str(currentUser.get("tenant_name") or "").strip() or None
|
||||
current_region = str(currentUser["tenant_scope_value"] or currentUser["area"] or "").strip()
|
||||
if not currentUser.get("is_area_admin"):
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前仅支持租户管理员上传合同模板")
|
||||
if not currentUser.get("can_manage"):
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前仅允许有权限的管理员上传合同模板")
|
||||
if not current_region:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前租户管理员账号未配置所属租户,无法上传合同模板")
|
||||
if requested_tenant_code and not current_tenant_code:
|
||||
|
||||
Reference in New Issue
Block a user