package com.dms.mapper;
import com.dms.model.DocumentEntity;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/** doc_document 表访问(按阶段逐步扩展)。 */
public interface DocumentMapper {
/** 路径级联:同步挂载文档的 organization_name 快照(row_version+1)。 */
@Update("UPDATE doc_document SET organization_name = #{orgName}, "
+ "updated_by = #{actorId}, updated_at = #{now}, "
+ "row_version = row_version + 1 "
+ "WHERE organization_id = #{organizationId} AND is_deleted = 0")
int syncOrganizationName(@Param("organizationId") long organizationId,
@Param("orgName") String orgName,
@Param("actorId") Long actorId,
@Param("now") LocalDateTime now);
// ------------------------------------------------------------ 基础读取(阶段5.2/5.3)
@Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0")
DocumentEntity findByIdNotDeleted(@Param("id") long id);
/** 子方案权限源:root 主案(有效 MAIN)。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'MAIN' "
+ "AND is_deleted = 0")
DocumentEntity findActiveMainById(@Param("id") long id);
/** 分类父节点检查:直挂有效主案数(_valid_parent / update_category)。 */
@Select("SELECT COUNT(*) FROM doc_document WHERE category_id = #{categoryId} "
+ "AND document_type = 'MAIN' AND is_deleted = 0")
long countActiveMainByCategory(@Param("categoryId") long categoryId);
/** 分类删除占用:直挂方案(MAIN+SUB_PLAN)数。 */
@Select("SELECT COUNT(*) FROM doc_document WHERE category_id = #{categoryId} "
+ "AND is_deleted = 0 AND document_type IN ('MAIN', 'SUB_PLAN')")
long countActivePlansByCategory(@Param("categoryId") long categoryId);
/** 分类路径级联:同步直挂方案快照(rv+1),返回受影响行数。 */
@Update("UPDATE doc_document SET category_name = #{categoryName}, "
+ "category_path = #{categoryPath}, updated_by = #{actorId}, "
+ "updated_at = #{now}, row_version = row_version + 1 "
+ "WHERE category_id = #{categoryId} AND is_deleted = 0 "
+ "AND document_type IN ('MAIN', 'SUB_PLAN')")
int syncCategorySnapshot(@Param("categoryId") long categoryId,
@Param("categoryName") String categoryName,
@Param("categoryPath") String categoryPath,
@Param("actorId") Long actorId,
@Param("now") LocalDateTime now);
// ------------------------------------------------------------ 密码锁(阶段5.3)
/** 密码锁维护目标:锁定有效主案行。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'MAIN' "
+ "AND is_deleted = 0 FOR UPDATE")
DocumentEntity findActiveMainForUpdate(@Param("id") long id);
/** 密码锁整体写入(哈希 + 7 勾选位 + rv+1)。 */
@Update("UPDATE doc_document SET password_hash = #{passwordHash}, "
+ "protect_view = #{protectView}, protect_download = #{protectDownload}, "
+ "protect_edit = #{protectEdit}, protect_delete = #{protectDelete}, "
+ "protect_config_permission = #{protectConfigPermission}, "
+ "protect_bind_attachment = #{protectBindAttachment}, "
+ "protect_unbind_attachment = #{protectUnbindAttachment}, "
+ "updated_by = #{updatedBy}, updated_at = #{updatedAt}, "
+ "row_version = row_version + 1 WHERE id = #{id}")
int updatePasswordLock(DocumentEntity document);
// ------------------------------------------------------------ 只读查询(阶段5.4)
/**
* 主案/子方案通用动态查询,对齐 document_query_service 的组合过滤:
* types 必填;categoryIds/parentDocumentId/parentIds/titlePattern/contentPattern/
* securityLevels/orgPattern/tags(配tagsMatch)/securityLevel/status/updated范围可选;
* orderBy 为服务层白名单校验后的排序子串,缺省不排序(对齐 Python 无 order_by 场景)。
*/
@Select("")
List listByFilters(Map filters);
/** 对齐 _active_plan:详情目标(MAIN/SUB_PLAN)。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0 "
+ "AND document_type IN ('MAIN', 'SUB_PLAN')")
DocumentEntity findActivePlanById(@Param("id") long id);
/** _record_view 锁定行。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0 FOR UPDATE")
DocumentEntity findNotDeletedForUpdate(@Param("id") long id);
/** _record_view 查看计数自增。 */
@Update("UPDATE doc_document SET view_count = view_count + 1 WHERE id = #{id}")
int incrementViewCount(@Param("id") long id);
// ------------------------------------------------------------ 写操作(阶段5.5)
/** 新建方案/附件(密码锁/计数等由库默认承担,对齐 Python session.add)。 */
@Insert("INSERT INTO doc_document (document_name, summary, document_type, "
+ "document_status, security_level, visibility_type, attachment_type, "
+ "category_id, category_name, category_path, organization_id, "
+ "organization_name, parent_document_id, root_document_id, tags, "
+ "original_file_name, file_relative_path, file_extension, mime_type, "
+ "file_size, file_hash, sort_no, content_text, content_extract_status, "
+ "content_extract_error, content_extracted_at, search_text, created_by, updated_by, "
+ "created_by_name, updated_by_name) VALUES (#{documentName}, #{summary}, "
+ "#{documentType}, #{documentStatus}, #{securityLevel}, #{visibilityType}, "
+ "#{attachmentType}, #{categoryId}, #{categoryName}, #{categoryPath}, "
+ "#{organizationId}, #{organizationName}, #{parentDocumentId}, "
+ "#{rootDocumentId}, #{tags}, #{originalFileName}, #{fileRelativePath}, "
+ "#{fileExtension}, #{mimeType}, #{fileSize}, #{fileHash}, #{sortNo}, "
+ "#{contentText}, #{contentExtractStatus}, #{contentExtractError}, "
+ "#{contentExtractedAt}, "
+ "#{searchText}, #{createdBy}, #{updatedBy}, #{createdByName}, "
+ "#{updatedByName})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(DocumentEntity document);
/** 上传查重锁定:同名同类型未删(SUB_PLAN 限定父内,MAIN 全局),对齐 Python duplicate 条件。 */
@Select("")
DocumentEntity findDuplicateForUpdate(@Param("name") String name, @Param("type") String type,
@Param("parentId") Long parentId);
/** 覆盖上传:全文件与元数据字段 + search_text + rv+1(document_name 不变)。 */
@Update("UPDATE doc_document SET summary = #{summary}, security_level = #{securityLevel}, "
+ "category_id = #{categoryId}, category_name = #{categoryName}, "
+ "category_path = #{categoryPath}, organization_id = #{organizationId}, "
+ "organization_name = #{organizationName}, tags = #{tags}, "
+ "original_file_name = #{originalFileName}, "
+ "file_relative_path = #{fileRelativePath}, file_extension = #{fileExtension}, "
+ "mime_type = #{mimeType}, file_size = #{fileSize}, file_hash = #{fileHash}, "
+ "content_text = #{contentText}, content_extract_status = #{contentExtractStatus}, "
+ "content_extract_error = #{contentExtractError}, "
+ "content_extracted_at = #{contentExtractedAt}, search_text = #{searchText}, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}")
int updateOverwrite(DocumentEntity document);
/** 上传时密码锁写入:仅密码字段(rv 不递增,对齐 Python 上传后 apply_password_lock 语义)。 */
@Update("UPDATE doc_document SET password_hash = #{passwordHash}, "
+ "protect_view = #{protectView}, protect_download = #{protectDownload}, "
+ "protect_edit = #{protectEdit}, protect_delete = #{protectDelete}, "
+ "protect_config_permission = #{protectConfigPermission}, "
+ "protect_bind_attachment = #{protectBindAttachment}, "
+ "protect_unbind_attachment = #{protectUnbindAttachment} WHERE id = #{id}")
int insertPasswordLock(DocumentEntity document);
/** MAIN 换分类:子方案分类快照级联(rv+1)。 */
@Update("UPDATE doc_document SET category_id = #{categoryId}, "
+ "category_name = #{categoryName}, category_path = #{categoryPath}, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt}, row_version = row_version + 1 "
+ "WHERE parent_document_id = #{mainId} AND document_type = 'SUB_PLAN' "
+ "AND is_deleted = 0")
int syncSubPlanCategory(@Param("mainId") long mainId, @Param("categoryId") Long categoryId,
@Param("categoryName") String categoryName,
@Param("categoryPath") String categoryPath,
@Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName,
@Param("updatedAt") LocalDateTime updatedAt);
/** 新建主案排序末尾:全局 MAIN 最大 sort_no。 */
@Select("SELECT COALESCE(MAX(sort_no), 0) FROM doc_document "
+ "WHERE document_type = 'MAIN' AND parent_document_id IS NULL AND is_deleted = 0")
int maxMainSortNo();
/** 新建子方案排序末尾:父内 SUB_PLAN 最大 sort_no。 */
@Select("SELECT COALESCE(MAX(sort_no), 0) FROM doc_document "
+ "WHERE document_type = 'SUB_PLAN' AND parent_document_id = #{parentId} "
+ "AND is_deleted = 0")
int maxSubPlanSortNo(@Param("parentId") long parentId);
/** 方案编辑:元数据字段 + search_text + rv+1(对齐 update_document)。 */
@Update("UPDATE doc_document SET document_name = #{documentName}, summary = #{summary}, "
+ "tags = #{tags}, category_id = #{categoryId}, category_name = #{categoryName}, "
+ "category_path = #{categoryPath}, organization_id = #{organizationId}, "
+ "organization_name = #{organizationName}, security_level = #{securityLevel}, "
+ "visibility_type = #{visibilityType}, search_text = #{searchText}, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}")
int updateMeta(DocumentEntity document);
/** 附件编辑:元数据字段 + search_text + rv+1(对齐 update_attachment)。 */
@Update("UPDATE doc_document SET document_name = #{documentName}, "
+ "attachment_type = #{attachmentType}, summary = #{summary}, tags = #{tags}, "
+ "organization_id = #{organizationId}, organization_name = #{organizationName}, "
+ "search_text = #{searchText}, updated_by = #{updatedBy}, "
+ "updated_by_name = #{updatedByName}, updated_at = #{updatedAt}, "
+ "row_version = row_version + 1 WHERE id = #{id}")
int updateAttachmentMeta(DocumentEntity document);
/** 描述编辑:仅 description(rv+1,对齐 update_document_description)。 */
@Update("UPDATE doc_document SET description = #{description}, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}")
int updateDescription(DocumentEntity document);
/**
* re_extract_content:覆盖写回提取结果(含失败原因)并同步重建 search_text,
* touch updated_by/name/at 与 rv+1。
*/
@Update("UPDATE doc_document SET content_text = #{contentText}, "
+ "content_extract_status = #{contentExtractStatus}, "
+ "content_extract_error = #{contentExtractError}, "
+ "content_extracted_at = #{contentExtractedAt}, search_text = #{searchText}, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}")
int updateContentExtraction(DocumentEntity document);
/** update_document 锁定行(MAIN/SUB_PLAN)。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} "
+ "AND document_type IN ('MAIN', 'SUB_PLAN') AND is_deleted = 0 FOR UPDATE")
DocumentEntity findPlanForUpdate(@Param("id") long id);
/** update_attachment/delete_attachment 锁定行。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'ATTACHMENT' "
+ "AND is_deleted = 0 FOR UPDATE")
DocumentEntity findAttachmentForUpdate(@Param("id") long id);
/** get_attachment 读取行(无锁)。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'ATTACHMENT' "
+ "AND is_deleted = 0")
DocumentEntity findActiveAttachmentById(@Param("id") long id);
/** 对齐 session.get(Document, id):主键读取,不过滤任何状态。 */
@Select("SELECT * FROM doc_document WHERE id = #{id}")
DocumentEntity findByIdNoFilter(@Param("id") long id);
/** 新建子方案:父 child_count+1、rv+1、updated_by/by_name(不触碰 updated_at)。 */
@Update("UPDATE doc_document SET child_count = child_count + 1, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "row_version = row_version + 1 WHERE id = #{id}")
int incrementChildCount(@Param("id") long id, @Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName);
/** 删除子方案:父 child_count=GREATEST(0,-1)、rv+1、updated_*。 */
@Update("UPDATE doc_document SET child_count = GREATEST(0, child_count - 1), "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}")
int decrementChildCount(@Param("id") long id, @Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName,
@Param("updatedAt") LocalDateTime updatedAt);
/** 删除主案:attachment_count 清零。 */
@Update("UPDATE doc_document SET attachment_count = 0 WHERE id = #{id}")
int setAttachmentCountZero(@Param("id") long id);
/** 删除主案前置:有效子方案计数(parent 或 root 指向主案)。 */
@Select("SELECT COUNT(*) FROM doc_document WHERE (parent_document_id = #{id} "
+ "OR root_document_id = #{id}) AND document_type = 'SUB_PLAN' AND is_deleted = 0")
long countSubPlansByMain(@Param("id") long id);
/** 软删文档:is_deleted、deleted_at、updated_by、updated_at、rv+1。 */
@Update("UPDATE doc_document SET is_deleted = 1, deleted_at = #{now}, "
+ "updated_at = #{now}, updated_by = #{actorId}, updated_by_name = #{actorName}, "
+ "row_version = row_version + 1 WHERE id = #{id}")
int softDelete(@Param("id") long id, @Param("now") LocalDateTime now,
@Param("actorId") Long actorId, @Param("actorName") String actorName);
/** 重排:ids 集合行锁定(含软删行,对齐 Python select...in 不加 is_deleted 过滤)。 */
@Select("")
List listForUpdateByIds(@Param("ids") List ids);
/** 重排:兄弟全集锁定(MAIN 全局 / SUB_PLAN 父内)。 */
@Select("")
List listSiblingsForOrder(@Param("parentId") Long parentId);
/** 重排:仅写变化行(sort_no + updated_* + rv+1)。 */
@Update("UPDATE doc_document SET sort_no = #{sortNo}, updated_by = #{updatedBy}, "
+ "updated_by_name = #{updatedByName}, updated_at = #{updatedAt}, "
+ "row_version = row_version + 1 WHERE id = #{id}")
int updateSortNo(@Param("id") long id, @Param("sortNo") int sortNo,
@Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName,
@Param("updatedAt") LocalDateTime updatedAt);
// ------------------------------------------------------------ 下载/预览与挂载域(阶段5.6)
/** 预览/下载入口:三类通用有效行(对齐 _document)。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0 "
+ "AND document_type IN ('MAIN', 'SUB_PLAN', 'ATTACHMENT')")
DocumentEntity findReadableById(@Param("id") long id);
/** _record_download 查看计数同款:下载计数自增。 */
@Update("UPDATE doc_document SET download_count = download_count + 1 WHERE id = #{id}")
int incrementDownloadCount(@Param("id") long id);
/** 挂载/解挂后主案回写:attachment_count + rv+1 + updated_*。 */
@Update("UPDATE doc_document SET attachment_count = #{attachmentCount}, "
+ "row_version = row_version + 1, updated_by = #{updatedBy}, "
+ "updated_by_name = #{updatedByName}, updated_at = #{updatedAt} WHERE id = #{id}")
int updateAttachmentCount(@Param("id") long id,
@Param("attachmentCount") long attachmentCount,
@Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName,
@Param("updatedAt") LocalDateTime updatedAt);
/** 挂载重排后主案元数据触碰:rv+1 + updated_*(不动 attachment_count)。 */
@Update("UPDATE doc_document SET row_version = row_version + 1, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{updatedAt} WHERE id = #{id}")
int bumpMeta(@Param("id") long id, @Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName,
@Param("updatedAt") LocalDateTime updatedAt);
/**
* has_any_manage_action:任一"已发布主案"命中(密级×部门范围×查看权三来源)。
* scope 为 OWN/OTHER;集合为空时调用方传 [-1] 占位(对齐 Python
* in_(空集)恒假、not_in(空集)恒真语义);canView=1 时矩阵行自身即查看来源。
*/
@Select("")
boolean existsManageHit(@Param("securityLevel") String securityLevel,
@Param("scope") String scope,
@Param("ownIds") List ownIds,
@Param("ancestorIds") List ancestorIds,
@Param("userId") long userId,
@Param("canView") boolean canView);
/** _current_version:挂载唯一键冲突兜底的当前版本读取。 */
@Select("SELECT row_version FROM doc_document WHERE id = #{id} AND is_deleted = 0")
Integer currentRowVersion(@Param("id") long id);
/** 权限保存:visibility + updated_* + rv+1(对齐 save_permissions 的文档 touch)。 */
@Update("UPDATE doc_document SET visibility_type = #{visibility}, "
+ "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, "
+ "updated_at = #{now}, row_version = row_version + 1 WHERE id = #{id}")
int updateVisibility(@Param("id") long id, @Param("visibility") String visibility,
@Param("updatedBy") Long updatedBy,
@Param("updatedByName") String updatedByName,
@Param("now") LocalDateTime now);
// ------------------------------------------------------------ 回收站(阶段7,对齐 recycle_bin_service.py)
/**
* 回收站分页查询(对齐 list_recycle_bin_documents):软删 + deleted_at 非空 +
* 类型白名单;可选 documentType/categoryId/keyword 四字段 LIKE/deletedFrom≥/deletedTo<。
* orderBy/idOrder 为服务层白名单校验后的 "列 方向" 子串。
*/
@Select("")
List listDeletedPage(@Param("f") Map filters);
/** 回收站计数(与 listDeletedPage 同条件,无分页/排序)。 */
@Select("")
long countDeletedPage(@Param("f") Map filters);
/** 对齐 _restore_operation 行锁读:主键 FOR UPDATE,不过滤删除状态。 */
@Select("SELECT * FROM doc_document WHERE id = #{id} FOR UPDATE")
DocumentEntity findByIdForUpdate(@Param("id") long id);
/** 恢复后主案子案重数(AND 语义,区别于 countSubPlansByMain 的 OR 版)。 */
@Select("SELECT COUNT(*) FROM doc_document WHERE document_type = 'SUB_PLAN' "
+ "AND parent_document_id = #{mainId} AND root_document_id = #{mainId} "
+ "AND is_deleted = 0")
long countActiveSubPlansUnderMain(@Param("mainId") long mainId);
/** 主案恢复批次附件锁定(对齐 attachments 锁读:in + id 升序 FOR UPDATE,不过滤)。 */
@Select("")
List listByIdsForUpdate(@Param("ids") java.util.Collection ids);
/** 子方案恢复:继承主案分类快照(对齐 Python 对象赋值 flush)。 */
@Update("UPDATE doc_document SET category_id = #{categoryId}, "
+ "category_name = #{categoryName}, category_path = #{categoryPath} "
+ "WHERE id = #{id}")
int updateCategorySnapshot(@Param("id") long id, @Param("categoryId") Long categoryId,
@Param("categoryName") String categoryName,
@Param("categoryPath") String categoryPath);
/** 文档复活:is_deleted=0、deleted_at=NULL、updated_*、rv+1。 */
@Update("UPDATE doc_document SET is_deleted = 0, deleted_at = NULL, "
+ "updated_at = #{now}, updated_by = #{actorId}, "
+ "updated_by_name = #{actorName}, row_version = row_version + 1 "
+ "WHERE id = #{id}")
int restoreDocument(@Param("id") long id, @Param("now") LocalDateTime now,
@Param("actorId") Long actorId, @Param("actorName") String actorName);
/**
* 主案恢复后 child_count/attachment_count 重数(对齐 SQLAlchemy 仅变化时落库:
* 值不变则 0 行受影响,不触碰 updated_*)。
*/
@Update("UPDATE doc_document SET child_count = #{childCount}, "
+ "attachment_count = #{attachmentCount} WHERE id = #{id} "
+ "AND (child_count != #{childCount} OR attachment_count != #{attachmentCount})")
int restoreMainCounts(@Param("id") long id,
@Param("childCount") long childCount,
@Param("attachmentCount") long attachmentCount);
/** 子方案恢复后主案 touch:child_count 重数 + updated_* + rv+1(无条件)。 */
@Update("UPDATE doc_document SET child_count = #{childCount}, "
+ "updated_at = #{now}, updated_by = #{actorId}, "
+ "updated_by_name = #{actorName}, row_version = row_version + 1 "
+ "WHERE id = #{id}")
int restoreParentCounts(@Param("id") long id, @Param("childCount") long childCount,
@Param("now") LocalDateTime now, @Param("actorId") Long actorId,
@Param("actorName") String actorName);
}