File size: 5,232 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | """
合同文件夹绑定服务
处理合同与本地文件夹绑定的业务逻辑
"""
from __future__ import annotations
import logging
from typing import Any, ClassVar, cast
from apps.contracts.models import Contract, ContractFolderBinding
from apps.core.filesystem import (
FolderBindingCrudService,
FolderBrowsePolicy,
FolderFilesystemService,
FolderPathValidator,
)
from apps.core.protocols import IDocumentTemplateBindingService
from .contract_subdir_path_resolver import ContractSubdirPathResolver
logger = logging.getLogger("apps.contracts")
class FolderBindingService(FolderBindingCrudService):
"""
文件夹绑定服务
职责:
1. 管理合同与文件夹的绑定关系
2. 验证文件夹路径格式
3. 处理文件保存到绑定文件夹
4. 管理子目录结构
5. 根据文书模板绑定配置确定保存路径
"""
# 默认子目录配置(仅在没有文书模板绑定配置时使用)
DEFAULT_SUBDIRS: ClassVar = {
"contract_documents": "合同文书",
"supplementary_agreements": "补充协议",
"other_files": "其他文件",
}
binding_model = ContractFolderBinding
owner_model = Contract
owner_rel_field: str = "contract"
owner_id_field: str = "contract_id"
owner_label: str = "合同"
def __init__(
self,
document_template_binding_service: IDocumentTemplateBindingService | None = None,
filesystem_service: FolderFilesystemService | None = None,
path_validator: FolderPathValidator | None = None,
browse_policy: FolderBrowsePolicy | None = None,
) -> None:
super().__init__(
filesystem_service=filesystem_service,
path_validator=path_validator,
browse_policy=browse_policy,
roots_setting_name="FOLDER_BROWSE_ROOTS",
fallback_roots_setting_name="CONTRACT_FOLDER_BROWSE_ROOTS",
)
self._subdir_path_resolver = ContractSubdirPathResolver(
template_binding_service=document_template_binding_service,
)
def _resolve_subdir_path(self, *, owner_type: str, subdir_key: str) -> str | None:
return cast(str | None, self._subdir_path_resolver.resolve(case_type=owner_type, subdir_key=subdir_key))
def _sanitize_file_name(self, file_name: str) -> str:
"""兼容旧测试入口:委托给统一路径校验器。"""
return cast(str, self.path_validator.sanitize_file_name(file_name))
def _normalize_relative_path(self, relative_path: str) -> str:
"""兼容旧测试入口:委托给统一路径校验器。"""
return cast(str, self.path_validator.normalize_relative_path(relative_path))
# 为了保持向后兼容,提供 contract_id 参数的便捷方法
def create_binding_for_contract(self, contract_id: int, folder_path: str) -> Any:
"""为合同创建文件夹绑定(便捷方法)"""
return self.create_binding(owner_id=contract_id, folder_path=folder_path)
def update_binding_for_contract(self, contract_id: int, folder_path: str) -> Any:
"""为合同更新文件夹绑定(便捷方法)"""
return self.update_binding(owner_id=contract_id, folder_path=folder_path)
def delete_binding_for_contract(self, contract_id: int) -> bool:
"""删除合同的文件夹绑定(便捷方法)"""
return cast(bool, self.delete_binding(owner_id=contract_id))
def get_binding_for_contract(self, contract_id: int) -> ContractFolderBinding | None:
"""获取合同的文件夹绑定(便捷方法)"""
binding = self.get_binding(owner_id=contract_id)
return binding
def save_file_for_contract(
self, contract_id: int, file_content: bytes, file_name: str, subdir_key: str = "contract_documents"
) -> str | None:
"""为合同保存文件到绑定文件夹(便捷方法)"""
return self.save_file_to_bound_folder(
owner_id=contract_id,
file_content=file_content,
file_name=file_name,
subdir_key=subdir_key,
)
def extract_zip_for_contract(self, contract_id: int, zip_content: bytes) -> str | None:
"""为合同解压 ZIP 到绑定文件夹(便捷方法)"""
return self.extract_zip_to_bound_folder(contract_id=contract_id, zip_content=zip_content)
def save_file_to_bound_folder(
self,
owner_id: int,
file_content: bytes,
file_name: str,
subdir_key: str = "contract_documents",
) -> str | None:
"""保存文件到绑定文件夹(实现 IContractFolderBindingService 协议)"""
return cast(
str | None,
super().save_file_to_bound_folder(
owner_id=owner_id,
file_content=file_content,
file_name=file_name,
subdir_key=subdir_key,
),
)
def extract_zip_to_bound_folder(self, contract_id: int, zip_content: bytes) -> str | None:
"""解压 ZIP 到绑定文件夹(实现 IContractFolderBindingService 协议)"""
return cast(str | None, super().extract_zip_to_bound_folder(owner_id=contract_id, zip_content=zip_content))
|