| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- """共享附件及其有效挂载关系只读查询。"""
- from __future__ import annotations
- from typing import Mapping
- from sqlalchemy import String, cast, func, or_, select
- from dms.common.enums import AttachmentType, DocumentType
- from dms.common.errors import ResourceNotFoundError
- from dms.common.pagination import page_result
- from dms.common.response import serialize_id
- from dms.common.time_range import parse_updated_range
- from dms.extensions import db
- from dms.models import AttachmentBinding, Document
- from dms.security.auth_context import AuthContext, get_auth_context
- from dms.services.authorization_service import (
- attachment_allowed_actions,
- evaluate_plan_access,
- require_attachment_access,
- )
- from dms.services.document_query_service import (
- DOCUMENT_SORTS,
- _enum,
- _iso,
- _page,
- _record_view,
- _sort,
- parse_string_id,
- )
- def mounted_plan_count(attachment_id: int) -> int:
- return (
- db.session.scalar(
- select(func.count(AttachmentBinding.id)).where(
- AttachmentBinding.attachment_document_id == attachment_id,
- AttachmentBinding.is_deleted.is_(False),
- )
- )
- or 0
- )
- def attachment_summary(
- attachment: Document,
- context: AuthContext,
- *,
- plan_access=None,
- ) -> dict[str, object]:
- return {
- "id": serialize_id(attachment.id),
- "documentName": attachment.document_name,
- "summary": attachment.summary,
- "documentType": attachment.document_type,
- "status": attachment.document_status,
- "securityLevel": attachment.security_level,
- "visibilityType": attachment.visibility_type,
- "visibilitySummary": "全部已登录用户",
- "attachmentType": attachment.attachment_type,
- "categoryId": None,
- "categoryName": None,
- "categoryPath": None,
- "parentDocumentId": None,
- "rootDocumentId": None,
- "tags": attachment.tags or [],
- "fileExtension": attachment.file_extension,
- "childCount": attachment.child_count,
- "attachmentCount": attachment.attachment_count,
- "viewCount": attachment.view_count,
- "downloadCount": attachment.download_count,
- "createdByName": attachment.created_by_name,
- "createdAt": _iso(attachment.created_at),
- "updatedAt": _iso(attachment.updated_at),
- "rowVersion": attachment.row_version,
- "allowedActions": attachment_allowed_actions(context, plan_access),
- "mountedPlanCount": mounted_plan_count(attachment.id),
- }
- def attachment_detail(
- attachment: Document,
- context: AuthContext,
- ) -> dict[str, object]:
- result = attachment_summary(attachment, context)
- result.update(
- {
- "originalFileName": attachment.original_file_name,
- "mimeType": attachment.mime_type,
- "fileSize": attachment.file_size,
- "fileHash": attachment.file_hash,
- "permissionSummary": {
- "organizationCount": 0,
- "userCount": 0,
- "inheritedFromMainPlan": False,
- },
- "contentText": attachment.content_text,
- }
- )
- return result
- def _attachment(attachment_id: int) -> Document:
- attachment = db.session.scalar(
- select(Document).where(
- Document.id == attachment_id,
- Document.document_type == DocumentType.ATTACHMENT.value,
- Document.is_deleted.is_(False),
- )
- )
- if attachment is None:
- raise ResourceNotFoundError("共享附件不存在")
- return attachment
- def _filtered_attachments(params: Mapping[str, str]):
- statement = select(Document).where(
- Document.document_type == DocumentType.ATTACHMENT.value,
- Document.is_deleted.is_(False),
- )
- keyword = params.get("keyword")
- if keyword and keyword.strip():
- escaped = keyword.strip().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
- pattern = f"%{escaped}%"
- statement = statement.where(
- or_(
- Document.document_name.like(pattern, escape="\\"),
- Document.summary.like(pattern, escape="\\"),
- Document.search_text.like(pattern, escape="\\"),
- cast(Document.tags, String).like(pattern, escape="\\"),
- )
- )
- attachment_type = _enum(
- params.get("attachmentType"), AttachmentType, "attachmentType"
- )
- if attachment_type:
- statement = statement.where(Document.attachment_type == attachment_type)
- extension = params.get("fileExtension")
- if extension:
- statement = statement.where(
- Document.file_extension == extension.strip().lower().lstrip(".")
- )
- updated_from, updated_to = parse_updated_range(params)
- if updated_from:
- statement = statement.where(Document.updated_at >= updated_from)
- if updated_to:
- statement = statement.where(Document.updated_at <= updated_to)
- order, _ = _sort(params, allowed=DOCUMENT_SORTS)
- return statement.order_by(order, Document.id.asc())
- def list_attachments(params: Mapping[str, str]) -> dict[str, object]:
- context = get_auth_context()
- page = _page(params)
- attachments = db.session.scalars(_filtered_attachments(params)).all()
- total = len(attachments)
- selected = attachments[page.offset : page.offset + page.page_size]
- return page_result(
- [attachment_summary(item, context) for item in selected],
- page=page.page,
- page_size=page.page_size,
- total=total,
- )
- def get_attachment(attachment_id: int) -> dict[str, object]:
- context = get_auth_context()
- attachment = _attachment(attachment_id)
- can_view, can_download = require_attachment_access(
- attachment, context, download=False
- )
- result = attachment_detail(attachment, context)
- if context.role_code.value == "USER":
- result["allowedActions"] = ["VIEW"] + (
- ["DOWNLOAD"] if can_download else []
- )
- result["viewCount"] = _record_view(attachment)
- return result
- def list_attachment_main_plans(attachment_id: int) -> dict[str, object]:
- context = get_auth_context()
- _attachment(attachment_id)
- rows = db.session.execute(
- select(AttachmentBinding, Document)
- .join(Document, Document.id == AttachmentBinding.main_document_id)
- .where(
- AttachmentBinding.attachment_document_id == attachment_id,
- AttachmentBinding.is_deleted.is_(False),
- Document.document_type == DocumentType.MAIN.value,
- Document.is_deleted.is_(False),
- )
- .order_by(Document.document_name.asc(), Document.id.asc())
- ).all()
- items: list[dict[str, object]] = []
- for _, main in rows:
- if not evaluate_plan_access(main, context).allowed:
- continue
- items.append(
- {
- "id": serialize_id(main.id),
- "documentName": main.document_name,
- "categoryName": main.category_name,
- "securityLevel": main.security_level,
- }
- )
- return {"items": items, "total": len(items)}
- def list_main_plan_attachments(
- main_document_id: int,
- params: Mapping[str, str],
- ) -> dict[str, object]:
- from dms.common.errors import MainPlanNotFoundError
- from dms.services.document_query_service import _require_plan_access
- context = get_auth_context()
- main = db.session.scalar(
- select(Document).where(
- Document.id == main_document_id,
- Document.document_type == DocumentType.MAIN.value,
- Document.is_deleted.is_(False),
- )
- )
- if main is None:
- raise MainPlanNotFoundError()
- main_access = _require_plan_access(main, context)
- page = _page(params)
- statement = (
- select(AttachmentBinding, Document)
- .join(Document, Document.id == AttachmentBinding.attachment_document_id)
- .where(
- AttachmentBinding.main_document_id == main.id,
- AttachmentBinding.is_deleted.is_(False),
- Document.document_type == DocumentType.ATTACHMENT.value,
- Document.is_deleted.is_(False),
- )
- )
- keyword = params.get("keyword")
- if keyword and keyword.strip():
- escaped = keyword.strip().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
- pattern = f"%{escaped}%"
- statement = statement.where(
- or_(
- Document.document_name.like(pattern, escape="\\"),
- Document.summary.like(pattern, escape="\\"),
- Document.search_text.like(pattern, escape="\\"),
- )
- )
- attachment_type = _enum(
- params.get("attachmentType"), AttachmentType, "attachmentType"
- )
- if attachment_type:
- statement = statement.where(Document.attachment_type == attachment_type)
- updated_from, updated_to = parse_updated_range(params)
- if updated_from:
- statement = statement.where(Document.updated_at >= updated_from)
- if updated_to:
- statement = statement.where(Document.updated_at <= updated_to)
- rows = db.session.execute(
- statement.order_by(
- AttachmentBinding.sort_no.asc(),
- AttachmentBinding.id.asc(),
- )
- ).all()
- total = len(rows)
- selected = rows[page.offset : page.offset + page.page_size]
- items: list[dict[str, object]] = []
- for binding, attachment in selected:
- item = attachment_summary(
- attachment, context, plan_access=main_access
- )
- item.update(
- {
- "bindingId": serialize_id(binding.id),
- "bindingSortNo": binding.sort_no,
- }
- )
- items.append(item)
- return page_result(
- items,
- page=page.page,
- page_size=page.page_size,
- total=total,
- )
- __all__ = [
- "get_attachment",
- "list_attachment_main_plans",
- "list_attachments",
- "list_main_plan_attachments",
- "parse_string_id",
- ]
|