云
2026-09-29 f87ce86307056f63f79312178037cc8714eb2b13
feat(docs): 添加档案管理模块业务流程文档

- 设计档案分类书架分层分列管理功能
- 实现档案文档集中存储与检索机制
- 支持电子附件与实体位置兼具存储
- 建立密级管控与敏感权限管理体系
- 实现借阅登记与两级审批流程
- 添加归还登记与流转闭环功能
- 提供逾期未还实时提醒机制
- 开发档案存量与流转统计功能
- 实现课题会员财务三方归档联动
- 定义档案借阅审批BPM流程模型
已添加74个文件
已修改36个文件
31738 ■■■■■ 文件已修改
docs/archive_module_integration.md 389 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/finance_reimbursement_frontend_integration.md 334 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/README.md 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/business.json 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/flows.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/modules.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/objects.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/roles.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/index.html 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/01-business-mindmap.mmd 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/08-module-relation.mmd 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/09-data-flow.mmd 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/10-business-object.mmd 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/13-ums-membership-flow.mmd 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/14-research-flow.mmd 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/15-archive-flow.mmd 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/research_fund_finance_integration.md 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_archive_bpm_export.sql 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_check_merge.js 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_finance_bpm_export.sql 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_merge_archive.js 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_merge_research.js 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_patch_0929.js 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_research_bpm_export.sql 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/_ums_membership_approve.bpmn 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/archive_borrow_approve.bpmn 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/archive_module.sql 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/archive_module_migration.sql 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/config_export_all_20260928.sql 11838 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/config_export_all_20260928.sql.bak 11650 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/finance_reimbursement_approve.bpmn 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/research_fund_finance_migration.sql 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/research_module.sql 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/research_topic_approve.bpmn 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/ums_module.sql 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/招标技术文档.md 1071 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive-api/pom.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive-api/src/main/java/cn/iocoder/yudao/module/archive/api/document/ArchiveDocumentApi.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive-api/src/main/java/cn/iocoder/yudao/module/archive/api/document/dto/ArchiveDocumentCreateReqDTO.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive-api/src/main/java/cn/iocoder/yudao/module/archive/api/document/dto/ArchiveDocumentRespDTO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/pom.xml 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/api/document/ArchiveDocumentApiImpl.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/ArchiveBorrowController.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowPageReqVO.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowRespVO.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowReturnReqVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowSaveReqVO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/ArchiveCategoryController.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/vo/ArchiveCategoryListReqVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/vo/ArchiveCategoryRespVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/vo/ArchiveCategorySaveReqVO.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/ArchiveDocumentController.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/vo/ArchiveDocumentPageReqVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/vo/ArchiveDocumentRespVO.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/vo/ArchiveDocumentSaveReqVO.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/ArchiveStatController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/vo/ArchiveStatCategoryVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/vo/ArchiveStatRespVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/vo/ArchiveStatValueVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/dataobject/borrow/ArchiveBorrowDO.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/dataobject/category/ArchiveCategoryDO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/dataobject/document/ArchiveDocumentDO.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/mysql/borrow/ArchiveBorrowMapper.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/mysql/category/ArchiveCategoryMapper.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/mysql/document/ArchiveDocumentMapper.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/redis/ArchiveNoRedisDAO.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/redis/RedisKeyConstants.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveBorrowStatusEnum.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveCarrierTypeEnum.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveCategoryStatusEnum.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveDocumentStatusEnum.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveSecretLevelEnum.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveSourceTypeEnum.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/DictTypeConstants.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ErrorCodeConstants.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/framework/web/config/ArchiveWebConfiguration.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/borrow/ArchiveBorrowService.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/borrow/ArchiveBorrowServiceImpl.java 351 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/borrow/listener/ArchiveBorrowStatusListener.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/category/ArchiveCategoryService.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/category/ArchiveCategoryServiceImpl.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/document/ArchiveDocumentService.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/document/ArchiveDocumentServiceImpl.java 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/stat/ArchiveStatService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/stat/ArchiveStatServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/FinanceReimbursementController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementDO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/FinanceReimbursementStatusEnum.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/application/ResearchApplicationDO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/UmsMemberController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/archive_module_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,389 @@
# æ¡£æ¡ˆç®¡ç†æ¨¡å— - å‰ç«¯è”调方案
> æ¨¡å—:`yudao-module-archive`(主模块)+ `yudao-module-archive-api`(跨模块归档 RPC)
> åŽç«¯æŽ¥å£å‰ç¼€ï¼š`/archive/category`、`/archive/document`、`/archive/borrow`、`/archive/stat`
> é™„件关联:`recordType = archive_document`,`application = file`
> æƒé™ç‚¹å‰ç¼€ï¼š`archive:category:*`、`archive:document:*`、`archive:borrow:*`、`archive:stat:query`
## ä¸€ã€æ¶‰åŠé¡µé¢ï¼ˆå‰ç«¯æ–‡ä»¶æ¸…单)
| æ–‡ä»¶ | è¯´æ˜Ž |
|------|------|
| `src/packages/constants/src/dict-enum.ts` | æ–°å¢ž `ARCHIVE_DICT`(6 ä¸ªå­—典类型)并展开进 `DICT_TYPE` |
| `src/api/archive/category/index.ts` | æ¡£æ¡ˆåˆ†ç±» API |
| `src/api/archive/document/index.ts` | æ¡£æ¡ˆ API |
| `src/api/archive/borrow/index.ts` | å€Ÿé˜…单 API |
| `src/api/archive/stat/index.ts` | æ¡£æ¡ˆç»Ÿè®¡ API |
| `src/views/archive/category/index.vue` | åˆ†ç±»æ ‘表页(左树右表形态,`treeConfig` å±•开) |
| `src/views/archive/category/data.ts` | åˆ†ç±»è¡¨å• + æœç´¢è¡¨å• + è¡¨æ ¼åˆ— |
| `src/views/archive/category/modules/form.vue` | åˆ†ç±»æ–°å¢ž/编辑弹窗(含「新增下级」) |
| `src/views/archive/document/index.vue` | æ¡£æ¡ˆåˆ—表页(检索 + å¯¼å‡º + é™„件标记) |
| `src/views/archive/document/data.ts` | æ¡£æ¡ˆè¡¨å• + æœç´¢è¡¨å• + è¡¨æ ¼åˆ— |
| `src/views/archive/document/modules/form.vue` | æ¡£æ¡ˆæ–°å¢ž/编辑/详情弹窗(实体位置四字段 + ç”µå­é™„件上传) |
| `src/views/archive/borrow/index.vue` | å€Ÿé˜…列表页(逾期 Tag、提交审批、归还登记) |
| `src/views/archive/borrow/data.ts` | å€Ÿé˜…表单 + æœç´¢è¡¨å• + è¡¨æ ¼åˆ— + `BorrowStatus` å¸¸é‡ |
| `src/views/archive/borrow/modules/form.vue` | å€Ÿé˜…登记/编辑/详情弹窗 |
| `src/views/archive/borrow/modules/process-select-modal.vue` | é€‰æ‹©å®¡æ‰¹æµç¨‹å¼¹çª— |
| `src/views/archive/borrow/modules/return-form.vue` | å½’还登记弹窗 |
| `src/views/archive/borrow/detail/approve/index.vue` | **BPM å®¡æ‰¹è¯¦æƒ…页**,路径必须与 `bpm_process_definition_info.form_custom_view_path` å®Œå…¨ä¸€è‡´ |
| `src/views/archive/stat/index.vue` | æ¡£æ¡ˆç»Ÿè®¡é¡µï¼ˆ4 å¡ç‰‡ + 3 å›¾è¡¨ï¼‰ |
| `src/views/archive/stat/data.ts` | å¡ç‰‡å®šä¹‰ + é¥¼å›¾/柱图 option æž„造 |
> å­—典枚举必须登记到 `dict-enum.ts`,否则 `DICT_TYPE.ARCHIVE_*` å–不到值,列表列与表单下拉会空白。
**菜单已配置完成**,本项目为动态路由,前端无需手工加路由文件:
| èœå• | id | è·¯å¾„ | ç»„ä»¶ | æƒé™æ ‡è¯† |
|------|----|------|------|----------|
| æ¡£æ¡ˆç®¡ç†ï¼ˆé¡¶çº§ç›®å½• `parent_id=0`,`path=/archive`) | 1075600 | `/archive` | â€” | â€” |
| æ¡£æ¡ˆåˆ†ç±» | 1075601 | `category` | `archive/category/index` | `archive:category:query` |
| æ¡£æ¡ˆç®¡ç† | 1075602 | `document` | `archive/document/index` | `archive:document:query` |
| å€Ÿé˜…登记 | 1075603 | `borrow` | `archive/borrow/index` | `archive:borrow:query` |
| æ¡£æ¡ˆç»Ÿè®¡ | 1075604 | `stat` | `archive/stat/index` | `archive:stat:query` |
按钮权限:`archive:category:create/update/delete`、`archive:document:create/update/delete/export/sensitive`、`archive:borrow:create/update/delete/submit/return`、`archive:stat:query`。
> âš  **授权易错点**:这些菜单与权限点**未授予任何角色**,因此只有超级管理员(`super_admin`,代码直返全部菜单)能看到;普通角色需在「系统管理 â†’ è§’色」勾选授权。
> âš  **敏感权限**:`archive:document:sensitive`(菜单 1075625「保密档案查看」)决定能否看到密级=保密的档案。**未授权时后端会强制过滤**,不是前端隐藏。
**字典已配置完成**,前端直接用 `getDictOptions` / `DictTag` å–值,无需自建枚举:
| å­—典类型 | åç§° | å–值 |
|----------|------|------|
| `archive_category_status` | æ¡£æ¡ˆåˆ†ç±»çŠ¶æ€ | 0 å¯ç”¨ / 1 åœç”¨ |
| `archive_document_status` | æ¡£æ¡ˆçŠ¶æ€ | 10 åœ¨åº“ / 20 å€Ÿå‡ºä¸­ |
| `archive_secret_level` | æ¡£æ¡ˆå¯†çº§ | 1 å…¬å¼€ / 2 å†…部 / 3 ä¿å¯† |
| `archive_carrier_type` | æ¡£æ¡ˆè½½ä½“ | 1 ç”µå­ / 2 å®žä½“ / 3 ç”µå­+实体 |
| `archive_source_type` | æ¡£æ¡ˆæ¥æº | 1 æ‰‹å·¥å½•å…¥ / 2 è¯¾é¢˜å½’æ¡£ / 3 ä¼šå‘˜å½’æ¡£ / 4 è´¢åŠ¡å½’æ¡£ |
| `archive_borrow_status` | æ¡£æ¡ˆå€Ÿé˜…状态 | 0 è‰ç¨¿ / 15 å®¡æ‰¹ä¸­ / 20 å€Ÿå‡ºä¸­ / 30 å·²å½’还 / 35 å·²é©³å›ž |
---
## äºŒã€ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
### 2.1 æ¡£æ¡ˆç™»è®°ï¼ˆæ‰‹å·¥ï¼‰
```
选择档案分类(树形下拉,默认带出该分类的 default_secret_level)
        â”‚
        â”œâ”€â”€ æ¡£æ¡ˆæ ‡é¢˜ã€å¯†çº§ã€è½½ä½“、归档日期(必填)
        â”œâ”€â”€ å®žä½“位置:书架号 / å±‚号 / åˆ—号 / å†Œå·ï¼ˆæ¡£æ¡ˆç›’号)/ å­˜æ”¾ä½ç½®æè¿°
        â””── ç”µå­æ–‡ä»¶ï¼šèµ° /system/storage-blob/upload æ‹¿ blobId,随 create/update ä¸€èµ·æäº¤
                    â”‚
                 ä¿å­˜ï¼ˆcreate)
                    â–¼
        åŽç«¯ç”Ÿæˆæ¡£æ¡ˆç¼–号 DA+yyyyMMdd+6位流水,落库并 bindAttachments
                    â”‚
                åˆ—表可检索(标题/关键词/来源单号/分类/密级/载体/状态/归档日期区间)
```
- **附件字段**:表单字段名 `blobIds`(number[])。**全量替换语义** â€”— æ¸…空全部附件时必须显式传 `[]`,传 `null` è§†ä¸ºã€Œä¸åŠ¨é™„ä»¶ã€ã€‚
- **打开编辑/详情时**:需把 `attachmentList` æ˜ å°„成上传组件可识别的对象(`uid`/`name`/`url`/`status`/`id`),提交时按 `valueKey: 'id'` æå–为 blobId。
- **只读字段**:`no`(档案编号)、`status`(档案状态)、`sourceType` / `sourceNo`(来源)、`archivistName`(归档人)由后端维护,表单只展示不回传。
### 2.2 å€Ÿé˜…闭环
```
登记借阅(选择在库档案 + å€Ÿé˜…人 + å€Ÿé˜…日期 + å½’还期限)
        â”‚
     ä¿å­˜ï¼ˆè‰ç¨¿ 0)
        â”‚
     æäº¤å®¡æ‰¹ï¼ˆå…ˆè°ƒ approve-process-list æ‹¿æµç¨‹ key,再 submit)
        â–¼
     å®¡æ‰¹ä¸­ 15 â”€â”€[BPM é€šè¿‡]──► å€Ÿå‡ºä¸­ 20 â”€â”€[归还登记]──► å·²å½’还 30
        â”‚                          â”‚
        â”‚                    ï¼ˆåŒæ—¶æŠŠæ¡£æ¡ˆç½®ä¸ºã€Œå€Ÿå‡ºä¸­ã€ï¼‰
        â”‚
        â””──[BPM é©³å›ž]──► å·²é©³å›ž 35 â”€â”€ï¼ˆæ”¹åŽå¯é‡æ–°æäº¤ï¼‰
```
**关键带入规则**:
1. **借阅登记时**:选中的档案会以 `no + title` å½¢å¼å±•示;提交时只传 `documentId`,档案编号与标题由后端快照到借阅单(`documentNo` / `documentTitle`)。
2. **提交审批时**:**流程 key å¿…须来自后端 `approve-process-list`,禁止前端写死**。列表为空时应提示「暂无可用审批流程,请先到 BPM æµç¨‹è®¾è®¡å™¨å‘布档案借阅审批流程」。
3. **归还登记时**:只传 `id` + `returnRemark`。归还成功后档案自动回到「在库」,借阅单变「已归还」并写入 `returnTime` / `returnConfirmUserName`。
### 2.3 é€¾æœŸæé†’(实时计算,不落库、不发通知)
- åŽç«¯åœ¨ `page` / `overdue-page` è¿”回时实时计算:`overdue = (status == 20 && returnDeadline < ä»Šå¤©)`,`overdueDays = ä»Šå¤© - returnDeadline`。
- å‰ç«¯**不需要**自行计算,直接用 `row.overdue` / `row.overdueDays` æ¸²æŸ“红色 Tag。
- `overdueOnly` ä¸æ˜¯æŸ¥è¯¢å‚数,而是独立接口 `GET /archive/borrow/overdue-page`;前端用「只看逾期」按钮切换调用的接口即可。
### 2.4 ä¸‰æ–¹å½’档联动(本模块的外部入口)
| æ¥æºæ¨¡å— | æŽ¥å£ | æƒé™ç‚¹ | è§¦å‘前提 |
|----------|------|--------|----------|
| è¯¾é¢˜ | `POST /research/application/archive?id` | `research:application:archive` | ç”³æŠ¥å• `status=20 å·²ç«‹é¡¹` ä¸”未归档 |
| ä¼šå‘˜ | `POST /ums/member/archive?id` | `ums:member:archive` | ä¼šå‘˜ `status=1 æ­£å¸¸` ä¸”未归档 |
| è´¢åŠ¡ | `POST /finance/reimbursement/archive?id` | `finance:reimbursement:archive`(既有) | æŠ¥é”€å• `status=20 å·²é€šè¿‡` ä¸”未归档 |
- ä¸‰ä¸ªæŽ¥å£**均返回生成的档案编号**(`CommonResult<String>`,如 `DA20260929000001`),前端 toast ç›´æŽ¥æç¤ºã€Œå½’档成功,已生成档案 XXX」。
- ä¸‰æ–¹åˆ—表页的响应体新增两个字段用于控制按钮显隐与「已归档」标记:
  | å­—段 | ç±»åž‹ | è¯´æ˜Ž |
  |------|------|------|
  | `archiveDocumentId` | number | å½’档生成的档案 ID(非空即已归档) |
  | `archived` | boolean | æ˜¯å¦å·²å½’æ¡£ |
- ç”Ÿæˆçš„æ¡£æ¡ˆï¼š`sourceType` ä¸º 2 è¯¾é¢˜ / 3 ä¼šå‘˜ / 4 è´¢åŠ¡ï¼Œ`sourceNo` å–来源单号(立项编号 / ä¼šå‘˜ç¼–号 / æŠ¥é”€å•号),分类固定取 `research` / `member` / `finance`。
- **财务归档默认密级为「保密」**(分类 `finance` çš„ `default_secret_level=3`),因此财务档案在无 `archive:document:sensitive` æƒé™æ—¶**在档案列表里看不到**。
- **重复归档会报错**,提示中带来源单号与已生成的档案编号;前端应在归档成功后刷新列表(`archiveDocumentId` éžç©ºåŽæŒ‰é’®è‡ªåŠ¨éšè—ï¼‰ã€‚
- **财务归档是叠加语义**:既有逻辑把报销单 20→40 å¹¶å†™ `archiveTime`,本次在此基础上追加生成档案,两者同事务。
---
## ä¸‰ã€API
### 3.1 æ¡£æ¡ˆåˆ†ç±» `/archive/category`
| æ–¹æ³• | è·¯å¾„ | æƒé™ | è¯´æ˜Ž |
|------|------|------|------|
| POST | `/archive/category/create` | `archive:category:create` | æ–°å¢žåˆ†ç±» |
| PUT | `/archive/category/update` | `archive:category:update` | ä¿®æ”¹åˆ†ç±» |
| DELETE | `/archive/category/delete?id` | `archive:category:delete` | åˆ é™¤ï¼ˆæœ‰å­åˆ†ç±»/有档案时拒绝) |
| GET | `/archive/category/get?id` | `archive:category:query` | åˆ†ç±»è¯¦æƒ… |
| GET | `/archive/category/list` | `archive:category:query` | åˆ†ç±»åˆ—表(**平铺**,供树表 `transform` ç”¨ï¼‰ |
| GET | `/archive/category/tree` | `archive:category:query` | åˆ†ç±»æ ‘(供左树与下拉用) |
**`/list` è¯·æ±‚参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `name` | String | å¦ | åˆ†ç±»åç§°ï¼Œæ¨¡ç³ŠåŒ¹é… |
| `status` | Integer | å¦ | çŠ¶æ€ï¼š0 å¯ç”¨ / 1 åœç”¨ |
**新增/修改请求体:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `id` | Long | ä¿®æ”¹å¿…å¡« | ç¼–号 |
| `parentId` | Long | æ˜¯ | çˆ¶åˆ†ç±» ID,0=顶级 |
| `name` | String | æ˜¯ | åˆ†ç±»åç§° |
| `code` | String | æ˜¯ | åˆ†ç±»ç¼–码,唯一(member/research/finance ç­‰ï¼Œå½’档联动按此查找) |
| `sort` | Integer | å¦ | æ˜¾ç¤ºæŽ’序 |
| `description` | String | å¦ | åˆ†ç±»è¯´æ˜Ž |
| `defaultSecretLevel` | Integer | æ˜¯ | é»˜è®¤å¯†çº§ï¼ˆ1/2/3) |
| `status` | Integer | å¦ | çŠ¶æ€ï¼ˆ0 å¯ç”¨ / 1 åœç”¨ï¼‰ |
| `remark` | String | å¦ | å¤‡æ³¨ |
**响应字段:** `id`、`parentId`、`name`、`code`、`sort`、`description`、`defaultSecretLevel`、`status`、`remark`、`createTime`、`children`(树形接口才返回)。
**树表接入要点**:`/list` è¿”回**平铺**数组,前端 VXE è¡¨æ ¼ç”¨ `treeConfig: { parentField: 'parentId', rowField: 'id', transform: true, expandAll: true, reserve: true }` è‡ªè¡Œè½¬æ ‘,`pagerConfig.enabled = false`,名称列加 `treeNode: true`。
### 3.2 æ¡£æ¡ˆ `/archive/document`
| æ–¹æ³• | è·¯å¾„ | æƒé™ | è¯´æ˜Ž |
|------|------|------|------|
| POST | `/archive/document/create` | `archive:document:create` | æ–°å¢žæ¡£æ¡ˆ |
| PUT | `/archive/document/update` | `archive:document:update` | ä¿®æ”¹ï¼ˆå€Ÿå‡ºä¸­æ‹’绝) |
| DELETE | `/archive/document/delete?id` | `archive:document:delete` | åˆ é™¤ï¼ˆå€Ÿå‡ºä¸­/存在未归还借阅单拒绝) |
| GET | `/archive/document/get?id` | `archive:document:query` | è¯¦æƒ…(保密档案需 `archive:document:sensitive`) |
| GET | `/archive/document/page` | `archive:document:query` | åˆ†é¡µï¼ˆæ— æ•æ„Ÿæƒé™æ—¶è‡ªåŠ¨è¿‡æ»¤ä¿å¯†æ¡£æ¡ˆï¼‰ |
| GET | `/archive/document/export-excel` | `archive:document:export` | å¯¼å‡º Excel(同上过滤) |
**`/page` è¯·æ±‚参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `pageNo` / `pageSize` | Integer | æ˜¯ | åˆ†é¡µ |
| `no` | String | å¦ | æ¡£æ¡ˆç¼–号,模糊匹配 |
| `title` | String | å¦ | æ¡£æ¡ˆæ ‡é¢˜ï¼Œæ¨¡ç³ŠåŒ¹é… |
| `categoryId` | Long | å¦ | æ¡£æ¡ˆåˆ†ç±» ID |
| `secretLevel` | Integer | å¦ | å¯†çº§ |
| `carrierType` | Integer | å¦ | è½½ä½“ |
| `sourceType` | Integer | å¦ | æ¥æºç±»åž‹ |
| `sourceNo` | String | å¦ | æ¥æºå•号,模糊匹配 |
| `tags` | String | å¦ | å…³é”®è¯ï¼Œæ¨¡ç³ŠåŒ¹é… tags å­—段 |
| `status` | Integer | å¦ | æ¡£æ¡ˆçŠ¶æ€ |
| `archiveDate` | LocalDate[] | å¦ | å½’档日期区间,**格式 `yyyy-MM-dd`**(RangePicker å¿…须设 `valueFormat: 'YYYY-MM-DD'`,不能带时分秒) |
**新增/修改请求体(SaveReqVO åªæŽ¥å—以下字段):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `id` | Long | ä¿®æ”¹å¿…å¡« | ç¼–号 |
| `title` | String | æ˜¯ | æ¡£æ¡ˆæ ‡é¢˜ |
| `categoryId` | Long | æ˜¯ | æ¡£æ¡ˆåˆ†ç±» ID |
| `secretLevel` | Integer | æ˜¯ | å¯†çº§ |
| `carrierType` | Integer | æ˜¯ | è½½ä½“ |
| `shelfNo` / `layerNo` / `columnNo` / `boxNo` | String | å¦ | å®žä½“位置四字段 |
| `storePlace` | String | å¦ | å­˜æ”¾ä½ç½®æè¿° |
| `archiveDate` | String(`yyyy-MM-dd`) | æ˜¯ | å½’档日期 |
| `summary` | String | å¦ | æ‘˜è¦ |
| `tags` | String | å¦ | å…³é”®è¯ï¼Œè‹±æ–‡é€—号分隔 |
| `remark` | String | å¦ | å¤‡æ³¨ |
| `blobIds` | Long[] | å¦ | é™„ä»¶ ID å…¨é‡åˆ—表,**清空须传 `[]`** |
**响应新增/只读字段:** `no`、`categoryName`、`sourceType`、`sourceNo`、`sourceId`、`sourceTitle`、`archivistUserId`、`archivistName`、`status`、`attachmentList`、`hasAttachment`。
> âš  `no` / `status` / `source*` / `archivist*` **不要在提交体里回传**(后端 SaveReqVO ä¸æŽ¥æ”¶ï¼Œå›žä¼ æ— æ„ä¹‰ä¸”会被忽略)。
### 3.3 å€Ÿé˜… `/archive/borrow`
| æ–¹æ³• | è·¯å¾„ | æƒé™ | è¯´æ˜Ž |
|------|------|------|------|
| POST | `/archive/borrow/create` | `archive:borrow:create` | ç™»è®°å€Ÿé˜…(草稿) |
| PUT | `/archive/borrow/update` | `archive:borrow:update` | ä¿®æ”¹ï¼ˆä»…草稿/已驳回) |
| DELETE | `/archive/borrow/delete?id` | `archive:borrow:delete` | åˆ é™¤ï¼ˆä»…草稿) |
| GET | `/archive/borrow/get?id` | `archive:borrow:query` | è¯¦æƒ… |
| GET | `/archive/borrow/page` | `archive:borrow:query` | åˆ†é¡µ |
| GET | `/archive/borrow/overdue-page` | `archive:borrow:query` | **逾期未还**分页(借出中 ä¸” å½’还期限已过) |
| POST | `/archive/borrow/submit?id&processDefinitionKey` | `archive:borrow:submit` | æäº¤å®¡æ‰¹ |
| GET | `/archive/borrow/approve-process-list` | `archive:borrow:query` | å®¡æ‰¹æµç¨‹å®šä¹‰åˆ—表(`{ id, key, name }`) |
| POST | `/archive/borrow/return` | `archive:borrow:return` | å½’还登记 |
**`/page` ä¸Ž `/overdue-page` è¯·æ±‚参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `pageNo` / `pageSize` | Integer | æ˜¯ | åˆ†é¡µ |
| `no` | String | å¦ | å€Ÿé˜…单号,模糊匹配 |
| `documentNo` | String | å¦ | æ¡£æ¡ˆç¼–号,模糊匹配 |
| `documentTitle` | String | å¦ | æ¡£æ¡ˆæ ‡é¢˜ï¼Œæ¨¡ç³ŠåŒ¹é… |
| `borrowerName` | String | å¦ | å€Ÿé˜…人姓名,模糊匹配 |
| `status` | Integer | å¦ | çŠ¶æ€ |
| `borrowDate` | LocalDate[] | å¦ | å€Ÿé˜…日期区间,`yyyy-MM-dd` |
| `returnDeadline` | LocalDate[] | å¦ | å½’还期限区间,`yyyy-MM-dd` |
**新增/修改请求体:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `id` | Long | ä¿®æ”¹å¿…å¡« | ç¼–号 |
| `documentId` | Long | æ˜¯ | æ¡£æ¡ˆ ID(须为**在库**档案) |
| `borrowerUserId` | Long | å¦ | å€Ÿé˜…人用户 ID |
| `borrowerName` | String | æ˜¯ | å€Ÿé˜…人姓名 |
| `borrowerDept` | String | å¦ | å€Ÿé˜…人部门 |
| `borrowerPhone` | String | å¦ | è”系电话 |
| `borrowDate` | String(`yyyy-MM-dd`) | æ˜¯ | å€Ÿé˜…日期 |
| `borrowPurpose` | String | å¦ | å€Ÿé˜…目的 |
| `returnDeadline` | String(`yyyy-MM-dd`) | æ˜¯ | å½’还期限(不得早于借阅日期) |
| `remark` | String | å¦ | å¤‡æ³¨ |
**归还登记请求体:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `id` | Long | æ˜¯ | å€Ÿé˜…单编号 |
| `returnRemark` | String | å¦ | å½’还备注 |
**响应新增只读字段:** `no`、`documentNo`、`documentTitle`、`approveTime`、`returnTime`、`returnConfirmUserId`、`returnConfirmUserName`、`rejectReason`、`rejectTime`、`overdue`、`overdueDays`、`processInstanceId`。
**响应示例:**
```json
{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 1,
        "no": "JY20260929000001",
        "documentId": 1,
        "documentNo": "DA20260929000001",
        "documentTitle": "2026年度学会工作总结",
        "borrowerName": "张三",
        "borrowerDept": "秘书处",
        "borrowDate": "2026-09-20",
        "returnDeadline": "2026-10-05",
        "status": 20,
        "approveTime": "2026-09-20 10:12:33",
        "overdue": false,
        "overdueDays": 0
      }
    ],
    "total": 1
  },
  "msg": ""
}
```
### 3.4 ç»Ÿè®¡ `/archive/stat`
| æ–¹æ³• | è·¯å¾„ | æƒé™ | è¯´æ˜Ž |
|------|------|------|------|
| GET | `/archive/stat/overview` | `archive:stat:query` | ç»Ÿè®¡æ€»è§ˆï¼ˆå­˜é‡ + æµè½¬ + ä¸‰ç»„分布) |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| `totalCount` | Long | æ¡£æ¡ˆæ€»æ•° |
| `inStockCount` | Long | åœ¨åº“档案数 |
| `borrowedCount` | Long | å€Ÿå‡ºä¸­**档案**数 |
| `borrowingCount` | Long | å€Ÿå‡ºä¸­**借阅单**数 |
| `overdueCount` | Long | é€¾æœŸæœªè¿˜å€Ÿé˜…单数 |
| `byCategory` | Array | åˆ†ç±»åˆ†å¸ƒï¼Œå…ƒç´  `{ categoryId, name, total }` |
| `bySecretLevel` | Array | å¯†çº§åˆ†å¸ƒï¼Œå…ƒç´  `{ value, name, total }` |
| `byCarrierType` | Array | è½½ä½“分布,元素 `{ value, name, total }` |
> `bySecretLevel` / `byCarrierType` çš„ `name` å·²ç”±åŽç«¯è½¬æˆå­—典标签,前端**直接当图例名用**,无需再查字典。
### 3.5 è·¨æ¨¡å—归档(本模块对外)
| æ–¹æ³• | è·¯å¾„ | æƒé™ | è¿”回 |
|------|------|------|------|
| POST | `/research/application/archive?id` | `research:application:archive` | `CommonResult<String>`(档案编号) |
| POST | `/ums/member/archive?id` | `ums:member:archive` | `CommonResult<String>`(档案编号) |
| POST | `/finance/reimbursement/archive?id` | `finance:reimbursement:archive` | `CommonResult<String>`(档案编号) |
**响应示例:**
```json
{ "code": 0, "data": "DA20260929000001", "msg": "" }
```
---
## å››ã€å­—段展示规则
| å­—段 | å±•示位置 | è§„则 |
|------|----------|------|
| `no` | æ¡£æ¡ˆåˆ—表、详情 | åŽç«¯ç”Ÿæˆï¼Œåªè¯» |
| `title` | åˆ—表、详情、表单 | åˆ—表可模糊搜索 |
| `categoryName` | åˆ—表 | åŽç«¯ join åˆ†ç±»å†—余,列表直接展示名称而非 ID |
| `secretLevel` | åˆ—表、表单 | `CellDict` å– `archive_secret_level`;**保密档案无权限时根本不返回** |
| `carrierType` | åˆ—表、表单 | `CellDict` å– `archive_carrier_type` |
| `sourceType` | åˆ—表 | `CellDict` å– `archive_source_type` |
| `sourceNo` | åˆ—表、详情 | æ¥æºå•号(立项编号/会员编号/报销单号) |
| `storePlace` + å››ä½ç½®å­—段 | åˆ—表、详情 | å®žä½“位置;电子载体时可为空 |
| `archiveDate` | åˆ—表、详情、表单 | `yyyy-MM-dd` |
| `status`(档案) | åˆ—表、表单(只读) | `CellDict` å– `archive_document_status` |
| `hasAttachment` | åˆ—表 | ç”¨ slot æ¸²æŸ“「有 / æ— ã€Tag |
| `overdue` / `overdueDays` | å€Ÿé˜…列表、借阅详情 | ç”¨ slot æ¸²æŸ“,逾期时红色 Tag「逾期 N å¤©ã€ |
| `status`(借阅) | å€Ÿé˜…列表、审批详情 | `CellDict` å– `archive_borrow_status` |
| `documentNo` / `documentTitle` | å€Ÿé˜…列表 | å½’档时的快照,档案改名不影响历史借阅单 |
| `approveTime` / `returnTime` | å€Ÿé˜…列表 | `formatDateTime` æ ¼å¼åŒ– |
| `archiveDocumentId` | è¯¾é¢˜ / ä¼šå‘˜ / è´¢åŠ¡åˆ—è¡¨ | éžç©ºå³æ˜¾ç¤ºã€Œå·²å½’档」,并隐藏「归档」按钮 |
---
## äº”、业务规则说明
| åœºæ™¯ | è§„则 |
|------|------|
| åˆ é™¤åˆ†ç±» | æœ‰å­åˆ†ç±»æˆ–有档案时拒绝,提示中回显分类名与具体原因 |
| ä¿®æ”¹åˆ†ç±»çˆ¶çº§ | ä¸èƒ½æŠŠåˆ†ç±»è®¾ä¸ºè‡ªèº«æˆ–其子分类 |
| ä¿®æ”¹æ¡£æ¡ˆ | æ¡£æ¡ˆå¤„于「借出中」时拒绝修改 |
| åˆ é™¤æ¡£æ¡ˆ | æ¡£æ¡ˆå¤„于「借出中」或存在未归还借阅单时拒绝 |
| åˆ é™¤å€Ÿé˜…单 | ä»…「草稿」可删除 |
| æäº¤å€Ÿé˜…审批 | ä»…「草稿」「已驳回」可提交;提交前校验同档案无「审批中/借出中」借阅单 |
| å½’还期限 | ä¸å¾—早于借阅日期 |
| å®¡æ‰¹é€šè¿‡ | å€Ÿé˜…单 â†’ å€Ÿå‡ºä¸­å¹¶å†™ `approveTime`;**同时**把档案置为「借出中」 |
| å®¡æ‰¹é©³å›ž | å€Ÿé˜…单 â†’ å·²é©³å›žå¹¶å†™ `rejectReason` / `rejectTime`,改后可重新提交 |
| å½’还登记 | ä»…「借出中」可登记;成功后档案回到「在库」 |
| é€¾æœŸ | å®žæ—¶è®¡ç®—,不落库、不发站内信、不建定时任务;仅列表 Tag ä¸Žã€Œåªçœ‹é€¾æœŸã€æŽ¥å£ä½“现 |
| æ•æ„Ÿæ¡£æ¡ˆ | æ—  `archive:document:sensitive` æ—¶ï¼Œ`/page`、`/get`、`/export-excel` ä¸‰ä¸ªå…¥å£å‡è¿‡æ»¤/拒绝 |
| å½’档幂等 | åŒä¸€æ¥æºå•据重复归档会报错,提示中带来源单号与已生成档案编号 |
| è´¢åŠ¡å½’æ¡£ | æŠ¥é”€å• 20→40(既有)**å¹¶**生成「保密」档案(本次新增),同事务 |
---
## å…­ã€æ³¨æ„äº‹é¡¹
1. **附件一律走 system æ¨¡å—**:`POST /system/storage-blob/upload` â†’ æ‹¿ `blobId`;表单字段用全局 `FileUpload` ç»„件绑定 `blobIds`。**禁止**在档案表加 `file_url` åˆ—、禁止新增上传接口、禁止用 infra çš„ `FileApi`。
2. **`blobIds` æ˜¯å…¨é‡æ›¿æ¢è¯­ä¹‰**:编辑时若清空了全部附件,必须提交 `[]`;提交 `null` åŽç«¯ä¼šè§†ä¸ºã€Œä¸åŠ¨é™„ä»¶ã€ã€‚æ‰“å¼€ç¼–è¾‘å¼¹çª—æ—¶åº”æŠŠ `attachmentList` æ˜ å°„成 `{ uid, name, url, status: 'done', id }` æ•°ç»„。
3. **日期区间一律 `valueFormat: 'YYYY-MM-DD'`**:后端 `LocalDate[]` åªæŽ¥å— `yyyy-MM-dd`,带时分秒会解析失败。
4. **流程 key ä¸å†™æ­»**:提交借阅审批前必须先调 `approve-process-list`;返回空数组时提示用户先去 BPM å‘布流程。
5. **审批详情页路径是硬约定**:`src/views/archive/borrow/detail/approve/index.vue` å¿…须与 `bpm_process_definition_info.form_custom_view_path` ä¸€è‡´ï¼Œä¸”组件需接受 `props: { id: string }`(BPM å®¡æ‰¹ä¸­å¿ƒé€šè¿‡ `businessKey` ä¼ å…¥å€Ÿé˜…单 ID)。改动路径会导致待办审批页打不开。
6. **档案「借出中」是并发敏感的**:审批通过时会条件更新档案状态,若档案已被另一条借阅单占用会返回业务异常;前端提示用户刷新后重试即可,不要静默忽略。
7. **不要在归档按钮里判断 `archived` ä¹‹å¤–的字段**:按钮显隐条件建议用 `status === å·²é€šè¿‡/已立项/正常 && !archiveDocumentId`,归档成功后刷新列表即可自动隐藏。
8. **超管与普通角色的可见性差异**:菜单与权限点默认只对超管可见,普通角色需先在「系统管理 â†’ è§’色」授权,否则整块 `/archive` èœå•不出现。
docs/finance_reimbursement_frontend_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,334 @@
# è´¢åŠ¡-发票报销 - å‰ç«¯è”调方案
> æ¨¡å—:`yudao-module-finance`(独立模块,不与其他业务模块关联)
> åŽç«¯æŽ¥å£å‰ç¼€ï¼š`/finance/reimbursement`
> é™„件关联:`recordType = finance_reimbursement`,`application = file`
## æ¶‰åŠé¡µé¢
前端 `finance` ç›®å½•已按本项目约定建好,共 8 ä¸ªæ–‡ä»¶ï¼ˆå«å­—典枚举注册):
| æ–‡ä»¶ | è¯´æ˜Ž |
|------|------|
| `src/packages/constants/src/dict-enum.ts` | æ–°å¢ž `FINANCE_DICT`(`FINANCE_REIMBURSEMENT_STATUS` / `FINANCE_EXPENSE_TYPE`),并加入 `DICT_TYPE` æ±‡æ€»å¯¼å‡º |
| `src/api/finance/reimbursement/index.ts` | æŠ¥é”€å• API å°è£… |
| `src/views/finance/reimbursement/index.vue` | æŠ¥é”€å•列表页(菜单指向此组件) |
| `src/views/finance/reimbursement/data.ts` | è¡¨å•排布 + è¡¨æ ¼åˆ—定义 + çŠ¶æ€å¸¸é‡ `ReimbursementStatus` |
| `src/views/finance/reimbursement/modules/form.vue` | æ–°å»º/编辑/详情弹窗(主表字段 + å‘票上传) |
| `src/views/finance/reimbursement/modules/item-form.vue` | æŠ¥é”€æ˜Žç»†è¡Œç¼–辑(可增删行,金额汇总回填主表) |
| `src/views/finance/reimbursement/modules/process-select-modal.vue` | é€‰æ‹©å®¡æ‰¹æµç¨‹å¼¹çª— |
| `src/views/finance/reimbursement/detail/approve/index.vue` | å®¡æ‰¹è¯¦æƒ…页(BPM å®¡æ‰¹ä¸­å¿ƒåŠ¨æ€åŠ è½½ç”¨ï¼‰ |
> å­—典枚举必须登记到 `dict-enum.ts`,否则 `DICT_TYPE.FINANCE_*` å–不到值,列表与表单下拉会空白。
**菜单已配置完成**,无需前端手工加路由(本项目为动态路由):
| èœå• | id | è·¯å¾„ | ç»„ä»¶ | è¯´æ˜Ž |
|------|----|------|------|------|
| è´¢åŠ¡æŠ¥é”€ | 1075425 | `/finance` | â€” | **顶级目录**(`parent_id=0`,sort=75,排在「售后管理」与「基础设施」之间) |
| æŠ¥é”€å• | 1075417 | `reimbursement` | `finance/reimbursement/index` | æŒ‚在「财务报销」下 |
> æŠ¥é”€å•为**独立模块**,因此刻意**不挂在 ERP ç³»ç»Ÿ â†’ è´¢åŠ¡ç®¡ç†**下(那里是付款单/收款单/结算账户,属 ERP ä¸šåŠ¡ï¼‰ï¼Œè€Œæ˜¯æ–°å»ºé¡¶çº§ç›®å½•ã€Œè´¢åŠ¡æŠ¥é”€ã€è‡ªæˆä¸€æ¡é“¾è·¯ã€‚
>
> âš  **菜单位置易错点**:报销单是**顶级菜单的二级项** â€”— ä¾§è¾¹æ å±•å¼€ **「财务报销」** å³å¯çœ‹åˆ°ï¼›é¢åŒ…屑显示为 `财务报销 > æŠ¥é”€å•`,最终路由 `/finance/reimbursement`(**注意不是** `/erp/finance/reimbursement`)。ERP ç³»ç»Ÿä¸‹çš„「财务管理」里**没有**报销单。
>
> âš  **授权易错点**:`system_role_menu` ä¸­ 1075425 ä¸Ž 1075417 **均未授予任何角色**,因此**只有超级管理员**(`super_admin`,代码直返全部菜单)能看到;普通角色登录看不到,需要在「系统管理 â†’ è§’色」里勾选授权。
>
> èœå•**每次进入应用都会重新拉取**(`accessStore` çš„ `persist.pick` åªå« token/accessCodes,不含 `accessMenus`),所以不存在「需要清缓存才看得到」的问题;看不到只可能是**位置没展开**或**账号角色未授权**。
**字典已配置完成**,前端直接用 `getDictOptions` / `DictTag` å–值,无需自建枚举:
| å­—典类型 | åç§° | å–值 |
|----------|------|------|
| `finance_reimbursement_status` | æŠ¥é”€çŠ¶æ€ | 0 è‰ç¨¿ / 15 å®¡æ‰¹ä¸­ / 20 å·²é€šè¿‡ / 35 å·²é©³å›ž / 40 å·²å½’æ¡£ |
| `finance_expense_type` | è´¹ç”¨ç±»åž‹ | 1 å·®æ—… / 2 åŠžå…¬ / 3 æ‹›å¾… / 4 äº¤é€š / 5 å…¶ä»– |
**按钮权限标识**(已落 `system_menu`,前端用 `v-access:code` æˆ– `AccessControl` æŽ§åˆ¶æ˜¾éšï¼‰ï¼š
`finance:reimbursement:query`、`:create`、`:update`、`:delete`、`:export`、`:update-status`、`:archive`
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
```
新建报销单(填主表 + æ˜Žç»†è¡Œ + ä¸Šä¼ å‘票)
        â”‚
        â”œâ”€â”€ ä¸»è¡¨å­—段:报销标题、报销总金额、申请日期、备注
        â””── æ˜Žç»†è¡Œï¼ˆ1..n):费用类型、发票号码、发票金额、发票日期、开票方、费用说明
                    â”‚
                ä¿å­˜ï¼ˆcreate)
                    â”‚  â† é™„件:先上传拿 blobId,再把全部 blobId æ”¾è¿›æœ¬æ¬¡è¯·æ±‚体一起提交
                    â–¼
              status = 0 è‰ç¨¿
                    â”‚
              ã€æäº¤å®¡æ‰¹ã€‘选流程 â†’ ç”Ÿæˆ BPM å®žä¾‹
                    â–¼
              status = 15 å®¡æ‰¹ä¸­  â†â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                    â”‚                            â”‚
         â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”´â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”                 â”‚
    å®¡æ‰¹é€šè¿‡                 å®¡æ‰¹é©³å›ž              â”‚
         â–¼                      â–¼                â”‚
   status = 20 å·²é€šè¿‡      status = 35 å·²é©³å›ž â”€â”€â”˜ é‡æ–°æäº¤
         â”‚
    ã€å½’档】
         â–¼
   status = 40 å·²å½’档(终态)
```
**数据带入要点**
1. **明细行不单独调用接口**:明细随主表的 `create` / `update` ä¸€æ¬¡æ€§æäº¤ï¼ˆ`items` æ•°ç»„),后端按「先删后增」全量覆盖。前端不需要逐行调接口。
2. **发票附件两步走**(不得在业务表存文件地址):
   1. å…ˆè°ƒ `/system/storage-blob/upload` æ‹¿åˆ° `blobId`;
   2. ä¿å­˜æŠ¥é”€å•时把**该单当前全部** `blobId` æ”¾è¿› `blobIds` ä¸€èµ·æäº¤ï¼Œç”±åŽç«¯ç»Ÿä¸€ç»‘定到 `recordType=finance_reimbursement` + `recordId=报销单id`。
3. **主表「报销总金额」建议前端自动汇总**:后端不校验 `totalAmount` ä¸Ž `items[].invoiceAmount` ä¹‹å’Œæ˜¯å¦ç›¸ç­‰ï¼Œç›´æŽ¥å…¥åº“。若要保证一致,请前端在 `items` å˜åŒ–时把 `invoiceAmount` æ±‚和回填 `totalAmount`(用户仍可手改)。
4. **审批流程 key æ¥è‡ª `approve-process-list`**:前端不得写死流程 key,必须实时取列表后由用户选择(后端在提交时会按所选 key åˆ›å»ºæµç¨‹å®žä¾‹ï¼‰ã€‚
5. **审批详情页跳转**:BPM å®¡æ‰¹ä¸­å¿ƒå¸¦ `processInstanceId` è·³è½¬ï¼Œå‰ç«¯åº”从详情接口 `get` å–到 `processInstanceId` åŽæ‹¼æŽ¥è¯¦æƒ…页路由。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž | æƒé™æ ‡è¯† |
|------|------|------|----------|
| POST | `/finance/reimbursement/create` | åˆ›å»ºæŠ¥é”€å• | `finance:reimbursement:create` |
| PUT | `/finance/reimbursement/update` | ä¿®æ”¹æŠ¥é”€å• | `finance:reimbursement:update` |
| DELETE | `/finance/reimbursement/delete` | åˆ é™¤æŠ¥é”€å• | `finance:reimbursement:delete` |
| GET | `/finance/reimbursement/get` | æŸ¥è¯¢æŠ¥é”€å•详情 | `finance:reimbursement:query` |
| GET | `/finance/reimbursement/page` | åˆ†é¡µæŸ¥è¯¢æŠ¥é”€å• | `finance:reimbursement:query` |
| GET | `/finance/reimbursement/export-excel` | å¯¼å‡º Excel | `finance:reimbursement:export` |
| POST | `/finance/reimbursement/submit` | æäº¤å®¡æ‰¹ | `finance:reimbursement:update-status` |
| POST | `/finance/reimbursement/archive` | å½’æ¡£ | `finance:reimbursement:archive` |
| GET | `/finance/reimbursement/approve-process-list` | èŽ·å–å¯ç”¨å®¡æ‰¹æµç¨‹åˆ—è¡¨ | `finance:reimbursement:query` |
### POST /finance/reimbursement/create
**请求参数**(JSON body):
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| no | String | å¦ | æŠ¥é”€å•号,最长 36。**留空(含只填空格)则由后端自动生成**;填了则按自定义单号校验后落库 |
| title | String | æ˜¯ | æŠ¥é”€æ ‡é¢˜ï¼Œæœ€é•¿ 200 |
| totalAmount | BigDecimal | æ˜¯ | æŠ¥é”€æ€»é‡‘额 |
| applyDate | String | æ˜¯ | ç”³è¯·æ—¥æœŸï¼Œæ ¼å¼ `yyyy-MM-dd` |
| remark | String | å¦ | å¤‡æ³¨ï¼Œæœ€é•¿ 500 |
| blobIds | Long[] | å¦ | å‘票附件的 blobId åˆ—表(可为空数组表示无发票) |
| items | Object[] | æ˜¯ | æŠ¥é”€æ˜Žç»†ï¼Œè‡³å°‘ 1 è¡Œ |
**自定义单号规则(`no`)**:
| è¾“å…¥ | åŽç«¯è¡Œä¸º |
|------|----------|
| ä¸ä¼  / `null` / ç©ºä¸² / çº¯ç©ºæ ¼ï¼ˆå«å…¨è§’空格 ` `、制表符) | è§†ä¸ºã€Œæœªå¡«ã€ï¼Œç”±ç³»ç»ŸæŒ‰ `BX` + `yyyyMMdd` + 6 ä½æµæ°´ç”Ÿæˆ |
| æœ‰å€¼ | åŽ»æŽ‰é¦–å°¾ç©ºæ ¼åŽä½œä¸ºè‡ªå®šä¹‰å•å·ï¼›**中间**含空格 / åˆ¶è¡¨ç¬¦ / å…¨è§’空格则报错 |
| æœ‰å€¼ä½†ä¸Žå…¶ä»–报销单重复 | æŠ¥é”™ `报销单号({})已被其他报销单使用,请更换其他单号,或清空该字段由系统自动生成` |
| æœ‰å€¼ä¸”中间有空格 | æŠ¥é”™ `报销单号({})中间不能包含空格,请去掉空格后重新输入,或清空该字段由系统自动生成` |
| é•¿åº¦ > 36 | æ ¡éªŒå¤±è´¥ `报销单号长度不能超过 36 ä¸ªå­—符` |
> å‰ç«¯åœ¨æäº¤å‰å»ºè®®å…ˆ `trim()`,把纯空格视为空串再提交,避免用户误以为填了单号。
`items` å…ƒç´ ï¼š
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| expenseType | Integer | æ˜¯ | è´¹ç”¨ç±»åž‹ï¼Œå–å­—å…¸ `finance_expense_type` |
| invoiceNo | String | å¦ | å‘票号码,最长 64 |
| invoiceAmount | BigDecimal | æ˜¯ | å‘票金额 |
| invoiceDate | String | å¦ | å‘票日期,格式 `yyyy-MM-dd` |
| drawer | String | å¦ | å¼€ç¥¨æ–¹ï¼Œæœ€é•¿ 200 |
| description | String | å¦ | è´¹ç”¨è¯´æ˜Žï¼Œæœ€é•¿ 500 |
**响应**:`data` ä¸ºæ–°å»ºæŠ¥é”€å•çš„ `id`(Long)。
### PUT /finance/reimbursement/update
请求参数与 `create` ç›¸åŒï¼Œé¢å¤–**必填** `id`(Long)。仅 `status=0 è‰ç¨¿` æˆ– `status=35 å·²é©³å›ž` å¯æ”¹ã€‚
**`no` åœ¨ä¿®æ”¹æ—¶çš„语义与新增不同**:
| è¾“å…¥ | åŽç«¯è¡Œä¸º |
|------|----------|
| ä¸ä¼  / `null` / ç©ºä¸² / çº¯ç©ºæ ¼ | **保持原单号不变**(不会重新生成) |
| ä¸Žå½“前单号相同 | è§†ä¸ºä¸å˜æ›´ |
| æœ‰å€¼ä¸”与当前不同 | æ ¡éªŒæ ¼å¼ä¸Žå”¯ä¸€æ€§åŽè¦†ç›–为新单号 |
> å‰ç«¯ç¼–辑态应回显原单号;若用户清空该输入框,提交时传空串即表示「不改单号」,不会被系统重新生成。
**响应**:`data = true`。
### DELETE /finance/reimbursement/delete
| å‚æ•° | ä½ç½® | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|------|
| id | query | Long | æ˜¯ | æŠ¥é”€å• id |
仅 `status=0 è‰ç¨¿` å¯åˆ ã€‚删除时会同时清理明细、发票附件。
### GET /finance/reimbursement/get
| å‚æ•° | ä½ç½® | ç±»åž‹ | å¿…å¡« |
|------|------|------|------|
| id | query | Long | æ˜¯ |
**响应字段**:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | Long | æŠ¥é”€å• id |
| no | String | æŠ¥é”€å•号。可能是后端生成的 `BX` + `yyyyMMdd` + 6 ä½æµæ°´ï¼ˆå¦‚ `BX20260928000001`),也可能是用户自定义的单号 |
| title | String | æŠ¥é”€æ ‡é¢˜ |
| totalAmount | BigDecimal | æŠ¥é”€æ€»é‡‘额 |
| status | Integer | çŠ¶æ€ï¼Œå–å­—å…¸ `finance_reimbursement_status` |
| applicantId | Long | ç”³è¯·äºº id |
| applicantName | String | ç”³è¯·äººå§“名(后端已解析昵称,前端不用再查) |
| applyDate | String | ç”³è¯·æ—¥æœŸ `yyyy-MM-dd` |
| processInstanceId | String | BPM æµç¨‹å®žä¾‹ id,未提交时为 `null` |
| archiveTime | String | å½’档时间 `yyyy-MM-dd HH:mm:ss`,未归档为 `null` |
| remark | String | å¤‡æ³¨ |
| creator | String | åˆ›å»ºäºº id(字符串形式) |
| creatorName | String | åˆ›å»ºäººå§“名(后端已解析) |
| createTime | String | åˆ›å»ºæ—¶é—´ |
| updateTime | String | æ›´æ–°æ—¶é—´ |
| items | Object[] | æŠ¥é”€æ˜Žç»†åˆ—表 |
| attachmentList | Object[] | å‘票附件列表 |
| hasAttachment | Boolean | æ˜¯å¦æœ‰å‘票附件 |
`items` å…ƒç´ ï¼š`id`、`reimbursementId`、`expenseType`、`invoiceNo`、`invoiceAmount`、`invoiceDate`、`drawer`、`description`。
`attachmentList` å…ƒç´ ï¼š`id`(blobId)、`url`(文件地址)、`previewURL`(预览地址)、`name`(文件名)、`downloadURL`(下载地址)、`application`、`storageAttachmentId`(附件关联 id)。
### GET /finance/reimbursement/page
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNo | Integer | æ˜¯ | é¡µç ï¼Œä»Ž 1 å¼€å§‹ |
| pageSize | Integer | æ˜¯ | æ¯é¡µæ¡æ•° |
| no | String | å¦ | æŠ¥é”€å•号,模糊匹配 |
| title | String | å¦ | æŠ¥é”€æ ‡é¢˜ï¼Œæ¨¡ç³ŠåŒ¹é… |
| status | Integer | å¦ | çŠ¶æ€ï¼Œç²¾ç¡®åŒ¹é… |
| applicantId | Long | å¦ | ç”³è¯·äºº id |
| applyDate | String[] | å¦ | ç”³è¯·æ—¥æœŸåŒºé—´ |
**日期区间传参(易错点)**:后端字段是数组,需按下面形式传两个值,格式 **`yyyy-MM-dd`(不含时分秒)**:
- `applyDate[0]=2026-09-01&applyDate[1]=2026-09-30`
**响应**:`data.list` ä¸ºè¯¦æƒ…字段的数组(含 `items`、`attachmentList`),`data.total` ä¸ºæ€»æ•°ã€‚
### GET /finance/reimbursement/export-excel
查询参数同 `page`,无分页限制(忽略 `pageSize`)。响应为文件流,前端按 `blob` å¤„理即可。
导出列为:报销单号、报销标题、报销总金额、状态(自动转中文)、申请人、申请日期、归档时间、备注、创建时间、是否上传发票。
### POST /finance/reimbursement/submit
| å‚æ•° | ä½ç½® | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|------|
| id | query | Long | æ˜¯ | æŠ¥é”€å• id |
| processDefinitionKey | query | String | æ˜¯ | æµç¨‹å®šä¹‰ key,取自 `approve-process-list` |
注意:参数在 **query string**,请求体为空。
**响应**:`data = true`。成功后报销单 `status` å˜ä¸º `15 å®¡æ‰¹ä¸­`,`processInstanceId` è¢«å›žå†™ã€‚
### POST /finance/reimbursement/archive
| å‚æ•° | ä½ç½® | ç±»åž‹ | å¿…å¡« |
|------|------|------|------|
| id | query | Long | æ˜¯ |
**响应**:`data = true`。成功后 `status=40 å·²å½’æ¡£`、`archiveTime` è¢«å†™å…¥ã€‚
### GET /finance/reimbursement/approve-process-list
无请求参数。
**响应**:`data` ä¸ºæ•°ç»„,元素形如:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | String | æµç¨‹å®šä¹‰ id |
| key | String | æµç¨‹å®šä¹‰ key,提交审批时回传此值 |
| name | String | æµç¨‹åç§° |
若返回空数组,说明「报销审批」分类下**尚无已发布的流程定义**,此时必须禁用「提交审批」按钮并提示用户先到 BPM æµç¨‹è®¾è®¡å™¨å‘布流程。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| æŠ¥é”€å•号 `no` | åˆ—表、详情、编辑表单 | å¯ç¼–辑(选填)。新增时留空由系统生成;编辑时留空表示不改单号 |
| æŠ¥é”€æ ‡é¢˜ `title` | åˆ—表(可点开详情)、编辑表单 | å¿…å¡« |
| çŠ¶æ€ `status` | åˆ—表、详情 | ç”¨ `DictTag` æ¸²æŸ“å­—å…¸ `finance_reimbursement_status`,取后端返回的颜色 |
| æŠ¥é”€æ€»é‡‘额 `totalAmount` | åˆ—表(右对齐)、详情 | å¿…填,建议由明细汇总回填 |
| ç”³è¯·äºº `applicantName` | åˆ—表、详情 | åªè¯»ï¼ŒåŽç«¯å·²è§£æžå§“名 |
| ç”³è¯·æ—¥æœŸ `applyDate` | åˆ—表、编辑表单 | å¿…填,日期选择器(不带时间) |
| åˆ›å»ºäºº `creatorName` / åˆ›å»ºæ—¶é—´ `createTime` | åˆ—表、详情 | åªè¯» |
| å½’档时间 `archiveTime` | è¯¦æƒ… | åªè¯»ï¼Œæœªå½’档显示 `-` |
| å¤‡æ³¨ `remark` | è¯¦æƒ…、编辑表单 | é€‰å¡« |
| æ˜Žç»†è¡Œ `items` | ç¼–辑表单(子表格)、详情 | åˆ—表页可不展示,详情页可折叠展开 |
| æ˜Žç»†.费用类型 `expenseType` | æ˜Žç»†è¡¨æ ¼ | ç”¨å­—å…¸ `finance_expense_type` æ¸²æŸ“ |
| æ˜Žç»†.发票金额 `invoiceAmount` | æ˜Žç»†è¡¨æ ¼ï¼ˆå³å¯¹é½ï¼‰ | å¿…å¡« |
| å‘票附件 `attachmentList` | ç¼–辑表单、详情 | åªè¯»åˆ—表 + é¢„览/下载;编辑态允许增删(见「注意事项」) |
| æ˜¯å¦æœ‰å‘票 `hasAttachment` | åˆ—表 | å¯å±•示为「有/无」标签,方便财务扫单 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
### çŠ¶æ€ä¸Žå¯ç”¨æ“ä½œçŸ©é˜µ
| çŠ¶æ€ | ç¼–辑 | åˆ é™¤ | æäº¤å®¡æ‰¹ | å½’æ¡£ | è¯´æ˜Ž |
|------|:----:|:----:|:--------:|:----:|------|
| 0 è‰ç¨¿ | âœ… | âœ… | âœ… | âŒ | å”¯ä¸€å¯åˆ é™¤çš„状态 |
| 15 å®¡æ‰¹ä¸­ | âŒ | âŒ | âŒ | âŒ | å…¨éƒ¨åªè¯»ï¼Œç­‰å¾…审批结果 |
| 20 å·²é€šè¿‡ | âŒ | âŒ | âŒ | âœ… | ä»…可归档 |
| 35 å·²é©³å›ž | âœ… | âŒ | âœ… | âŒ | å¯æ”¹å‘票后重新提交;**不可删除** |
| 40 å·²å½’æ¡£ | âŒ | âŒ | âŒ | âŒ | ç»ˆæ€ï¼Œå…¨éƒ¨åªè¯» |
### æ ¡éªŒè§„则(后端已实现,前端应做同步前置校验以提升体验)
| åœºæ™¯ | è§„则 | åŽç«¯è¿”回的提示 |
|------|------|----------------|
| åˆ›å»º/修改时 `items` ä¸ºç©º | è‡³å°‘ 1 è¡Œæ˜Žç»† | `报销明细不能为空` |
| åˆ›å»º/修改时 `title` / `totalAmount` / `applyDate` ä¸ºç©º | å¿…å¡« | `报销标题不能为空` / `报销总金额不能为空` / `申请日期不能为空` |
| è‡ªå®šä¹‰å•号与其他报销单重复 | æ‹’绝 | `报销单号({})已被其他报销单使用,请更换其他单号,或清空该字段由系统自动生成` |
| è‡ªå®šä¹‰å•号中间含空格/制表符/全角空格 | æ‹’绝 | `报销单号({})中间不能包含空格,请去掉空格后重新输入,或清空该字段由系统自动生成` |
| è‡ªå®šä¹‰å•号长度 > 36 | æ‹’绝 | `报销单号长度不能超过 36 ä¸ªå­—符` |
| éžè‰ç¨¿/非已驳回状态调 `update` | æ‹’绝 | `报销单({})不是草稿或已驳回状态,无法修改,只有草稿和已驳回的报销单可以编辑` |
| éžè‰ç¨¿çŠ¶æ€è°ƒ `delete` | æ‹’绝 | `报销单({})不是草稿状态,无法删除` |
| éžè‰ç¨¿/非已驳回调 `submit` | æ‹’绝 | `提交审批失败,只有草稿或已驳回状态的报销单才能提交` |
| æäº¤æ—¶æ— æ˜Žç»† | æ‹’绝 | `提交审批失败,报销单没有明细行,无法提交` |
| éžå·²é€šè¿‡è°ƒ `archive` | æ‹’绝 | `归档失败,只有已通过状态的报销单才能归档` |
| æŠ¥é”€å•不存在 | æ‹’绝 | `报销单不存在` |
### å®¡æ‰¹ç»“果如何回写状态(前端不用管,仅作了解)
BPM å®¡æ‰¹æµå®ŒæˆåŽï¼ŒåŽç«¯ç›‘听器按结果自动改状态:
- å®¡æ‰¹é€šè¿‡ â†’ `20 å·²é€šè¿‡`
- å®¡æ‰¹é©³å›ž â†’ `35 å·²é©³å›ž`
- å®¡æ‰¹å–消 â†’ é€€å›ž `0 è‰ç¨¿`
前端只需在审批通过后重新拉列表/详情即可看到新状态。
## æ³¨æ„äº‹é¡¹
1. **附件更新是全量替换,不是增量**(最容易踩的坑):`update` æŽ¥å£çš„ `blobIds` è¯­ä¹‰æ˜¯ã€Œè¯¥æŠ¥é”€å•保存后应持有的**全部**附件」。因此编辑态必须把**已有附件的 blobId ä¸Žæ–°å¢žé™„ä»¶çš„ blobId åˆå¹¶æˆä¸€ä¸ªæ•°ç»„**一起提交;只传新增的那几个,会把原有发票全部删掉。传空数组 `[]` è¡¨ç¤ºæ¸…空全部附件;不传(`null`)表示不动附件。
2. **上传与保存分离**:上传走 `/system/storage-blob/upload` æ‹¿ `blobId`,保存走 `create` / `update` æäº¤ `blobIds`。禁止调用 `/infra/file/upload`、`/infra/file/presigned-url`,也禁止在业务表/表单里存 `fileUrl` ä¹‹ç±»çš„æ–‡ä»¶åœ°å€å­—段。
3. **删除时附件会一并清理**(后端已处理),前端不需要额外调附件删除接口。但要注意:**上传后未保存就关闭弹窗**的孤儿文件不会被自动清理,建议前端在关闭时提示,或复用同一批 `blobId` ä¸‹æ¬¡ä¿å­˜ã€‚
4. **`submit` çš„参数在 query,请求体为空**。若按 JSON body ä¼ ä¼šå–不到 `id` ä¸Ž `processDefinitionKey`。
5. **审批流程 key ä¸¥ç¦å†™æ­»**:必须先调 `approve-process-list`,把返回的 `name`+`key` æ¸²æŸ“成下拉(可参考 ERP ä»˜æ¬¾å•çš„ `process-select-modal`)。列表为空时禁用提交按钮。
6. **「已驳回」可编辑,但不可删除**:后端 `update` æ”¾è¡Œ `status=0 è‰ç¨¿` ä¸Ž `status=35 å·²é©³å›ž`;`delete` ä»åªæ”¾è¡Œè‰ç¨¿ã€‚因此编辑按钮在 0/35 æ˜¾ç¤ºï¼Œåˆ é™¤æŒ‰é’®åªåœ¨ 0 æ˜¾ç¤ºã€‚
7. **`totalAmount` ä¸Žæ˜Žç»†æ±‡æ€»ä¸è”动校验**:后端不做一致性校验,总金额由前端负责回填与展示,用户可手改。若要求强一致,需后端补校验规则。
8. **日期区间筛选传参格式是 `yyyy-MM-dd`**(如 `applyDate[0]=2026-09-01`),**不要带时分秒**,否则后端解析失败报 400。注意列表日期区间选择器必须显式设置 `valueFormat: 'YYYY-MM-DD'`,项目通用的 `getRangePickerDefaultProps()` é»˜è®¤æ˜¯ `YYYY-MM-DD HH:mm:ss`,直接用会踩这个坑。
9. **列表页请不要默认展开 `items` ä¸Ž `attachmentList`**:分页接口对每行都会查明细和附件,行数多时开销较大;建议列表只展示主表字段(含 `hasAttachment`),明细与附件放到详情页按需加载。
10. **报销单号可自定义,留空则由后端生成**:表单里保留「报销单号」输入框,占位提示写「留空则由系统自动生成」。提交前请 `trim()`,纯空格要当作未填提交(后端也会再 trim ä¸€æ¬¡å…œåº•)。生成规则为 Redis åºåˆ— `BX` + `yyyyMMdd` + 6 ä½æµæ°´ï¼ˆæ¯æ—¥é‡ç½®ï¼‰ã€‚**不要在编辑态用空串覆盖已有单号**——后端把空值解释为「保持不变」,不会重新生成。
11. **明细行提交时不要带 `id`**:后端保存明细是「先删后增」全量覆盖,而明细删除是**软删除**,若回传了原行 `id`,重新插入时会与已软删的行主键冲突。前端提交前必须剥掉每行的 `id` ä¸Ž `reimbursementId`,只保留 `expenseType`、`invoiceNo`、`invoiceAmount`、`invoiceDate`、`drawer`、`description`。
12. **审批详情页由 BPM æµç¨‹è®¾è®¡å™¨æŒ‚è½½**:BPM å®¡æ‰¹ä¸­å¿ƒé€šè¿‡æµç¨‹å®šä¹‰çš„ `formCustomViewPath` åŠ¨æ€åŠ è½½ä¸šåŠ¡è¯¦æƒ…é¡µï¼ˆ`registerComponent` æŒ‰è·¯å¾„匹配),因此 BPM æµç¨‹è®¾è®¡å™¨é‡Œå¿…须把「自定义表单路径」配置为 `/finance/reimbursement/detail/approve/index.vue`,否则审批中心里看不到报销单内容。
docs/project-business/README.md
@@ -8,19 +8,19 @@
- **业务定位**:离散制造工厂产供销人财物一体化:覆盖『销售接单 â†’ é‡‡è´­/生产 â†’ è´¨æ£€ â†’ ä»“储 â†’ å‘è´§ â†’ å›žæ¬¾ â†’ å”®åŽã€å…¨é“¾è·¯é—­çޝ
- **技术底座**:yudao(ruoyi-vue-pro)二开,Spring Boot + MyBatis + Flowable + Redis + MySQL 8.4 + Milvus + é€šä¹‰åƒé—®ï¼›å‰ç«¯ mom-pro2-before(Vue3)
- **核心主线**:CRM åˆåŒ â†’ ERP é”€å”®è®¢å• â†’ MES ä¸»è®¡åˆ’/工单/报工 â†’ è´¨æ£€ä¸Žä»“储 â†’ é”€å”®å‡ºåº“ â†’ å¼€ç¥¨å›žæ¬¾ â†’ å”®åŽé—­çޝ
- **独立模块**:财务报销(`yudao-module-finance`,报销单/明细/发票附件/审批/归档,对外提供 `yudao-module-finance-api` ä¾›ä»–模块建单)与会员管理(`yudao-module-ums`,入会申请/秘书处与理事会两级审批/会员/通讯录)各自成一条链路(UMS ä¾‹å¤–:审批通过会经 AdminUserApi åˆ›å»ºç³»ç»Ÿç™»å½•账号);课题管理(`yudao-module-research`,课题指南/在线申报/专家评审/三级立项审批/立项通知/经费拨付)为另一条独立科研主线,向外取 `ums_member` ä½œä¸ºç”³æŠ¥äººã€å†™ `system_notice` ç«‹é¡¹å…¬å‘Šï¼Œ**并已对接财务**:经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆè´¢åŠ¡æŠ¥é”€å•å¹¶æäº¤å®¡æ‰¹ï¼Œå®¡æ‰¹ç»“æžœç» BPM ç›‘听器回写课题侧拨款台账
- **独立模块**:财务报销(`yudao-module-finance`,报销单/明细/发票附件/审批/归档,对外提供 `yudao-module-finance-api` ä¾›ä»–模块建单)与会员管理(`yudao-module-ums`,入会申请/秘书处与理事会两级审批/会员/通讯录)各自成一条链路(UMS ä¾‹å¤–:审批通过会经 AdminUserApi åˆ›å»ºç³»ç»Ÿç™»å½•账号);课题管理(`yudao-module-research`,课题指南/在线申报/专家评审/三级立项审批/立项通知/经费拨付)为另一条独立科研主线,向外取 `ums_member` ä½œä¸ºç”³æŠ¥äººã€å†™ `system_notice` ç«‹é¡¹å…¬å‘Šï¼Œ**并已对接财务**:经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆè´¢åŠ¡æŠ¥é”€å•å¹¶æäº¤å®¡æ‰¹ï¼Œå®¡æ‰¹ç»“æžœç» BPM ç›‘听器回写课题侧拨款台账;档案管理(`yudao-module-archive`,分类书架/档案登记与检索/借阅两级审批/归还闭环/逾期实时提醒/统计)为第四条独立主线,**对外提供 `yudao-module-archive-api` ä¾›è¯¾é¢˜ã€ä¼šå‘˜ã€è´¢åŠ¡ä¸‰æ–¹å‘å…¶å½’æ¡£**(列表页手动「归档」按钮,归档不回写来源业务状态),自身对三方**无反向依赖**
## 2. ç›®å½•结构
```text
docs/project-business/
├── index.html      # å¯è§†åŒ–页面:双击直接打开(file:// å³å¯ï¼Œæ— éœ€ä»»ä½•服务)
├── mermaid/        # 14 å¼  Mermaid ä¸šåŠ¡å›¾ï¼ˆæƒå¨æºï¼Œæ”¹è¿™é‡Œï¼‰
├── mermaid/        # 15 å¼  Mermaid ä¸šåŠ¡å›¾ï¼ˆæƒå¨æºï¼Œæ”¹è¿™é‡Œï¼‰
├── data/           # ä¸šåŠ¡æ•°æ®ï¼ˆæƒå¨æºï¼Œæ”¹è¿™é‡Œï¼‰
│   â”œâ”€â”€ business.json  # é¡¹ç›®å…ƒä¿¡æ¯/业务十问/AI能力/待确认事项
│   â”œâ”€â”€ modules.json   # 16 ä¸ªä¸šåŠ¡æ¨¡å—å®šä¹‰
│   â”œâ”€â”€ modules.json   # 17 ä¸ªä¸šåŠ¡æ¨¡å—å®šä¹‰
│   â”œâ”€â”€ objects.json   # æ ¸å¿ƒä¸šåŠ¡å¯¹è±¡ï¼ˆä½œç”¨/上游/下游/表名)
│   â”œâ”€â”€ flows.json     # 9 æ¡æµç¨‹ï¼ˆæ‘˜è¦+步骤+角色)
│   â”œâ”€â”€ flows.json     # 10 æ¡æµç¨‹ï¼ˆæ‘˜è¦+步骤+角色)
│   â””── roles.json     # è§’色权限
└── README.md
```
@@ -49,6 +49,7 @@
| 12-finance-flow | è´¢åŠ¡æŠ¥é”€ï¼ˆç‹¬ç«‹æ¨¡å—ï¼‰ï¼šæŠ¥é”€å•+明细+发票附件 â†’ æäº¤ BPM å®¡æ‰¹ â†’ é€šè¿‡/驳回 â†’ å½’档终态;单据来源含手工新建与「课题经费拨付自动生成」两类 |
| 13-ums-membership-flow | ä¼šå‘˜å…¥ä¼šï¼ˆç‹¬ç«‹æ¨¡å—):入会申请 + ææ–™é™„ä»¶ â†’ æäº¤ BPM ä¸¤çº§å®¡æ‰¹ï¼ˆç§˜ä¹¦å¤„初审→理事会审议)→ é€šè¿‡è‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å· â†’ ä¼šå‘˜åˆ—表/通讯录检索 |
| 14-research-flow | è¯¾é¢˜ç®¡ç†ï¼ˆç‹¬ç«‹æ¨¡å—):指南发布(重点/一般)→在线申报(选会员带出申报人)→专家评分明细加权排名→BPM ä¸‰çº§å®¡æ‰¹(秘书处形式审查→专家评审→学会立项审批)→立项编号与公告→经费拨付(凭证必填→1:1 è‡ªåŠ¨ç”Ÿæˆå¹¶æäº¤è´¢åŠ¡æŠ¥é”€å•â†’è´¢åŠ¡å®¡æ ¸/财务主管两级审批→结果经监听器回写台账:通过已拨付/驳回已驳回可重发/撤回回待拨付)|
| 15-archive-flow | æ¡£æ¡ˆç®¡ç†ï¼ˆç‹¬ç«‹æ¨¡å—):分类书架(树形·默认密级→新增档案自动带出)→档案登记(密级三档/载体三型/实体位置四字段/电子附件/DA ç¼–号)→三方归档联动(课题已立项/会员正常/财务已通过 å„列表手动「归档」→经 `archive-api` ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ `archive_document_id`)→借阅登记(仅「在库」可选·同档案不得有在途借阅单)→BPM ä¸¤çº§å®¡æ‰¹(档案管理员审核→分管领导审批·通过后档案条件更新为借出中)→归还登记(回在库)→逾期实时计算(不建定时任务)→存量流转统计 |
图中状态数字均为源码枚举真实值(如工单 0草稿/1已确认/2完成/3取消;ERP å®¡æ‰¹ 15审批中/20已审核)。
@@ -69,8 +70,8 @@
- æœ¬ç›®å½•由项目技能 **project-business-analysis** ç®¡ç†ï¼ˆ`.claude/skills/project-business-analysis/`),并有规则 `.claude/rules/business-visualization-maintenance.md` å¼ºåˆ¶ï¼šä¸šåŠ¡æ¨¡å—/流程/状态/对象/角色/AI å˜æ›´åŽéœ€åŒæ­¥ç»´æŠ¤æœ¬ç›®å½•。
- å¢žé‡ç»´æŠ¤ï¼šæ”¹å¯¹åº”çš„ `mermaid/*.mmd` + `data/*.json` â†’ è¿è¡Œä¸Šé¢çš„ sync è„šæœ¬ â†’ æµè§ˆå™¨éªŒè¯å—影响图。
- å…¨é‡é‡åšï¼ˆåå·®è¿‡å¤§æ—¶ï¼‰ï¼šå¯¹ Claude è¯´ã€Œé‡æ–°ç”Ÿæˆä¸šåŠ¡å…¨æ™¯ã€ï¼ŒæŠ€èƒ½ä¼šé‡æ–°èµ°è¯»æºç å¹¶æŒ‰ç”Ÿæˆæ¨¡å¼é‡å»ºã€‚
- ç”ŸæˆåŸºçº¿ï¼šdev_business @ 87bf4a99,2026-09-10;2026-09-28 å¢žé‡è¡¥å……财务报销模块(`yudao-module-finance`)、会员管理模块(`yudao-module-ums`)与课题管理模块(`yudao-module-research`,一期项 1~5;二期结项验收与成果归档尚未做,图中已标注未覆盖范围);**2026-09-29 å¢žé‡**:课题经费拨付对接财务报销——新建 `yudao-module-finance-api`(`FinanceReimbursementApi` æœ¬åœ° Bean è°ƒç”¨ï¼šå»ºå•+提交/改单+重提/删除/查询),登记拨款时 1:1 è‡ªåŠ¨ç”Ÿæˆ `finance_reimbursement`(系统生成 1 è¡Œæ˜Žç»†ï¼‰å¹¶è‡ªåŠ¨æäº¤ `finance_reimbursement_approve` ä¸¤çº§å®¡æ‰¹ï¼Œæ–°å¢žç¬¬ 15 ä¸ªä¸šåŠ¡ç›‘å¬å™¨ `ResearchFundStatusListener` æŒ‰å®¡æ‰¹ç»“果回写课题拨款台账(通过 20 / é©³å›ž 30 / æ’¤å›ž 0),影响图 08 / 09 / 10 / 14 ä¸Ž data å…¨éƒ¨å››ä»½ json。
- ç”ŸæˆåŸºçº¿ï¼šdev_business @ 87bf4a99,2026-09-10;2026-09-28 å¢žé‡è¡¥å……财务报销模块(`yudao-module-finance`)、会员管理模块(`yudao-module-ums`)与课题管理模块(`yudao-module-research`,一期项 1~5;二期结项验收与成果归档尚未做,图中已标注未覆盖范围);**2026-09-29 å¢žé‡**:课题经费拨付对接财务报销——新建 `yudao-module-finance-api`(`FinanceReimbursementApi` æœ¬åœ° Bean è°ƒç”¨ï¼šå»ºå•+提交/改单+重提/删除/查询),登记拨款时 1:1 è‡ªåŠ¨ç”Ÿæˆ `finance_reimbursement`(系统生成 1 è¡Œæ˜Žç»†ï¼‰å¹¶è‡ªåŠ¨æäº¤ `finance_reimbursement_approve` ä¸¤çº§å®¡æ‰¹ï¼Œæ–°å¢žç¬¬ 15 ä¸ªä¸šåŠ¡ç›‘å¬å™¨ `ResearchFundStatusListener` æŒ‰å®¡æ‰¹ç»“果回写课题拨款台账(通过 20 / é©³å›ž 30 / æ’¤å›ž 0),影响图 08 / 09 / 10 / 14 ä¸Ž data å…¨éƒ¨å››ä»½ json;**2026-09-29 å†å¢žé‡**:新增档案管理独立模块 `yudao-module-archive` + `yudao-module-archive-api`(`ArchiveDocumentApi`:`archiveBySource` å¹‚等归档 / `isArchived` / `getBySource`),3 å¼ è¡¨ `archive_category`/`archive_document`/`archive_borrow`、6 ä¸ªå­—典(82000~82005)、`bpm_category(8003)` + æ‰‹å†™ `archive_borrow_approve.bpmn`(已在 mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1,form_type=20,创建路径 `/archive/borrow/index`、查看路径 `/archive/borrow/detail/approve/index.vue`)、菜单 1075600~1075699;新增业务监听器 `ArchiveBorrowStatusListener`,全库业务状态监听器计数修正为 **24 ä¸ª** `*StatusListener`(14 ä¸ªç»§æ‰¿ `BpmProcessInstanceStatusEventListener`);三方归档回写 `archive_document_id` åˆ° `research_application`/`ums_member`/`finance_reimbursement`,全库表数 427→430;新增图 **15-archive-flow**,增量修订图 01 / 08 / 09 / 10 ä¸Ž data å…¨éƒ¨äº”份 json(新增 `arc` æ¨¡å—、3 ä¸ªä¸šåŠ¡å¯¹è±¡ã€`archive` æµç¨‹ã€4 ä¸ªä¸šåŠ¡è§’è‰²ï¼‰ã€‚
## 7. å·²çŸ¥å¾…确认业务
见 `data/business.json` çš„ `pendingConfirm`(页面「待确认事项」栏),要点:仓库类型/合格仓改造未落地、HRM äº¤æŽ¥ expandUserIds å°šæœªè¢«ä¸šåŠ¡æŸ¥è¯¢æŽ¥å…¥ã€å›žæ¬¾ä¸Žæ”¶æ¬¾å•æœªèµ° BPM、财务勾稽回写方法为空、NCR å¤„置不回写源单、财务报销的 BPM æµç¨‹å®šä¹‰æœªå‘布与普通角色未授菜单、会员管理的 BPM æµç¨‹æœªå‘布且缺审批自定义表单页、自动建账号用手机号作初始密码存在弱口令风险、课题管理的评分权重与通过阈值为占位规则待学会确认、课题经费拨付虽已对接财务报销但台账与报销单为软关联(财务侧删单会留悬空 `reimbursement_id`)、新建的财务报销审批流程节点审批人需上线前在 BPM è®¾è®¡å™¨æŒ‰å®žé™…组织调整、申报单与会员为软关联可能悬空等。
见 `data/business.json` çš„ `pendingConfirm`(页面「待确认事项」栏),要点:仓库类型/合格仓改造未落地、HRM äº¤æŽ¥ expandUserIds å°šæœªè¢«ä¸šåŠ¡æŸ¥è¯¢æŽ¥å…¥ã€å›žæ¬¾ä¸Žæ”¶æ¬¾å•æœªèµ° BPM、财务勾稽回写方法为空、NCR å¤„置不回写源单、财务报销的 BPM æµç¨‹å®šä¹‰æœªå‘布与普通角色未授菜单、会员管理的 BPM æµç¨‹æœªå‘布且缺审批自定义表单页、自动建账号用手机号作初始密码存在弱口令风险、课题管理的评分权重与通过阈值为占位规则待学会确认、课题经费拨付虽已对接财务报销但台账与报销单为软关联(财务侧删单会留悬空 `reimbursement_id`)、新建的财务报销审批流程节点审批人需上线前在 BPM è®¾è®¡å™¨æŒ‰å®žé™…组织调整、申报单与会员为软关联可能悬空;档案管理的 BPM éƒ¨ç½²äº§ç‰©ä¸ºæ‰‹å·¥å¯¼å‡ºéœ€ä¸¥å®ˆã€Œå…ˆéƒ¨ç½²å†å¯¼å‡ºå†åˆå¹¶ã€é¡ºåºã€èœå•与权限仅在各归档脚本中普通角色未授权、档案与来源单据(课题申报书/会员/报销单)为软关联来源删单后会留悬空快照、「文书档案」等无来源模块仅手工录入且合同档案未纳入自动归档、逾期仅查询时实时计算不建定时任务不发站内信、「同一档案仅一条未归还借阅单」由应用层校验 + æ¡ä»¶æ›´æ–°å…±åŒä¿è¯ã€ç»Ÿè®¡ä¸º `GROUP BY` èšåˆè¿‡ä¸‡åŽéœ€æ”¹é€ ã€ä¸‰æ–¹æ¥æºè¡¨å„加可空列 `archive_document_id` å±žè·¨æ¨¡å—改表需确认变更窗口等。
docs/project-business/data/business.json
@@ -9,6 +9,7 @@
      "FIN(发票报销,独立模块)",
      "UMS(学会会员,独立模块)",
      "RES(科研课题,独立模块)",
      "ARC(档案管理,独立模块)",
      "CRM(客户关系)",
      "SRM(供应商关系)",
      "HRM(人力资源)",
@@ -20,14 +21,14 @@
    "businessPositioning": "让工厂的订单、物料、生产、库存、质量、人员、资金在一个系统内闭环流转,管理层可通过 BI å¤§å±æŽŒæ¡å…¨å±€ã€‚",
    "coreChain": "CRM å•†æœº/合同 â†’ ERP é”€å”®è®¢å• â†’ MES ä¸»ç”Ÿäº§è®¡åˆ’/工单/报工 â†’ MES è´¨æ£€ä¸Žä»“储 â†’ é”€å”®å‡ºåº“发货 â†’ å¼€ç¥¨å›žæ¬¾ â†’ å”®åŽé—­çޝ",
    "techBase": "Spring Boot + MyBatis + Flowable + Redis + Quartz/RocketMQ/Kafka + Milvus å‘量库 + é€šä¹‰åƒé—®",
    "database": "MySQL 8.4,运行库 mom-jssm å…± 427 å¼ è¡¨ï¼ˆå« system_*、bpm_*、act_* åº•座表与 mes_*/erp_*/crm_*/srm_*/hrm_*/mdm_*/ai_*/finance_*/ums_*/research_* ä¸šåŠ¡è¡¨ï¼‰ï¼Œå¤šç§Ÿæˆ·å·²å…³é—­",
    "database": "MySQL 8.4,运行库 mom-jssm å…± 430 å¼ è¡¨ï¼ˆå« system_*、bpm_*、act_* åº•座表与 mes_*/erp_*/crm_*/srm_*/hrm_*/mdm_*/ai_*/finance_*/ums_*/research_*/archive_* ä¸šåŠ¡è¡¨ï¼‰ï¼Œå¤šç§Ÿæˆ·å·²å…³é—­",
    "frontend": "mom-pro2-before(Vue3 ç®¡ç†åŽå°ï¼ŒåŒä»“库存放,只读参考)"
  },
  "stats": {
    "businessModules": 16,
    "coreObjects": 56,
    "documentFlows": 9,
    "approvalListeners": 15,
    "businessModules": 17,
    "coreObjects": 59,
    "documentFlows": 10,
    "approvalListeners": 24,
    "aiCapabilities": 8,
    "wmBillTypes": 15
  },
@@ -158,8 +159,16 @@
    "课题管理:经费拨付台账的「已拨付(20)」等价于报销单「已通过(20)」,不含出纳实际付款动作;若日后需要独立的「已付款」状态,需另增状态与审批节点【二期可扩展】",
    "课题管理:申报单与 ums_member æ˜¯è½¯å…³è”(无外键),会员被删除后申报单会留下悬空 memberId;成员列表需容错展示「会员已删除」【待确认是否加外键或删除保护】",
    "课题管理:BPM æµç¨‹ research_topic_approve ä¸‰èŠ‚ç‚¹é»˜è®¤å®¡æ‰¹äººä¸ºã€Œè§’è‰²=超级管理员(1) + ä¾æ¬¡å®¡æ‰¹ã€ï¼Œè¯¥ dev åº“超级管理员角色下有 4 ä¸ªç”¨æˆ·ï¼ˆadmin/张娟/马芳/马贞),每个节点需 4 äººä¼šç­¾æ‰æŽ¨è¿›ï¼›å…¨æ–°å®‰è£…库该角色仅 admin ä¸€äººã€æµç¨‹å¯å¼€ç®±å³ç”¨ã€‚dev åº“需减少超级管理员用户或改在 BPM è®¾è®¡å™¨ä¸­ä¸ºæ¯èŠ‚ç‚¹æŒ‡å®šå®¡æ‰¹äººã€çŽ¯å¢ƒé…ç½®é¡¹ï¼Œéžä»£ç ç¼ºé™·ã€‘",
    "课题管理:立项通知复用 system_notice ç³»ç»Ÿå…¬å‘Šï¼Œä¸æ–°å¢ž system_notify_template、不发站内信;如需短信/站内信定向通知需另行配置【待确认通知渠道】"
    "课题管理:立项通知复用 system_notice ç³»ç»Ÿå…¬å‘Šï¼Œä¸æ–°å¢ž system_notify_template、不发站内信;如需短信/站内信定向通知需另行配置【待确认通知渠道】",
    "档案管理:档案借阅 BPM æµç¨‹ archive_borrow_approve ä¸ºæ‰‹å·¥å¯¼å‡ºéƒ¨ç½²äº§ç‰©ï¼ˆ_archive_bpm_export.sql å†… act_*/bpm è¡¨çš„ UUID ä¸Žæ—¶é—´æˆ³åªèƒ½åœ¨éƒ¨ç½²æ—¶ç”Ÿæˆï¼Œæ— æ³•预写),必须先在 BPM æ¨¡åž‹éƒ¨ç½²å†å¯¼å‡ºå†åˆå¹¶è¿› config_export_all é˜¶æ®µä¸€ï¼›é¡ºåºé”™ä¼šå¯¼è‡´æ–°åº“初始化时该流程缺失【部署顺序硬约束,已在文件头与 README åŒé‡æ ‡æ³¨ã€‘",
    "档案管理:菜单 1075600~1075699 ä¸Žå„ archive:* æƒé™ç‚¹ä»…在归档脚本中,普通角色未配 system_role_menu,非超管登录看不到「档案管理」目录【待确认是否补授权,与财务报销/会员管理同类问题】",
    "档案管理:档案与来源单据(课题申报书/会员/报销单)为软关联,来源单据被删除后档案的 source_id/source_no ä¼šæˆä¸ºæ‚¬ç©ºå¿«ç…§ï¼›æ¡£æ¡ˆæœ¬èº«ä¸éšæ¥æºå•据删除而删除【待确认是否需要删除保护或外键】",
    "档案管理:需求中的「学会文件、会议纪要、调研报告、学术论文、合同协议」等电子文档由「文书档案」分类承载,系统内无对应来源模块,仅支持手工录入并挂电子附件;合同档案未纳入自动归档(用户明确排除)【已确认的边界】",
    "档案管理:逾期提醒按用户决策仅查询时实时计算(不建 infra_job、不发站内信),借阅列表「逾期」列与 /overdue-page ç‹¬ç«‹å…¥å£å‘ˆçŽ°ï¼›è‹¥æ—¥åŽéœ€è¦åˆ°æœŸä¸»åŠ¨æŽ¨é€ï¼Œéœ€å¦åŠ å®šæ—¶ä»»åŠ¡ä¸Žé€šçŸ¥æ¸ é“ã€ç”¨æˆ·å·²ç¡®è®¤çš„ç®€åŒ–ï¼ŒäºŒæœŸå¯æ‰©å±•ã€‘",
    "档案管理:「同一档案仅允许一条未归还借阅单」由应用层 submit æ ¡éªŒ + å®¡æ‰¹é€šè¿‡æ—¶æ¡ä»¶æ›´æ–°ï¼ˆUPDATE ... WHERE status=10)共同保证,MySQL æ— æ³•对「部分状态唯一」建约束;校验与更新之间的理论窗口已收敛为可感知的冲突报错(档案已被占用)而非脏数据【已接受的风险】",
    "档案管理:档案统计按分类/密级/载体三组 GROUP BY èšåˆï¼ˆæ—  N+1),当前量级可接受;档案过万后需改为缓存或物化统计表【本期不做】",
    "档案管理:跨模块改表——research_application / ums_member / finance_reimbursement å„新增可空列 archive_document_id(archive_module_migration.sql ç”¨ information_schema å®ˆå«ä¿è¯å¹‚等),需与三方模块确认变更窗口【跨模块改表,无破坏性】"
  ],
  "generatedAt": "2026-09-29",
  "analysisBasis": "基于 dev_business åˆ†æ”¯ 87bf4a99 æºç ï¼Œ6 ç»„并行源码走读(MES ç”Ÿäº§è´¨é‡ / MES ä»“储 / ERP / CRM+SRM / å”®åŽ+HRM / AI+BI+MDM+BPM+底座);2026-09-28 å¢žé‡è¡¥å…… yudao-module-finance ç‹¬ç«‹è´¢åŠ¡æŠ¥é”€æ¨¡å—ï¼ˆæŠ¥é”€å• + æ˜Žç»† + å‘票附件 + BPM å®¡æ‰¹ + å½’档)与 yudao-module-ums ç‹¬ç«‹ä¼šå‘˜ç®¡ç†æ¨¡å—(入会申请 + BPM ä¸¤çº§å®¡æ‰¹ + å®¡æ‰¹é€šè¿‡è‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å· + ä¼šå‘˜åˆ—表/通讯录);2026-09-28 å¢žé‡ï¼šBPM æµç¨‹å®šä¹‰ ums_membership_approve(入会审批)已在 mom-jssm éƒ¨ç½²å¹¶å¹¶å…¥ config_export_all é˜¶æ®µä¸€ï¼ˆåˆ†ç±» ums_membership_approve,秘书处初审 â†’ ç†äº‹ä¼šå®¡è®®ï¼Œä¸¤èŠ‚ç‚¹å®¡æ‰¹äººå‡ä¸ºã€Œè§’è‰²ã€ç­–ç•¥ã€è§’è‰²=超级管理员(1) å…œåº•,表单类型=自定义表单,查看路径 /ums/application/detail/approve/index.vue);同时修复 mom-jssm ç”± CREATE TABLE ... LIKE å…‹éš†å¯¼è‡´çš„ 47 ä¸ªæŒ‡å‘ ruoyi-vue-pro çš„ act_*/flw_* è·¨åº“外键(重建为指向 mom-jssm è‡ªèº«ï¼Œå­¤å„¿è¡Œé¢„检为 0);2026-09-28 å¢žé‡ï¼šæ–°å¢ž yudao-module-research ç‹¬ç«‹ç§‘研课题模块一期(课题指南发布 + åœ¨çº¿ç”³æŠ¥ + ä¸“家评审明细 + BPM ä¸‰çº§ç«‹é¡¹å®¡æ‰¹ + ç«‹é¡¹é€šçŸ¥å…¬å‘Š + ç»è´¹æ‹¨ä»˜è®°å½•,4 å¼  research_* è¡¨ / 5 ä¸ªå­—å…¸ / bpm_category id=8002 / æµç¨‹ research_topic_approve / èœå• 1075510~1075553),并并入 config_export_all_20260928 é˜¶æ®µä¸€ï¼›ç”³æŠ¥äººå– ums_member(软关联)、立项通知复用 system_notice å…¬å‘Šã€æ‹¨æ¬¾å‡­è¯èµ° system é™„件中心 recordType=research_fund_record;2026-09-29 å¢žé‡ï¼šè¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯¹æŽ¥è´¢åŠ¡æŠ¥é”€â€”â€”æ–°å»º yudao-module-finance-api(FinanceReimbursementApi:建单+提交/改单+重提/删除/查询四方法,本地 Spring Bean è°ƒç”¨ï¼Œæ— éœ€ Feign),课题侧登记拨款时 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement(系统生成 1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他 5、发票金额=拨付金额、发票日期=拨付日期、说明=「课题〔名〕第 N æœŸæ‹¨æ¬¾ã€ï¼‰å¹¶ç«‹å³æäº¤ BPM,回写 reimbursement_id/reimbursement_no、台账置「报销审批中(15)」;凭证改为登记时必填并双绑(research_fund_record + finance_reimbursement);新增 ResearchFundStatusListener(第 15 ä¸ªä¸šåŠ¡ç›‘å¬å™¨ï¼Œä¸Ž finance è‡ªå·±çš„ FinanceReimbursementStatusListener è®¢é˜…同一流程 key å¹¶è¡Œè§¦å‘)按审批结果回写台账:通过→已拨付(20)、驳回→已驳回(30)+驳回原因/时间、取消流程→退回待拨付(0)可改后可重新发起(发起人撤回只把实例退回发起节点、流程未结束,不触发回写,台账保持 15);新造 2 èŠ‚ç‚¹ BPMN docs/sql/finance_reimbursement_approve.bpmn(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼Œcategory id=216 å·²å­˜åœ¨ï¼‰ï¼Œ2026-09-29 å·²åœ¨ mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1(form_type=20、自定义表单创建路径 /finance/reimbursement/index、查看路径 /finance/reimbursement/detail/approve/index.vue)并并入 config_export_all_20260929 é˜¶æ®µä¸€ï¼Œä¸¤çº§å®¡æ‰¹å…¨é€šè¿‡/驳回/取消三条回写链路均已端到端验证"
  "analysisBasis": "基于 dev_business åˆ†æ”¯ 87bf4a99 æºç ï¼Œ6 ç»„并行源码走读(MES ç”Ÿäº§è´¨é‡ / MES ä»“储 / ERP / CRM+SRM / å”®åŽ+HRM / AI+BI+MDM+BPM+底座);2026-09-28 å¢žé‡è¡¥å…… yudao-module-finance ç‹¬ç«‹è´¢åŠ¡æŠ¥é”€æ¨¡å—ï¼ˆæŠ¥é”€å• + æ˜Žç»† + å‘票附件 + BPM å®¡æ‰¹ + å½’档)与 yudao-module-ums ç‹¬ç«‹ä¼šå‘˜ç®¡ç†æ¨¡å—(入会申请 + BPM ä¸¤çº§å®¡æ‰¹ + å®¡æ‰¹é€šè¿‡è‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å· + ä¼šå‘˜åˆ—表/通讯录);2026-09-28 å¢žé‡ï¼šBPM æµç¨‹å®šä¹‰ ums_membership_approve(入会审批)已在 mom-jssm éƒ¨ç½²å¹¶å¹¶å…¥ config_export_all é˜¶æ®µä¸€ï¼ˆåˆ†ç±» ums_membership_approve,秘书处初审 â†’ ç†äº‹ä¼šå®¡è®®ï¼Œä¸¤èŠ‚ç‚¹å®¡æ‰¹äººå‡ä¸ºã€Œè§’è‰²ã€ç­–ç•¥ã€è§’è‰²=超级管理员(1) å…œåº•,表单类型=自定义表单,查看路径 /ums/application/detail/approve/index.vue);同时修复 mom-jssm ç”± CREATE TABLE ... LIKE å…‹éš†å¯¼è‡´çš„ 47 ä¸ªæŒ‡å‘ ruoyi-vue-pro çš„ act_*/flw_* è·¨åº“外键(重建为指向 mom-jssm è‡ªèº«ï¼Œå­¤å„¿è¡Œé¢„检为 0);2026-09-28 å¢žé‡ï¼šæ–°å¢ž yudao-module-research ç‹¬ç«‹ç§‘研课题模块一期(课题指南发布 + åœ¨çº¿ç”³æŠ¥ + ä¸“家评审明细 + BPM ä¸‰çº§ç«‹é¡¹å®¡æ‰¹ + ç«‹é¡¹é€šçŸ¥å…¬å‘Š + ç»è´¹æ‹¨ä»˜è®°å½•,4 å¼  research_* è¡¨ / 5 ä¸ªå­—å…¸ / bpm_category id=8002 / æµç¨‹ research_topic_approve / èœå• 1075510~1075553),并并入 config_export_all_20260928 é˜¶æ®µä¸€ï¼›ç”³æŠ¥äººå– ums_member(软关联)、立项通知复用 system_notice å…¬å‘Šã€æ‹¨æ¬¾å‡­è¯èµ° system é™„件中心 recordType=research_fund_record;2026-09-29 å¢žé‡ï¼šè¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯¹æŽ¥è´¢åŠ¡æŠ¥é”€â€”â€”æ–°å»º yudao-module-finance-api(FinanceReimbursementApi:建单+提交/改单+重提/删除/查询四方法,本地 Spring Bean è°ƒç”¨ï¼Œæ— éœ€ Feign),课题侧登记拨款时 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement(系统生成 1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他 5、发票金额=拨付金额、发票日期=拨付日期、说明=「课题〔名〕第 N æœŸæ‹¨æ¬¾ã€ï¼‰å¹¶ç«‹å³æäº¤ BPM,回写 reimbursement_id/reimbursement_no、台账置「报销审批中(15)」;凭证改为登记时必填并双绑(research_fund_record + finance_reimbursement);新增 ResearchFundStatusListener(第 15 ä¸ªä¸šåŠ¡ç›‘å¬å™¨ï¼Œä¸Ž finance è‡ªå·±çš„ FinanceReimbursementStatusListener è®¢é˜…同一流程 key å¹¶è¡Œè§¦å‘)按审批结果回写台账:通过→已拨付(20)、驳回→已驳回(30)+驳回原因/时间、取消流程→退回待拨付(0)可改后可重新发起(发起人撤回只把实例退回发起节点、流程未结束,不触发回写,台账保持 15);新造 2 èŠ‚ç‚¹ BPMN docs/sql/finance_reimbursement_approve.bpmn(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼Œcategory id=216 å·²å­˜åœ¨ï¼‰ï¼Œ2026-09-29 å·²åœ¨ mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1(form_type=20、自定义表单创建路径 /finance/reimbursement/index、查看路径 /finance/reimbursement/detail/approve/index.vue)并并入 config_export_all_20260929 é˜¶æ®µä¸€ï¼Œä¸¤çº§å®¡æ‰¹å…¨é€šè¿‡/驳回/取消三条回写链路均已端到端验证;2026-09-29 å¢žé‡ï¼šæ–°å¢ž yudao-module-archive ç‹¬ç«‹æ¡£æ¡ˆç®¡ç†æ¨¡å— + yudao-module-archive-api(ArchiveDocumentApi:archiveBySource æŒ‰ sourceType+sourceId å¹‚等归档 / isArchived / getBySource ä¸‰æ–¹æ³•,本地 Spring Bean è°ƒç”¨ï¼Œæ—  Feign),覆盖书架分类(archive_category æ ‘形、code å”¯ä¸€ã€defaultSecretLevel é»˜è®¤å¯†çº§ã€6 æ¡åˆå§‹åˆ†ç±»ï¼šä¼šå‘˜/课题/会议/财务/文书/党建,财务默认密级=保密 3)、档案管理(archive_document:DA+yyyyMMdd+6 ä½æµæ°´ç¼–号、密级三档 1 å…¬å¼€ 2 å†…部 3 ä¿å¯†ã€è½½ä½“三型 1 ç”µå­ 2 å®žä½“ 3 ç”µå­+实体、实体位置四字段 shelfNo/layerNo/columnNo/boxNo + storePlace、来源四类 sourceType 1 æ‰‹å·¥ 2 è¯¾é¢˜ 3 ä¼šå‘˜ 4 è´¢åС另 sourceNo/sourceId/sourceTitle å†—余快照、状态 10 åœ¨åº“ 20 å€Ÿå‡ºä¸­ã€ç”µå­é™„ä»¶èµ° system é™„件中心 recordType=archive_document、archiveBySource å¹‚等短路 + ä¸‰æ–¹è¡¨ archive_document_id å›žå†™ä½œä¸ºå·²å½’档硬标记,重复归档由来源侧错误码拦截且 archive ä¾§ DOCUMENT_DUPLICATE_SOURCE å…œåº•)、借阅登记与审批(archive_borrow:JY+同级流水编号、借阅人信息/借阅目的/归还期限、状态 0 è‰ç¨¿ 15 å®¡æ‰¹ä¸­ 20 å€Ÿå‡ºä¸­ 30 å·²å½’还 35 å·²é©³å›žï¼Œä»… 0/35 å¯æ”¹å¯é‡æã€ä»… 0 å¯åˆ ã€ä»… 20 å¯ç™»è®°å½’还;submit å‰æ ¡éªŒåŒæ¡£æ¡ˆä¸å¾—已有 15/20 åœ¨é€”借阅,先解析流程再建单杜绝孤儿草稿;BPM æµç¨‹ archive_borrow_approve ä¸¤çº§ï¼šæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼›ç›‘听器 ArchiveBorrowStatusListener æŒ‰ businessKey å›žå†™ 20+approveTime / 35+驳回原因时间 / é€€å›ž 0,审批通过时条件更新档案 SET status=20 WHERE status=10,0 è¡Œåˆ™æŠ¥æ¡£æ¡ˆå·²è¢«å ç”¨å¹¶å›žæ»šï¼‰ã€å½’还登记(条件更新 20→30 + returnTime/归还确认人,再把档案条件更新回 10,形成闭环)、逾期实时计算(overdue=status==20 ä¸” returnDeadline<今天,不建 infra_job ä¸å‘站内信,列表「逾期」列 + /archive/borrow/overdue-page ç‹¬ç«‹å…¥å£ï¼‰ã€æ¡£æ¡ˆç»Ÿè®¡ï¼ˆ4 å¡ç‰‡ï¼šæ€»æ•°/在库/借出/逾期 + åˆ†ç±»/密级/载体三组 GROUP BY èšåˆï¼Œæ—  N+1);敏感档案用密级字段 + ç‹¬ç«‹æƒé™ç‚¹ archive:document:sensitive,/get è¶ŠæƒæŠ› DOCUMENT_SENSITIVE_NO_PERMISSION、/page ä¸Ž /export-excel å¼ºåˆ¶è¿‡æ»¤ secret_level<>3,统一走 applySecretLevelFilter / validateSecretLevelVisible ä¸¤ç§æœ‰æ–¹æ³•;新增 3 å¼ è¡¨ archive_category/archive_document/archive_borrow、6 ä¸ªå­—典(82000~82005)、bpm_category id=8003 + æ‰‹å†™ docs/sql/archive_borrow_approve.bpmn(category=archive_borrow_approve、form_type=20、创建路径 /archive/borrow/index、查看路径 /archive/borrow/detail/approve/index.vue)已在 mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1、菜单 1075600~1075699;三方归档联动为列表页手动「归档」按钮(非事件驱动,归档不回写来源业务状态):课题 research_application status=20 å·²ç«‹é¡¹ â†’ sourceType=2 å¯†çº§ 2 å†…部 sourceNo=立项编号;会员 ums_member status=1 æ­£å¸¸ â†’ sourceType=3;财务 finance_reimbursement status=20 â†’ sourceType=4 å¯†çº§ 3 ä¿å¯†ï¼Œä¸Žæ—¢æœ‰ 20→40 + archiveTime åŒäº‹åŠ¡å åŠ ï¼›ä¸‰å¼ æ¥æºè¡¨å„æ–°å¢žå¯ç©ºåˆ— archive_document_id(archive_module_migration.sql ç”¨ information_schema å®ˆå«ä¿è¯å¹‚等)。全库表数 427→430;业务状态监听器实际计数修正为 24 ä¸ª *StatusListener(14 ä¸ªç»§æ‰¿ BpmProcessInstanceStatusEventListener)。本模块不反向依赖 ums/finance/research,仅提供 -api å­æ¨¡å—由三方手动调用;依赖方向 research/ums/finance â†’ archive-api,无环。本次同步更新的业务可视化产物:新增 mermaid/15-archive-flow.mmd,增量修订 01-business-mindmap / 08-module-relation / 09-data-flow / 10-business-object,以及 data/modules.json(新增 arc æ¨¡å—、bpm ç›‘听器计数 15→24)、objects.json(新增档案分类/档案/借阅单 3 å¯¹è±¡ + è¯¾é¢˜ç”³æŠ¥ä¹¦/会员/报销单各补 archive_document_id)、flows.json(新增 archive æµç¨‹ 9 æ­¥ï¼‰ã€roles.json(新增档案管理员/借阅人/借阅审批人/归档经办人 4 è§’色)、business.json"
}
docs/project-business/data/flows.json
@@ -82,7 +82,19 @@
       {"stage": "专家评审", "action": "申报单处于审批中(15)时录入专家评分明细(一位专家一行:姓名/单位/职称 + é€‰é¢˜ä»·å€¼0.3/研究方案0.3/团队基础0.2/预期成果0.2 å››é¡¹åˆ†å€¼ + ç»“论 + æ„è§ï¼‰ï¼›åŽç«¯æ¯æ¬¡å¢žåˆ æ”¹åŽé‡ç®— compositeScore åŠ æƒåˆè®¡ã€æŒ‰åŠ æƒåˆ†é™åºé‡æŽ’ ranking,并回写主单 reviewAvgScore(全部专家均分)与 reviewPass(均分达标且多数结论为推荐立项);明细全部删除时汇总值清空为未汇总;四项分值均在 0~100 ä¹‹å¤–时提示指出具体维度与实际值", "roles": ["评审专家", "学会秘书处", "课题管理员"]},
       {"stage": "立项审批", "action": "BPM æµç¨‹ research_topic_approve é¡ºåºèµ°ä¸‰èŠ‚ç‚¹ï¼šç§˜ä¹¦å¤„å½¢å¼å®¡æŸ¥ â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ã€‚审批方法与审批人在流程定义内配置,部署后由管理员在 BPM æµç¨‹è®¾è®¡å™¨æŒ‰å­¦ä¼šå®žé™…组织调整;审批中心内的自定义表单只读回显申报单 + è¯„审明细 + æ‹¨ä»˜è®°å½•。通过→20 å·²ç«‹é¡¹ã€é©³å›žâ†’35 å·²é©³å›žã€å–消→退回 0 è‰ç¨¿", "roles": ["学会秘书处(形式审查)", "评审专家(评审节点)", "学会理事会(立项审批)"]},
       {"stage": "立项与通知", "action": "监听器 ResearchApplicationStatusListener å…ˆå›žå†™å®¡æ‰¹çŠ¶æ€ï¼Œå®¡æ‰¹é€šè¿‡åŽå†è°ƒ approveApplication:生成立项编号 LX+yyyyMMdd+6位流水 ä¸Ž approveTime(已有编号则幂等跳过)。点「发布立项通知」生成 system_notice å…¬å‘Šï¼ˆæ ‡é¢˜ã€Žå…³äºŽå…¬å¸ƒç«‹é¡¹è¯¾é¢˜çš„通知』,正文含课题名/立项编号/类别/申报人/单位/资助金额),写回 noticeId é˜²é‡å¤â€”—重复点击按已发布拦截并告知上次发布时间", "roles": ["课题管理员", "学会秘书处"]},
       {"stage": "经费拨付", "action": "仅「已立项(20)」的申报单可登记拨款记录(批次名称/拨付金额/拨付日期/收款单位/备注/凭证附件),凭证必填。登记时一条龙完成:① è¯¾é¢˜ä¾§è½ research_fund_record å°è´¦ï¼ˆå…ˆç½®å¾…拨付)② åŒä¸€æ‰¹ blob åŒç»‘到课题台账与财务报销单 â‘¢ ç» yudao-module-finance-api è‡ªåŠ¨ç”Ÿæˆ 1:1 çš„ finance_reimbursement æŠ¥é”€å•(1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他、发票金额=拨付金额、发票日期=拨付日期、费用说明=「课题〔课题名〕第N期/第N次经费拨款」)④ ç«‹å³æäº¤ BPM æŠ¥é”€å®¡æ‰¹ â‘¤ å°è´¦å›žå†™æŠ¥é”€å•号并置「报销审批中(15)」。金额校验:已登记金额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出该单申请经费,超出时提示报出本次、已登记、合计、预算四项数值。状态由系统驱动:0待拨付/15报销审批中/20已拨付/30已驳回;只有「待拨付」可删(删除联动清理财务侧草稿报销单),「待拨付」「已驳回」可改并重新发起报销(已驳回改后清空驳回原因与时间);取消流程则退回「待拨付(0)」(发起人撤回只把实例退回发起节点、流程未结束,台账保持「报销审批中(15)」,需重新提交后才继续走审批)。财务审批分类下没有已发布流程时直接报错且不建单,不留孤儿草稿报销单", "roles": ["学会财务", "课题管理员", "财务审核人", "财务主管"]}]},
     {"stage": "经费拨付", "action": "仅「已立项(20)」的申报单可登记拨款记录(批次名称/拨付金额/拨付日期/收款单位/备注/凭证附件),凭证必填。登记时一条龙完成:① è¯¾é¢˜ä¾§è½ research_fund_record å°è´¦ï¼ˆå…ˆç½®å¾…拨付)② åŒä¸€æ‰¹ blob åŒç»‘到课题台账与财务报销单 â‘¢ ç» yudao-module-finance-api è‡ªåŠ¨ç”Ÿæˆ 1:1 çš„ finance_reimbursement æŠ¥é”€å•(1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他、发票金额=拨付金额、发票日期=拨付日期、费用说明=「课题〔课题名〕第N期/第N次经费拨款」)④ ç«‹å³æäº¤ BPM æŠ¥é”€å®¡æ‰¹ â‘¤ å°è´¦å›žå†™æŠ¥é”€å•号并置「报销审批中(15)」。金额校验:已登记金额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出该单申请经费,超出时提示报出本次、已登记、合计、预算四项数值。状态由系统驱动:0待拨付/15报销审批中/20已拨付/30已驳回;只有「待拨付」可删(删除联动清理财务侧草稿报销单),「待拨付」「已驳回」可改并重新发起报销(已驳回改后清空驳回原因与时间);取消流程则退回「待拨付(0)」(发起人撤回只把实例退回发起节点、流程未结束,台账保持「报销审批中(15)」,需重新提交后才继续走审批)。财务审批分类下没有已发布流程时直接报错且不建单,不留孤儿草稿报销单", "roles": ["学会财务", "课题管理员", "财务审核人", "财务主管"]}]},
    {"id": "archive", "name": "档案管理流程", "mermaid": "mermaid/15-archive-flow.mmd", "color": "#7fb3d5",
     "summary": "独立档案模块(yudao-module-archive):书架分类(分层分列)规整 â†’ æ¡£æ¡ˆç™»è®°ï¼ˆç”µå­é™„ä»¶ + å®žä½“位置四字段,密级三档)→ å€Ÿé˜…登记走 BPM ä¸¤çº§å®¡æ‰¹ï¼ˆæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼‰â†’ å®¡æ‰¹é€šè¿‡æ¡£æ¡ˆè½¬ã€Œå€Ÿå‡ºä¸­ã€â†’ å½’还登记回「在库」形成流转闭环 â†’ é€¾æœŸæœªè¿˜æŸ¥è¯¢æ—¶å®žæ—¶è®¡ç®—并在列表标红 â†’ å­˜é‡ä¸Žæµè½¬ç»Ÿè®¡ï¼ˆæ€»æ•°/在库/借出中/逾期 + åˆ†ç±»Â·å¯†çº§Â·è½½ä½“三组分布)。同时接收课题(已立项)、会员(正常)、财务(已通过)三方的「归档」联动,经新建的 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™æ¥æºè¡¨ archive_document_id。会议/党建档案系统内无来源模块,仅手工录入。",
     "steps": [
       {"stage": "建分类", "action": "新建分类可挂父分类形成分类树(编码 code å…¨å±€å”¯ä¸€ï¼‰ï¼Œè®¾æŽ’序、说明、默认密级与启用状态;6 æ¡åˆå§‹åˆ†ç±»ï¼šä¼šå‘˜æ¡£æ¡ˆ/课题档案/会议档案/财务档案(默认密级=保密 3)/文书档案/党建档案。有子分类或该分类下已有档案时禁止删除;父分类不能设为自身或其子分类", "roles": ["档案管理员"]},
       {"stage": "登记档案", "action": "填标题、分类、密级(1公开/2内部/3保密)、载体(1电子/2实体/3电子+实体)、归档日期、摘要、关键词;选电子则上传附件(走 system é™„件中心,recordType=archive_document,先 /system/storage-blob/upload å– blobId å†éšä¿å­˜è¯·æ±‚çš„ blobIds æäº¤ï¼Œå…¨é‡æ›¿æ¢è¯­ä¹‰ï¼‰ï¼Œé€‰å®žä½“则填书架号/层号/列号/册号档案盒与存放位置描述;后端生成档案编号 DA+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®ï¼‰ï¼ŒçŠ¶æ€ã€Œåœ¨åº“(10)」", "roles": ["档案管理员"]},
       {"stage": "检索", "action": "按档案编号/标题/关键词 tags/来源单号/分类/密级/载体/状态/归档日期区间多条件检索,支持 Excel å¯¼å‡ºã€‚密级=保密(3) çš„æ¡£æ¡ˆï¼šå½“前用户无 archive:document:sensitive æ—¶åˆ—表与导出强制过滤掉、直连详情接口报无权限错误", "roles": ["档案管理员", "全员可查公开档"]},
       {"stage": "三方归档", "action": "课题申报书(仅已立项 20)、会员(仅正常 1)、财务报销单(仅已通过 20)的列表页各有一个「归档」按钮,经 yudao-module-archive-api çš„ archiveBySource æŒ‰ã€Œæ¥æºç±»åž‹ + æ¥æºå•据 ID」幂等生成档案(来源单号/标题作为快照存入档案,源单附件一并带入档案;财务档案密级固定为「保密」),并回写来源表 archive_document_id ä½œä¸ºå·²å½’档硬标记;重复点击由来源侧拦截并提示已生成的档案编号。财务侧「归档」与其既有 20→40 çŠ¶æ€æŽ¨è¿›åœ¨åŒä¸€äº‹åŠ¡å†…å åŠ æ‰§è¡Œ", "roles": ["课题管理员", "会员管理员", "财务"]},
       {"stage": "借阅登记", "action": "选档案(仅「在库(10)」可选)、填借阅人/部门/联系电话、借阅日期、借阅目的、归还期限;生成借阅单号 JY+yyyyMMdd+6位流水,状态「草稿(0)」。提交前校验同一档案不得存在「审批中(15)」或「借出中(20)」的借阅单,命中时按已存在的借阅单号精确报错;submit å…ˆè§£æžæµç¨‹åˆ—表(无已发布流程直接报错、不留孤儿草稿)再建单,回写 processInstanceId å¹¶ç½®ã€Œå®¡æ‰¹ä¸­(15)」", "roles": ["借阅人", "档案管理员"]},
       {"stage": "借阅审批", "action": "BPM æµç¨‹ archive_borrow_approve ä¸¤çº§é¡ºåºå®¡æ‰¹ï¼šæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼ˆå®¡æ‰¹æ–¹æ³•与审批人在流程定义内配置,前端只选流程不写死 key;审批中心经自定义表单查看路径 /archive/borrow/detail/approve/index.vue åªè¯»å›žæ˜¾å€Ÿé˜…单与档案信息)。审批通过由 ArchiveBorrowStatusListener ç½®ã€Œå€Ÿå‡ºä¸­(20)」并回写 approveTime,同时条件更新档案状态为「借出中」(影响 0 è¡Œè¯´æ˜Žæ¡£æ¡ˆå·²è¢«å…¶å®ƒæµç¨‹å ç”¨ â†’ æŠ¥é”™å¹¶å›žæ»šï¼Œä¿è¯åŒä¸€æ¡£æ¡ˆä¸ä¼šè¢«ä¸¤æ¡æµç¨‹åŒæ—¶å€Ÿå‡ºï¼‰ï¼›é©³å›žç½®ã€Œå·²é©³å›ž(35)」并回写驳回原因与时间,修改后可重新提交;取消流程退回「草稿(0)」", "roles": ["档案管理员(审核)", "分管领导(审批)"]},
       {"stage": "归还登记", "action": "仅「借出中(20)」可登记归还:填归还备注 â†’ æ¡ä»¶æ›´æ–°å€Ÿé˜…单状态到「已归还(30)」并回写 returnTime ä¸Žå½’还确认人,同时条件更新档案回「在库(10)」,形成完整流转闭环;更新 0 è¡Œè¯´æ˜ŽçŠ¶æ€å·²å˜åŒ–ï¼ŒæŠ¥æ˜Žç¡®é”™è¯¯", "roles": ["档案管理员"]},
       {"stage": "逾期提醒", "action": "不建定时任务、不发站内信:逾期仅在查询时实时计算(状态=借出中 ä¸” å½’还期限早于今天,逾期天数=今天−归还期限),借阅列表的「逾期」列渲染红色「逾期 N å¤©ã€æ ‡ç­¾ï¼›å¦æä¾› /archive/borrow/overdue-page ç‹¬ç«‹å…¥å£ï¼Œåˆ—表页有「只看逾期」切换在 /page ä¸Ž /overdue-page ä¹‹é—´åˆ‡æ¢æ•°æ®æº", "roles": ["档案管理员"]},
       {"stage": "档案统计", "action": "统计页展示档案总数、在库数、借出中数、逾期未还数四张卡片,以及按分类分布(饼图)、按密级分布(柱图)、按载体分布(饼图)三组分布,数据来自 /archive/stat/overview çš„单次聚合查询", "roles": ["档案管理员", "管理层"]}]},
    {"id": "ai", "name": "AI ä¸šåŠ¡æµç¨‹", "mermaid": "mermaid/11-ai-business-flow.mmd", "color": "#56c8d8",
     "summary": "统一模式:业务侧收集上下文(必要时调 MDM/SRM/CRM API å–名称)→组装 Prompt→chat/chatWithImage→解析 JSON→前端预填或建议;无 Function Calling;RAG çŸ¥è¯†åº“已建成但暂无业务消费。",
     "steps": [
docs/project-business/data/modules.json
@@ -113,10 +113,19 @@
     "upstream": ["会员管理(ums_member å–申报人姓名/单位/职称/电话)", "BPM(流程定义 research_topic_approve / finance_reimbursement_approve)"],
     "downstream": ["system(system_notice ç«‹é¡¹é€šçŸ¥å…¬å‘Š)", "system(附件中心)", "财务(经费拨付生成 finance_reimbursement æŠ¥é”€å•·已对接)"],
     "coreTables": ["research_guideline", "research_application", "research_topic_review", "research_fund_record"]},
    {"key": "arc", "name": "档案管理(独立)", "color": "#7fb3d5", "tag": "ARC", "flow": "archive",
     "positioning": "学会/协会型实体与电子档案全生命周期:分类书架规整 â†’ æ¡£æ¡ˆç™»è®°ä¸Žæ£€ç´¢ â†’ å€Ÿé˜…审批 â†’ å½’还闭环 â†’ å­˜é‡ä¸Žæµè½¬ç»Ÿè®¡ï¼Œå¹¶æŽ¥æ”¶è¯¾é¢˜/会员/财务三方的归档联动",
     "problem": "课题材料、会员材料、财务凭证散落各模块无处归集;纸质档案存放位置靠线下台账,借阅与归还无留痕、逾期无人管;财务等敏感档案无密级管控",
     "features": ["档案分类树(书架·分层分列):会员/课题/会议/财务/文书/党建 6 æ¡åˆå§‹åˆ†ç±»Â·ç¼–码唯一·支持父子层级", "分类默认密级(财务分类=保密)·分类有子分类或下有档案禁删", "档案主表:电子附件 + å®žä½“位置四字段(书架号/层号/列号/册号档案盒)+存放位置描述", "档案编号 DA+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)·借阅单号 JY+同日流水", "密级三档 å…¬å¼€1/内部2/保密3 + ç‹¬ç«‹æƒé™ç‚¹ archive:document:sensitive(无权限时列表与导出强制过滤、详情报错)", "载体三型 ç”µå­1/实体2/电子+实体3", "来源四类 æ‰‹å·¥1/课题2/会员3/财务4(记录来源单号/ID/标题快照便于检索)", "全字段检索:标题/关键词 tags/来源单号/分类/密级/载体/状态/归档日期区间 + Excel å¯¼å‡º", "借阅登记(借阅人/部门/电话/日期/目的/归还期限)+BPM ä¸¤çº§å®¡æ‰¹(档案管理员审核→分管领导审批)·流程 key=archive_borrow_approve", "归还登记:实时更新档案状态回「在库」,形成完整流转闭环", "逾期未还仅查询时实时计算(overdueDays)·列表红色「逾期 N å¤©ã€Tag + /overdue-page ç‹¬ç«‹å…¥å£ + ã€Œåªçœ‹é€¾æœŸã€åˆ‡æ¢Â·ä¸å»º infra_job ä¸å‘站内信", "档案统计:总数/在库/借出中/逾期未还 + æŒ‰åˆ†ç±»/密级/载体三组分布(4 å¡ç‰‡ + 3 å›¾è¡¨)", "对外提供 yudao-module-archive-api(ArchiveDocumentApi:archiveBySource å¹‚ç­‰/ isArchived / getBySource)供课题·会员·财务三方手动归档", "附件走 system é™„件中心(recordType=archive_document / archive_borrow)"],
     "objects": ["档案分类", "档案", "借阅单"],
     "roles": ["档案管理员", "分管领导(借阅审批)", "借阅人", "归档经办人(课题/会员/财务)"],
     "upstream": ["课题管理(已立项申报书·archiveApplication)", "会员管理(正常会员·archiveMember)", "财务报销(已通过报销单·既有 archive å åŠ )", "BPM(流程定义 archive_borrow_approve)"],
     "downstream": ["system(附件中心 storage_attachment)", "来源表回写 archive_document_id"],
     "coreTables": ["archive_category", "archive_document", "archive_borrow"]},
    {"key": "bpm", "name": "协同审批", "color": "#e6a23c", "tag": "BPM", "flow": null,
     "positioning": "全系统单据审批中枢(Flowable)",
     "problem": "多模块单据需要灵活可配的多级审批",
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "15个业务监听器(同一流程 key å…è®¸å¤šä¸ªç›‘听器并行·各自按 key è¿‡æ»¤)", "表单·用户组·抄送·加签"],
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "24个业务状态监听器(*StatusListener·同一流程 key å…è®¸å¤šä¸ªç›‘听器并行·各自按 key è¿‡æ»¤Â·æ¡£æ¡ˆå€Ÿé˜… ArchiveBorrowStatusListener ä¸Žè´¢åŠ¡/课题监听器订阅不同 key äº’不干扰)", "表单·用户组·抄送·加签"],
     "objects": ["流程模型", "流程实例", "审批任务"],
     "roles": ["审批人(按流程配置)", "流程管理员"],
     "upstream": ["各业务提交审批"], "downstream": ["回调更新各业务单据状态"],
docs/project-business/data/objects.json
@@ -48,13 +48,16 @@
    {"name": "排班计划", "module": "设备", "table": "mes_cal_plan", "doc": "班组×班次×日期排班,供生产与考勤引用", "upstream": [], "downstream": ["生产任务", "考勤"], "keywords": "排班 ç­ç»„ ç­æ¬¡ æ—¥åކ"},
    {"name": "报告模板", "module": "质检报告", "table": "qc_report_template", "doc": "智能质检报告模板,启用0/停用1;画布内容存在 qc_report_template_version(草稿0/已发布1/已停用2,已发布不可覆盖),currentVersion æŒ‡å‘当前已发布版本;源文件经 AI å¯¼å…¥è¯†åˆ«æˆè‰ç¨¿åŽç”±äººå·¥ç¡®è®¤æ‰æˆä¸ºç‰ˆæœ¬ï¼›åŒä¸€æ¨¡æ¿é‡å¤å¯¼å…¥æŒ‰ã€æ›¿æ¢ã€‘处理——先删旧附件(连带 blob è¡Œä¸Žç£ç›˜æ–‡ä»¶ï¼‰å†ç»‘新的,不留孤儿文件;删除模板会连带清附件", "upstream": ["人工设计(GrapesJS è®¾è®¡å™¨)", "AI å¯¼å…¥è‰ç¨¿(qc-report/ai-import/draft)"], "downstream": ["模板版本", "报告实例"], "keywords": "报告模板 è´¨æ£€æŠ¥å‘Š template è®¾è®¡å™¨ ç‰ˆæœ¬ AI导入 è¯†åˆ« è‰ç¨¿ æºæ–‡ä»¶æ›¿æ¢"},
    {"name": "报告实例", "module": "质检报告", "table": "qc_report_instance", "doc": "出件产物,编号 QR+yyyyMMdd+4位流水按天独立且业务唯一,可显式指定覆盖(注意 uk_report_no ä¸åŒºåˆ†è½¯åˆ é™¤ï¼Œåˆ æŽ‰çš„号不回收);两条出件入口:通用 generate(调用方自带 context)与 generate-from-qc(数据从 MES è´¨æ£€å•取,权限码 qc-report:instance:generate å·²å…¥ system_menu id=1075416,五道校验按业务优先级只报第一处:105_000 ç±»åž‹éžæ³• / 105_001 å•据不存在 / 105_002 æœªå®Œæˆ / 105_003 æœªåˆ¤å®š / 105_005 æ¨¡æ¿ç±»åž‹ä¸ç¬¦ / 105_006 æ— å¯ç”¨æ£€éªŒé¡¹ï¼›å››å¼ è´¨æ£€å•列表的行操作「出报告」仅在 status=已完成时可见,弹窗只列 reportType ä¸€è‡´ã€æ¨¡æ¿å¯ç”¨ã€ä¸” currentVersion æŒ‡å‘的版本已发布的模板(后端 /qc-report/template/selectable åˆ¤å®šâ€”—停用版本不清空 currentVersion,前端拿分页结果自己推导会漏),结果就地展示并跳本页列表);冻结 data_snapshot(判定后数据,含后端算好的 PASS/FAIL ä¸Žåˆæ ¼çŽ‡ï¼‰ä¸Ž render_html;regenerate åªé‡æ¸²æŽ’版、数据与编号不变;business_id/business_type å­˜ MES è¡¨åï¼ˆmes_qc_iqc ç­‰ï¼‰ä¸Žå•据 ID,可反查、一单可出多份;报告头另带单据原判定 qcResult(数)/qcResultText(中文,合格/特采/不合格退货/不合格报废),与引擎三态判定并存互不覆盖;响应带 undecidableCount=快照中 result ä¸ºç©ºä¸²çš„项数与 warnings(分组标题跳过/指标已删/未录实测值/样品超3),两者都不落库、事后看不到", "upstream": ["报告模板版本", "质检单上下文(通用 generate ç”±è°ƒç”¨æ–¹ä¼ å…¥)", "MES è´¨æ£€å• IQC/IPQC/OQC/RQC(generate-from-qc ç» MesQcReportApi å–æ•°)"], "downstream": ["前端实例页 /qc/report-instance(列表·iframe预览·快照页签·导出PDF)", "PDF归档(system_storage_attachment recordType=qc_report_instance)"], "keywords": "报告实例 å‡ºä»¶ QR编号 å¿«ç…§ å¤çް regenerate PDF归档 æŠ¥å‘Šå®žä¾‹é¡µ å¯¼å‡ºPDF generate-from-qc è´¨æ£€å•出报告 å‡ºæŠ¥å‘Šå¼¹çª— qcResult undecidableCount warnings å¾…判定 åæŸ¥ ä¸šåŠ¡å•æ®ç­›é€‰"},
    {"name": "报销单", "module": "财务报销", "table": "finance_reimbursement", "doc": "单号可自定义(中间不允许空格,与已有单号重复则报错);新增时留空或纯空格由系统生成 BX+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®),修改时留空表示保持不变。status: 0草稿/15审批中/20已通过/35已驳回/40已归档;仅草稿可删、草稿与已驳回可改、仅已通过可归档;提交后由 BPM ç›‘听器按结果回写状态(取消退回草稿),审批中 processInstanceId è¢«å›žå†™ã€‚来源两条:① è´¢åŠ¡æ¨¡å—é¡µé¢äººå·¥å¡«æŠ¥ â‘¡è¯¾é¢˜ç®¡ç†ã€Œç»è´¹æ‹¨ä»˜ã€ç» FinanceReimbursementApi(yudao-module-finance-api)自动建单并立即提交(1:1、金额一致、系统固定 1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他)——后者的审批结果由课题侧 ResearchFundStatusListener è®¢é˜…同一流程回写拨款台账,报销单本身不回写任何外部单据", "upstream": ["课题管理(经费拨付·经 FinanceReimbursementApi è‡ªåŠ¨å»ºå•)", "人工填报"], "downstream": ["报销明细", "发票附件(storage_attachment·recordType=finance_reimbursement)", "课题拨款台账(仅经 BPM äº‹ä»¶å›žå†™çŠ¶æ€)"], "keywords": "报销单 æŠ¥é”€ å‘票 reimbursement BX å½’æ¡£ è´¢åŠ¡ è´¹ç”¨ è‡ªå®šä¹‰å•号 è¯¾é¢˜ç»è´¹æ‹¨ä»˜ è‡ªåŠ¨å»ºå• FinanceReimbursementApi"},
    {"name": "报销单", "module": "财务报销", "table": "finance_reimbursement", "doc": "单号可自定义(中间不允许空格,与已有单号重复则报错);新增时留空或纯空格由系统生成 BX+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®),修改时留空表示保持不变。status: 0草稿/15审批中/20已通过/35已驳回/40已归档;仅草稿可删、草稿与已驳回可改、仅已通过可归档;提交后由 BPM ç›‘听器按结果回写状态(取消退回草稿),审批中 processInstanceId è¢«å›žå†™ã€‚来源两条:① è´¢åŠ¡æ¨¡å—é¡µé¢äººå·¥å¡«æŠ¥ â‘¡è¯¾é¢˜ç®¡ç†ã€Œç»è´¹æ‹¨ä»˜ã€ç» FinanceReimbursementApi(yudao-module-finance-api)自动建单并立即提交(1:1、金额一致、系统固定 1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他)——后者的审批结果由课题侧 ResearchFundStatusListener è®¢é˜…同一流程回写拨款台账,报销单本身不回写任何外部单据;archive_document_id éžç©ºè¡¨ç¤ºå·²å½’æ¡£", "upstream": ["课题管理(经费拨付·经 FinanceReimbursementApi è‡ªåŠ¨å»ºå•)", "人工填报"], "downstream": ["报销明细", "发票附件(storage_attachment·recordType=finance_reimbursement)", "课题拨款台账(仅经 BPM äº‹ä»¶å›žå†™çŠ¶æ€)", "档案管理(既有归档动作同事务叠加·生成「保密 3」级财务档案并回写 archive_document_id)"], "keywords": "报销单 æŠ¥é”€ å‘票 reimbursement BX å½’æ¡£ è´¢åŠ¡ è´¹ç”¨ è‡ªå®šä¹‰å•号 è¯¾é¢˜ç»è´¹æ‹¨ä»˜ è‡ªåŠ¨å»ºå• FinanceReimbursementApi è´¢åŠ¡æ¡£æ¡ˆ ä¿å¯† archive_document_id"},
    {"name": "报销明细", "module": "财务报销", "table": "finance_reimbursement_item", "doc": "报销单的费用行:费用类型(1差旅/2办公/3招待/4交通/5其他)、发票号码、发票金额、发票日期、开票方、费用说明;随主表一次性提交(先删后增全量覆盖),提交时必须剥掉行 id ä¸Ž reimbursementId â€”— æ˜Žç»†åˆ é™¤æ˜¯è½¯åˆ ï¼Œå›žä¼ åŽŸ id ä¼šä¸Žå·²è½¯åˆ è¡Œä¸»é”®å†²çª", "upstream": ["报销单"], "downstream": [], "keywords": "报销明细 è´¹ç”¨æ˜Žç»† è´¹ç”¨ç±»åž‹ å‘票金额 item æ˜Žç»†è¡Œ å·®æ—…"},
    {"name": "入会申请单", "module": "会员管理", "table": "ums_application", "doc": "单号 SQ+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®),后端生成不可自定义;memberType 1个人/2单位决定字段分叉(个人用 workUnit/title,单位用 legalPerson/organizationCode);memberGrade æŒ‰ç±»åž‹å–不同字典;status: 0草稿/15审批中/20已通过/35已驳回。仅草稿可删(连带清附件)、草稿与已驳回可改可重提;提交后由 BPM ç›‘听器按结果回写(取消退回草稿),审批中回写 processInstanceId;审批通过触发自动建会员与系统账号。独立模块,不与其它业务模块产生单据关联", "upstream": ["人工填报(申请人)", "证明材料附件(storage_attachment·recordType=ums_application)"], "downstream": ["会员(审批通过自动创建)", "BPM æµç¨‹å®žä¾‹(ums_membership_approve åˆ†ç±»)"], "keywords": "入会申请 å…¥ä¼š ä¼šå‘˜ç”³è¯· application SQ ç§˜ä¹¦å¤„ ç†äº‹ä¼š å®¡æ‰¹ ä¼šå‘˜ç±»åž‹ ä¸ªäºº å•位 æ³•定代表人 ç ”究方向 ç”³è¯·ç­‰çº§"},
    {"name": "会员", "module": "会员管理", "table": "ums_member", "doc": "单号 HY+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);两个来源:①入会申请审批通过自动创建(回填 applicationId ä¸Ž userName/系统账号) â‘¡ä¼šå‘˜åˆ—表手工新增。status: 1正常/0暂停;memberGrade æŒ‰ memberType å–对应字典,校验规则是单位会员(type=2)不可选『理事长』(grade=5)。自动建档时入会日期=当天、不搬运 contactUser/unitIntro/expireDate(申请单无对应字段);自动建的系统账号用户名为联系电话且初始密码同为联系电话,部门固定为「学会会员」;手机号重复会明确报错且整单事务回滚", "upstream": ["入会申请单(审批通过)", "人工新增", "附件(storage_attachment·recordType=ums_member)"], "downstream": ["会员通讯录(按 regionCode/industry æ£€ç´¢)", "system_users(登录账号)"], "keywords": "会员 member HY ä¼šå‘˜ç¼–号 ä¼šå‘˜ç­‰çº§ ä¼šå‘˜ç±»åž‹ é€šè®¯å½• åœ°åŒº è¡Œä¸š å…¥ä¼šæ—¥æœŸ æœ‰æ•ˆæœŸ"},
    {"name": "会员", "module": "会员管理", "table": "ums_member", "doc": "单号 HY+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);两个来源:①入会申请审批通过自动创建(回填 applicationId ä¸Ž userName/系统账号) â‘¡ä¼šå‘˜åˆ—表手工新增。status: 1正常/0暂停;memberGrade æŒ‰ memberType å–对应字典,校验规则是单位会员(type=2)不可选『理事长』(grade=5)。自动建档时入会日期=当天、不搬运 contactUser/unitIntro/expireDate(申请单无对应字段);自动建的系统账号用户名为联系电话且初始密码同为联系电话,部门固定为「学会会员」;手机号重复会明确报错且整单事务回滚;archive_document_id éžç©ºè¡¨ç¤ºå·²å½’档(归档后列表页隐藏「归档」按钮)", "upstream": ["入会申请单(审批通过)", "人工新增", "附件(storage_attachment·recordType=ums_member)"], "downstream": ["会员通讯录(按 regionCode/industry æ£€ç´¢)", "档案管理(手动「归档」·仅正常状态可归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id)", "system_users(登录账号)"], "keywords": "会员 member HY ä¼šå‘˜ç¼–号 ä¼šå‘˜ç­‰çº§ ä¼šå‘˜ç±»åž‹ é€šè®¯å½• åœ°åŒº è¡Œä¸š å…¥ä¼šæ—¥æœŸ æœ‰æ•ˆæœŸ å½’æ¡£ archive_document_id"},
    {"name": "课题指南", "module": "课题管理", "table": "research_guideline", "doc": "指南编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);category 1重点课题/2一般课题;publishStatus 0草稿/20已发布/30已截止,仅草稿可改可删,publish å‘布回写 publishTime,close æˆªæ­¢ç½® 30;已产生申报单(applicationMapper.selectCountByGuidelineId å¤§äºŽ 0)的指南禁止删除并在提示中报出该指南下已有多少张申报单;正文 content ä¸ºé•¿æ–‡æœ¬ï¼›é™„ä»¶ recordType=research_guideline", "upstream": ["人工维护(学会秘书处)", "指南附件(storage_attachment·recordType=research_guideline)"], "downstream": ["课题申报书(申报时引用且必须已发布并在申报期内)"], "keywords": "课题指南 æŒ‡å— å‘布 æˆªæ­¢ é‡ç‚¹è¯¾é¢˜ ä¸€èˆ¬è¯¾é¢˜ GL æŒ‡å—编号 ç”³æŠ¥æœŸ ç”³æŠ¥èµ·æ­¢"},
    {"name": "课题申报书", "module": "课题管理", "table": "research_application", "doc": "申报编号 SB+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);guidelineId å…³è”指南、memberId å…³è” ums_member(软关联无外键,会员删除后会留下悬空 memberId éœ€å®¹é”™æ˜¾ç¤º);applicantName/workUnit/jobTitle/contactPhone/contactEmail ç”±é€‰ä¼šå‘˜è‡ªåŠ¨å¸¦å‡ºå¯æ‰‹æ”¹ï¼›category å†—余自指南且不信任前端传值;budgetAmount ç”³è¯·ç»è´¹ä¸ºæ‹¨ä»˜ä¸Šé™ï¼›status: 0草稿/15审批中/20已立项/35已驳回。仅草稿可删(连带清附件)、草稿与已驳回可改可重提;submit å‰é€æ¡æ ¡éªŒã€ŒæŒ‡å—已发布」与「现在在申报期内」,任一不满足即拒绝并按维度给出指南名与实际值;审批通过由监听器生成立项编号 researchNo(LX+同日流水)与 approveTime(已有编号幂等跳过);reviewAvgScore/reviewPass ä¸ºè¯„审汇总回写值,明细删空时清成未汇总;noticeId è®°å½•已发布的立项通知公告,重复发布被拦截", "upstream": ["课题指南(必须已发布且在申报期内)", "会员(ums_member·带出申报人信息)", "申报附件(storage_attachment·recordType=research_application)"], "downstream": ["专家评审明细", "经费拨付记录", "立项通知(system_notice å…¬å‘Š)", "BPM æµç¨‹å®žä¾‹(research_topic_approve åˆ†ç±»)", "附件中心"], "keywords": "课题申报 ç”³æŠ¥ä¹¦ ç”³æŠ¥å• SB ç”³æŠ¥ç¼–号 ç«‹é¡¹ ç«‹é¡¹ç¼–号 LX ç”³è¯·ç»è´¹ ç”³æŠ¥ä¹¦æ­£æ–‡ è¯¾é¢˜ç»„成员 ä¸“家评审 è¯„审汇总 ç«‹é¡¹é€šçŸ¥ å‘布通知 æäº¤å®¡æ‰¹ é©³å›ž é‡æ"},
    {"name": "课题申报书", "module": "课题管理", "table": "research_application", "doc": "申报编号 SB+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);guidelineId å…³è”指南、memberId å…³è” ums_member(软关联无外键,会员删除后会留下悬空 memberId éœ€å®¹é”™æ˜¾ç¤º);applicantName/workUnit/jobTitle/contactPhone/contactEmail ç”±é€‰ä¼šå‘˜è‡ªåŠ¨å¸¦å‡ºå¯æ‰‹æ”¹ï¼›category å†—余自指南且不信任前端传值;budgetAmount ç”³è¯·ç»è´¹ä¸ºæ‹¨ä»˜ä¸Šé™ï¼›status: 0草稿/15审批中/20已立项/35已驳回。仅草稿可删(连带清附件)、草稿与已驳回可改可重提;submit å‰é€æ¡æ ¡éªŒã€ŒæŒ‡å—已发布」与「现在在申报期内」,任一不满足即拒绝并按维度给出指南名与实际值;审批通过由监听器生成立项编号 researchNo(LX+同日流水)与 approveTime(已有编号幂等跳过);reviewAvgScore/reviewPass ä¸ºè¯„审汇总回写值,明细删空时清成未汇总;noticeId è®°å½•已发布的立项通知公告,重复发布被拦截;archive_document_id éžç©ºè¡¨ç¤ºå·²å½’档(归档后列表页隐藏「归档」按钮)", "upstream": ["课题指南(必须已发布且在申报期内)", "会员(ums_member·带出申报人信息)", "申报附件(storage_attachment·recordType=research_application)"], "downstream": ["专家评审明细", "经费拨付记录", "立项通知(system_notice å…¬å‘Š)", "BPM æµç¨‹å®žä¾‹(research_topic_approve åˆ†ç±»)", "档案管理(手动「归档」·仅已立项可归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id)", "附件中心"], "keywords": "课题申报 ç”³æŠ¥ä¹¦ ç”³æŠ¥å• SB ç”³æŠ¥ç¼–号 ç«‹é¡¹ ç«‹é¡¹ç¼–号 LX ç”³è¯·ç»è´¹ ç”³æŠ¥ä¹¦æ­£æ–‡ è¯¾é¢˜ç»„成员 ä¸“家评审 è¯„审汇总 ç«‹é¡¹é€šçŸ¥ å‘布通知 æäº¤å®¡æ‰¹ é©³å›ž é‡æ å½’æ¡£ archive_document_id"},
    {"name": "专家评审明细", "module": "课题管理", "table": "research_topic_review", "doc": "一位专家一行的一主多行子表:evaluatorName/evaluatorUnit/evaluatorTitle + topicScore é€‰é¢˜ä»·å€¼ä¸Žæ„ä¹‰(权重0.3) + planScore ç ”究方案可行性(0.3) + teamScore å›¢é˜Ÿä¸Žç ”究基础(0.2) + resultScore é¢„期成果与效益(0.2) + conclusion 1推荐立项/2不推荐立项 + opinion è¯„审意见 + reviewTime。compositeScore ç”±åŽç«¯ weightedScore è®¡ç®—、ranking ç”±åŽç«¯æŒ‰åŠ æƒåˆ†é™åºé‡æŽ’ï¼Œå‡ä¸æŽ¥å—å‰ç«¯ä¼ å€¼ï¼›æ¯æ¬¡å¢žåˆ æ”¹åŽè°ƒ recalculate å›žå†™ä¸»å• reviewAvgScore(全部专家加权分均值·保留2位)与 reviewPass(均分达标 ä¸” å¤šæ•°ç»“论为推荐立项时为 1);四项分值超出 0~100 æ—¶æç¤ºæŒ‡å‡ºå…·ä½“维度与实际值;仅在申报单 status=15 å®¡æ‰¹ä¸­å¯å¢žåˆ æ”¹", "upstream": ["课题申报书(仅审批中可写)"], "downstream": ["课题申报书(回写 reviewAvgScore/reviewPass)"], "keywords": "专家评审 è¯„审明细 è¯„分 åŠ æƒ æƒé‡ æŽ’名 ranking compositeScore æŽ¨èç«‹é¡¹ è¯„审意见 å‡åˆ†"},
    {"name": "档案分类", "module": "档案管理", "table": "archive_category", "doc": "树形书架分类(parentId·0=顶级),承载「分层分列」规整:6 æ¡åˆå§‹åˆ†ç±» ä¼šå‘˜æ¡£æ¡ˆ member / è¯¾é¢˜æ¡£æ¡ˆ research / ä¼šè®®æ¡£æ¡ˆ meeting / è´¢åŠ¡æ¡£æ¡ˆ finance / æ–‡ä¹¦æ¡£æ¡ˆ document / å…šå»ºæ¡£æ¡ˆ party;code å…¨å±€å”¯ä¸€ uk_code;defaultSecretLevel ä¸ºè¯¥åˆ†ç±»ä¸‹æ¡£æ¡ˆçš„默认密级(1公开/2内部/3保密,财务分类=3);status 0启用/1停用。仅可改可删,有子分类禁删、该分类下有档案禁删、父分类不能设为自身或其子分类。会议/党建档案系统内无来源模块,仅支持手工录入档案", "upstream": ["人工维护(档案管理员)"], "downstream": ["档案(新增时带出默认密级)"], "keywords": "档案分类 ä¹¦æž¶ åˆ†å±‚分列 ä¼šå‘˜æ¡£æ¡ˆ è¯¾é¢˜æ¡£æ¡ˆ ä¼šè®®æ¡£æ¡ˆ è´¢åŠ¡æ¡£æ¡ˆ æ–‡ä¹¦æ¡£æ¡ˆ å…šå»ºæ¡£æ¡ˆ category é»˜è®¤å¯†çº§"},
    {"name": "档案", "module": "档案管理", "table": "archive_document", "doc": "档案主表,编号 DA+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)。电子与实体兼具:电子走 system é™„件中心 recordType=archive_document(表内不存文件地址)、实体记 shelfNo ä¹¦æž¶å·/layerNo å±‚号/columnNo åˆ—号/boxNo å†Œå·æ¡£æ¡ˆç›’ + storePlace å­˜æ”¾ä½ç½®æè¿°ã€‚carrierType 1电子/2实体/3电子+实体;secretLevel 1公开/2内部/3保密(为 3 ä¸”当前用户无 archive:document:sensitive æ—¶è¯¦æƒ…报错、列表与导出强制过滤);sourceType 1手工/2课题/3会员/4财务 + sourceNo/sourceId/sourceTitle è®°å½•来源快照以便检索;status 10在库/20借出中(借出中禁改、存在未归还借阅单时禁删)。来源为 2/3/4 çš„æ¡£æ¡ˆç”±ä¸‰æ–¹åˆ—表页「归档」按钮经 ArchiveDocumentApi.archiveBySource ç”Ÿæˆ(幂等),并回写来源表 archive_document_id", "upstream": ["档案分类", "档案登记(人工)", "课题归档(已立项申报书)", "会员归档(正常会员)", "财务归档(已通过报销单)"], "downstream": ["借阅单", "附件中心(recordType=archive_document)", "来源表 archive_document_id å›žå†™"], "keywords": "档案 æ–‡æ¡£ document DA æ¡£æ¡ˆç¼–号 å¯†çº§ ä¿å¯† å…¬å¼€ å†…部 æ•æ„Ÿæ¡£æ¡ˆ è½½ä½“ ç”µå­ å®žä½“ ä¹¦æž¶å· å±‚号 åˆ—号 å†Œå· æ¡£æ¡ˆç›’ å½’档日期 åœ¨åº“ å€Ÿå‡ºä¸­ æ£€ç´¢ å…³é”®è¯ tags æ¥æºå•号 å½’档联动"},
    {"name": "借阅单", "module": "档案管理", "table": "archive_borrow", "doc": "借阅与归还合一,编号 JY+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)。记录借阅人/部门/电话、借阅日期、借阅目的、归还期限(returnDeadline·逾期实时计算基准);documentNo/documentTitle ä¸ºæ¡£æ¡ˆå¿«ç…§ã€‚status 0草稿/15审批中/20借出中/30已归还/35已驳回:仅 0 ä¸Ž 35 å¯æ”¹å¯é‡æã€ä»… 0 å¯åˆ ã€ä»… 20 å¯ç™»è®°å½’还。submit å‰æ ¡éªŒåŒä¸€æ¡£æ¡ˆä¸å¾—已有 15/20 çš„借阅单(命中报已借用并带档案名与已存在借阅单号);流程 key archive_borrow_approve ä¸¤çº§(档案管理员审核→分管领导审批),前端不写死 key。审批通过由 ArchiveBorrowStatusListener å›žå†™ 20+approveTime å¹¶æ¡ä»¶æ›´æ–°æ¡£æ¡ˆ SET status=20 WHERE status=10(影响 0 è¡ŒæŠ¥æ¡£æ¡ˆå·²è¢«å ç”¨å¹¶å›žæ»š);归还登记条件更新 20→30 å¹¶å›žå†™ returnTime/归还确认人。逾期不落库,查询时实时算 overdueDays å¹¶åœ¨åˆ—表渲染红色「逾期 N å¤©ã€ï¼Œå¦æœ‰ /overdue-page ç‹¬ç«‹å…¥å£", "upstream": ["档案(仅 åœ¨åº“ 10 å¯é€‰)", "BPM(流程 archive_borrow_approve)", "人工登记"], "downstream": ["档案(状态 20 å€Ÿå‡ºä¸­ / å½’还回 10 åœ¨åº“)"], "keywords": "借阅 å€Ÿé˜…单 å€Ÿé˜…登记 å½’还 å½’还登记 é€¾æœŸ é€¾æœŸæœªè¿˜ å€Ÿé˜…人 å€Ÿé˜…目的 å½’还期限 JY å€Ÿé˜…单号 æ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ æµè½¬é—­çޝ"},
    {"name": "经费拨付记录", "module": "课题管理", "table": "research_fund_record", "doc": "一主多行的拨款台账:batchName æ‰¹æ¬¡åç§°/amount æ‹¨ä»˜é‡‘额/payDate æ‹¨ä»˜æ—¥æœŸ/payee æ”¶æ¬¾å•位/remark,凭证附件走 system é™„件中心 recordType=research_fund_record(表内不存文件地址)。status: 0待拨付/15报销审批中/20已拨付/30已驳回,由系统与 BPM é©±åŠ¨ï¼Œæ–°å¢ž/修改接口已移除 status å…¥å‚(防伪造「已拨付」)。登记即一条龙:落台账 â†’ åŒä¸€æ‰¹ blob åŒç»‘ â†’ ç» FinanceReimbursementApi è‡ªåŠ¨ç”Ÿæˆ 1:1 çš„ finance_reimbursement å¹¶ç«‹å³æäº¤ BPM â†’ å›žå†™ reimbursement_id/reimbursement_no å¹¶ç½® 15;审批结果由监听器回写:通过→20、驳回→30(+reject_reason/reject_time)、取消流程→退回 0(发起人撤回只退回发起节点、流程未结束,状态不变)。仅申报单 status=20 å·²ç«‹é¡¹å¯ç™»è®°ï¼›å‡­è¯å¿…填;金额校验:已登记金额(该单全部记录求和) + æœ¬æ¬¡é‡‘额 ä¸å¾—超出申报单 budgetAmount,超出时提示报出本次、已登记、合计、预算四项数值;仅「待拨付」可删(联动删除财务侧草稿报销单,非草稿则拒绝),「待拨付」「已驳回」可改并重新发起报销(已驳回改后清空 reject_reason/reject_time),编辑时把本行金额加回可用余额展示避免误解。与报销单为软关联(无外键),财务侧直接删单会留下悬空 reimbursement_id,此时修改报「报销单已不存在,请删除本条记录后重新登记」", "upstream": ["课题申报书(必须已立项)", "财务报销单(1:1 è‡ªåŠ¨ç”ŸæˆÂ·è½¯å…³è” reimbursement_id)"], "downstream": ["拨付凭证(storage_attachment·recordType=research_fund_record)", "财务报销单(生成并提交出账·审批结果回写本台账)"], "keywords": "经费拨付 æ‹¨æ¬¾ æ‹¨ä»˜è®°å½• æ‰¹æ¬¡ æ‹¨ä»˜é‡‘额 å‡­è¯ å¾…拨付 æŠ¥é”€å®¡æ‰¹ä¸­ å·²æ‹¨ä»˜ å·²é©³å›ž é©³å›žåŽŸå›  è´¢åŠ¡æŠ¥é”€å•å· reimbursement_id æ”¶æ¬¾å•位 èµ„助金额 é¢„ç®— è¶…额 é‡æ–°å‘起报销"}
  ]
}
docs/project-business/data/roles.json
@@ -23,6 +23,10 @@
    {"role": "入会申请人/会员管理员", "modules": ["UMS"], "operations": ["填报与修改入会申请(个人/单位)", "上传证明材料附件", "提交 BPM å®¡æ‰¹", "会员列表维护与会员通讯录检索"], "data": ["ums_application", "ums_member"], "scope": "权限码 ums:application:create/update/delete/submit Â· ums:member:create/update/delete/query Â· ums:member:directory;无数据权限隔离(全员可查通讯录,维护类按钮另凭权限码)"},
    {"role": "秘书处/理事会(审批人)", "modules": ["UMS", "BPM"], "operations": ["入会申请两级审批:秘书处初审 â†’ ç†äº‹ä¼šå®¡è®®"], "data": ["ums_application(状态回写)", "bpm_task"], "scope": "审批人由 BPM æµç¨‹å®šä¹‰é…ç½®ï¼ŒåŽç«¯ä¸å†™æ­»ï¼›é€šè¿‡åŽç³»ç»Ÿè‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å·"},
    {"role": "课题申报人/课题管理员", "modules": ["RES"], "operations": ["课题指南发布与截止(课题管理员)", "新建与修改课题申报书(选会员带出申报人信息、上传申报附件)", "提交 BPM å®¡æ‰¹ã€é©³å›žåŽä¿®æ”¹é‡æ", "每月/每批次登记经费拨付记录与凭证", "发布立项通知"], "data": ["research_guideline", "research_application", "research_topic_review", "research_fund_record"], "scope": "权限码 research:guideline:create/update/delete/publish Â· research:application:create/update/delete/submit/notice Â· research:review:create/update/delete Â· research:fund:create/update/delete;无数据权限隔离(列表不按申请人过滤),按钮凭权限码控制"},
    {"role": "档案管理员", "modules": ["ARC"], "operations": ["档案分类(书架)维护:分层建类、设默认密级、启用停用", "档案登记与多条件检索/导出(电子附件 + å®žä½“位置四字段、密级三档)", "借阅登记、提交审批", "归还登记与归还备注", "查看档案统计(存量与流转)"], "data": ["archive_category", "archive_document", "archive_borrow"], "scope": "权限码 archive:category:create/update/delete/query Â· archive:document:create/update/delete/query/export Â· archive:borrow:create/update/delete/query/submit/return Â· archive:stat:query;密级=保密(3) çš„æ¡£æ¡ˆå¦éœ€ archive:document:sensitive æ‰èƒ½æŸ¥çœ‹ï¼Œæ— è¯¥æƒé™æ—¶åˆ—表与导出强制过滤;无数据权限隔离(列表不按人过滤),按钮凭权限码控制"},
    {"role": "借阅人", "modules": ["ARC"], "operations": ["登记借阅单(选在库档案、填借阅人信息/借阅日期/借阅目的/归还期限)", "提交 BPM å€Ÿé˜…审批", "查看借阅进度(跳 BPM æµç¨‹å®žä¾‹è¯¦æƒ…)"], "data": ["archive_borrow", "archive_document(仅 åœ¨åº“ 10 å¯é€‰)"], "scope": "权限码 archive:borrow:create/query/submit;仅草稿(0)与已驳回(35)可改可重提、仅草稿可删;同一档案不得同时存在审批中(15)或借出中(20)的借阅单"},
    {"role": "档案管理员/分管领导(借阅审批人)", "modules": ["ARC", "BPM"], "operations": ["借阅审批两级:档案管理员审核 â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹", "在审批中心通过自定义表单只读查看借阅单与档案信息"], "data": ["archive_borrow(状态回写)", "archive_document(状态随审批结果条件更新)", "bpm_task"], "scope": "审批人与审批方法在 BPM æµç¨‹å®šä¹‰ archive_borrow_approve å†…配置(默认依次审批·角色兜底),后端不写死审批人,部署后由管理员在流程设计器按实际组织调整"},
    {"role": "归档经办人(课题/会员/财务)", "modules": ["ARC", "RES", "UMS", "FIN"], "operations": ["在课题申报书列表对「已立项」单据点「归档」", "在会员列表对「正常」会员点「归档」", "在报销单列表对「已通过」单据点「归档」(与其既有 20→40 å½’档同事务叠加)"], "data": ["archive_document(生成)", "research_application / ums_member / finance_reimbursement(回写 archive_document_id)"], "scope": "权限码 research:application:archive Â· ums:member:archive Â· finance:reimbursement:archive;调用 yudao-module-archive-api çš„ archiveBySource å¹‚等生成档案,重复点击由来源侧按错误码拦截并给出已生成档案编号"},
    {"role": "评审专家/学会秘书处·理事会(课题审批人)", "modules": ["RES", "BPM"], "operations": ["在审批中(15)录入本人的专家评分明细(四项分值 + ç»“论 + æ„è§)", "秘书处形式审查 â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ ä¸‰èŠ‚ç‚¹é¡ºåºå®¡æ‰¹", "在审批中心通过自定义表单只读查看申报书正文、评审明细与拨付记录"], "data": ["research_topic_review", "research_application(状态回写)", "bpm_task"], "scope": "评分录入要求申报单处于审批中(15)·由课题管理员或专家依据 research:review:* æƒé™ç æ‰§è¡Œï¼›å®¡æ‰¹äººä¸Žå®¡æ‰¹æ–¹æ³•在 BPM æµç¨‹å®šä¹‰ research_topic_approve å†…配置(默认依次审批·角色兜底),后端不写死审批人,部署后由管理员在流程设计器按学会实际组织调整"}
  ]
}
docs/project-business/index.html
@@ -10,7 +10,7 @@
  --line:#24344f; --text:#dbe4f0; --muted:#8fa1b8;
  --c-base:#8a94a6; --c-sale:#4f9cff; --c-sup:#c9a86a; --c-erp:#5fc79e;
  --c-prod:#e0b34f; --c-wm:#7e6bd6; --c-qc:#e06c6c; --c-dv:#59a8b8;
  --c-as:#c78bd6; --c-hrm:#9db85a; --c-bpm:#e6a23c; --c-ai:#56c8d8; --c-fin:#3ec9a7; --c-ums:#d6955c; --c-res:#e070a8;
  --c-as:#c78bd6; --c-hrm:#9db85a; --c-bpm:#e6a23c; --c-ai:#56c8d8; --c-fin:#3ec9a7; --c-ums:#d6955c; --c-res:#e070a8; --c-arch:#7fb3d5;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
@@ -114,6 +114,7 @@
  <a data-t="flow-finance"><span class="dot" style="background:var(--c-fin)"></span>财务报销流程</a>
  <a data-t="flow-ums"><span class="dot" style="background:var(--c-ums)"></span>会员入会流程</a>
  <a data-t="flow-research"><span class="dot" style="background:var(--c-res)"></span>课题管理流程</a>
  <a data-t="flow-archive"><span class="dot" style="background:var(--c-arch)"></span>档案管理流程</a>
  <div class="group">结构与数据</div>
  <a data-t="relation"><span class="dot" style="background:var(--c-bpm)"></span>系统关系</a>
  <a data-t="dataflow"><span class="dot" style="background:var(--c-fin)"></span>数据流</a>
@@ -143,6 +144,7 @@
<section class="page" id="flow-finance"></section>
<section class="page" id="flow-ums"></section>
<section class="page" id="flow-research"></section>
<section class="page" id="flow-archive"></section>
<section class="page" id="relation">
  <h2 class="pt">系统关系</h2>
  <p class="desc">模块之间的调用与数据依赖:实线=单据/数据流向,虚线=审批回调与辅助能力。</p>
@@ -195,6 +197,7 @@
      "FIN(发票报销,独立模块)",
      "UMS(学会会员,独立模块)",
      "RES(科研课题,独立模块)",
      "ARC(档案管理,独立模块)",
      "CRM(客户关系)",
      "SRM(供应商关系)",
      "HRM(人力资源)",
@@ -206,14 +209,14 @@
    "businessPositioning": "让工厂的订单、物料、生产、库存、质量、人员、资金在一个系统内闭环流转,管理层可通过 BI å¤§å±æŽŒæ¡å…¨å±€ã€‚",
    "coreChain": "CRM å•†æœº/合同 â†’ ERP é”€å”®è®¢å• â†’ MES ä¸»ç”Ÿäº§è®¡åˆ’/工单/报工 â†’ MES è´¨æ£€ä¸Žä»“储 â†’ é”€å”®å‡ºåº“发货 â†’ å¼€ç¥¨å›žæ¬¾ â†’ å”®åŽé—­çޝ",
    "techBase": "Spring Boot + MyBatis + Flowable + Redis + Quartz/RocketMQ/Kafka + Milvus å‘量库 + é€šä¹‰åƒé—®",
    "database": "MySQL 8.4,运行库 mom-jssm å…± 427 å¼ è¡¨ï¼ˆå« system_*、bpm_*、act_* åº•座表与 mes_*/erp_*/crm_*/srm_*/hrm_*/mdm_*/ai_*/finance_*/ums_*/research_* ä¸šåŠ¡è¡¨ï¼‰ï¼Œå¤šç§Ÿæˆ·å·²å…³é—­",
    "database": "MySQL 8.4,运行库 mom-jssm å…± 430 å¼ è¡¨ï¼ˆå« system_*、bpm_*、act_* åº•座表与 mes_*/erp_*/crm_*/srm_*/hrm_*/mdm_*/ai_*/finance_*/ums_*/research_*/archive_* ä¸šåŠ¡è¡¨ï¼‰ï¼Œå¤šç§Ÿæˆ·å·²å…³é—­",
    "frontend": "mom-pro2-before(Vue3 ç®¡ç†åŽå°ï¼ŒåŒä»“库存放,只读参考)"
  },
  "stats": {
    "businessModules": 16,
    "coreObjects": 56,
    "documentFlows": 9,
    "approvalListeners": 15,
    "businessModules": 17,
    "coreObjects": 59,
    "documentFlows": 10,
    "approvalListeners": 24,
    "aiCapabilities": 8,
    "wmBillTypes": 15
  },
@@ -344,10 +347,18 @@
    "课题管理:经费拨付台账的「已拨付(20)」等价于报销单「已通过(20)」,不含出纳实际付款动作;若日后需要独立的「已付款」状态,需另增状态与审批节点【二期可扩展】",
    "课题管理:申报单与 ums_member æ˜¯è½¯å…³è”(无外键),会员被删除后申报单会留下悬空 memberId;成员列表需容错展示「会员已删除」【待确认是否加外键或删除保护】",
    "课题管理:BPM æµç¨‹ research_topic_approve ä¸‰èŠ‚ç‚¹é»˜è®¤å®¡æ‰¹äººä¸ºã€Œè§’è‰²=超级管理员(1) + ä¾æ¬¡å®¡æ‰¹ã€ï¼Œè¯¥ dev åº“超级管理员角色下有 4 ä¸ªç”¨æˆ·ï¼ˆadmin/张娟/马芳/马贞),每个节点需 4 äººä¼šç­¾æ‰æŽ¨è¿›ï¼›å…¨æ–°å®‰è£…库该角色仅 admin ä¸€äººã€æµç¨‹å¯å¼€ç®±å³ç”¨ã€‚dev åº“需减少超级管理员用户或改在 BPM è®¾è®¡å™¨ä¸­ä¸ºæ¯èŠ‚ç‚¹æŒ‡å®šå®¡æ‰¹äººã€çŽ¯å¢ƒé…ç½®é¡¹ï¼Œéžä»£ç ç¼ºé™·ã€‘",
    "课题管理:立项通知复用 system_notice ç³»ç»Ÿå…¬å‘Šï¼Œä¸æ–°å¢ž system_notify_template、不发站内信;如需短信/站内信定向通知需另行配置【待确认通知渠道】"
    "课题管理:立项通知复用 system_notice ç³»ç»Ÿå…¬å‘Šï¼Œä¸æ–°å¢ž system_notify_template、不发站内信;如需短信/站内信定向通知需另行配置【待确认通知渠道】",
    "档案管理:档案借阅 BPM æµç¨‹ archive_borrow_approve ä¸ºæ‰‹å·¥å¯¼å‡ºéƒ¨ç½²äº§ç‰©ï¼ˆ_archive_bpm_export.sql å†… act_*/bpm è¡¨çš„ UUID ä¸Žæ—¶é—´æˆ³åªèƒ½åœ¨éƒ¨ç½²æ—¶ç”Ÿæˆï¼Œæ— æ³•预写),必须先在 BPM æ¨¡åž‹éƒ¨ç½²å†å¯¼å‡ºå†åˆå¹¶è¿› config_export_all é˜¶æ®µä¸€ï¼›é¡ºåºé”™ä¼šå¯¼è‡´æ–°åº“初始化时该流程缺失【部署顺序硬约束,已在文件头与 README åŒé‡æ ‡æ³¨ã€‘",
    "档案管理:菜单 1075600~1075699 ä¸Žå„ archive:* æƒé™ç‚¹ä»…在归档脚本中,普通角色未配 system_role_menu,非超管登录看不到「档案管理」目录【待确认是否补授权,与财务报销/会员管理同类问题】",
    "档案管理:档案与来源单据(课题申报书/会员/报销单)为软关联,来源单据被删除后档案的 source_id/source_no ä¼šæˆä¸ºæ‚¬ç©ºå¿«ç…§ï¼›æ¡£æ¡ˆæœ¬èº«ä¸éšæ¥æºå•据删除而删除【待确认是否需要删除保护或外键】",
    "档案管理:需求中的「学会文件、会议纪要、调研报告、学术论文、合同协议」等电子文档由「文书档案」分类承载,系统内无对应来源模块,仅支持手工录入并挂电子附件;合同档案未纳入自动归档(用户明确排除)【已确认的边界】",
    "档案管理:逾期提醒按用户决策仅查询时实时计算(不建 infra_job、不发站内信),借阅列表「逾期」列与 /overdue-page ç‹¬ç«‹å…¥å£å‘ˆçŽ°ï¼›è‹¥æ—¥åŽéœ€è¦åˆ°æœŸä¸»åŠ¨æŽ¨é€ï¼Œéœ€å¦åŠ å®šæ—¶ä»»åŠ¡ä¸Žé€šçŸ¥æ¸ é“ã€ç”¨æˆ·å·²ç¡®è®¤çš„ç®€åŒ–ï¼ŒäºŒæœŸå¯æ‰©å±•ã€‘",
    "档案管理:「同一档案仅允许一条未归还借阅单」由应用层 submit æ ¡éªŒ + å®¡æ‰¹é€šè¿‡æ—¶æ¡ä»¶æ›´æ–°ï¼ˆUPDATE ... WHERE status=10)共同保证,MySQL æ— æ³•对「部分状态唯一」建约束;校验与更新之间的理论窗口已收敛为可感知的冲突报错(档案已被占用)而非脏数据【已接受的风险】",
    "档案管理:档案统计按分类/密级/载体三组 GROUP BY èšåˆï¼ˆæ—  N+1),当前量级可接受;档案过万后需改为缓存或物化统计表【本期不做】",
    "档案管理:跨模块改表——research_application / ums_member / finance_reimbursement å„新增可空列 archive_document_id(archive_module_migration.sql ç”¨ information_schema å®ˆå«ä¿è¯å¹‚等),需与三方模块确认变更窗口【跨模块改表,无破坏性】"
  ],
  "generatedAt": "2026-09-29",
  "analysisBasis": "基于 dev_business åˆ†æ”¯ 87bf4a99 æºç ï¼Œ6 ç»„并行源码走读(MES ç”Ÿäº§è´¨é‡ / MES ä»“储 / ERP / CRM+SRM / å”®åŽ+HRM / AI+BI+MDM+BPM+底座);2026-09-28 å¢žé‡è¡¥å…… yudao-module-finance ç‹¬ç«‹è´¢åŠ¡æŠ¥é”€æ¨¡å—ï¼ˆæŠ¥é”€å• + æ˜Žç»† + å‘票附件 + BPM å®¡æ‰¹ + å½’档)与 yudao-module-ums ç‹¬ç«‹ä¼šå‘˜ç®¡ç†æ¨¡å—(入会申请 + BPM ä¸¤çº§å®¡æ‰¹ + å®¡æ‰¹é€šè¿‡è‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å· + ä¼šå‘˜åˆ—表/通讯录);2026-09-28 å¢žé‡ï¼šBPM æµç¨‹å®šä¹‰ ums_membership_approve(入会审批)已在 mom-jssm éƒ¨ç½²å¹¶å¹¶å…¥ config_export_all é˜¶æ®µä¸€ï¼ˆåˆ†ç±» ums_membership_approve,秘书处初审 â†’ ç†äº‹ä¼šå®¡è®®ï¼Œä¸¤èŠ‚ç‚¹å®¡æ‰¹äººå‡ä¸ºã€Œè§’è‰²ã€ç­–ç•¥ã€è§’è‰²=超级管理员(1) å…œåº•,表单类型=自定义表单,查看路径 /ums/application/detail/approve/index.vue);同时修复 mom-jssm ç”± CREATE TABLE ... LIKE å…‹éš†å¯¼è‡´çš„ 47 ä¸ªæŒ‡å‘ ruoyi-vue-pro çš„ act_*/flw_* è·¨åº“外键(重建为指向 mom-jssm è‡ªèº«ï¼Œå­¤å„¿è¡Œé¢„检为 0);2026-09-28 å¢žé‡ï¼šæ–°å¢ž yudao-module-research ç‹¬ç«‹ç§‘研课题模块一期(课题指南发布 + åœ¨çº¿ç”³æŠ¥ + ä¸“家评审明细 + BPM ä¸‰çº§ç«‹é¡¹å®¡æ‰¹ + ç«‹é¡¹é€šçŸ¥å…¬å‘Š + ç»è´¹æ‹¨ä»˜è®°å½•,4 å¼  research_* è¡¨ / 5 ä¸ªå­—å…¸ / bpm_category id=8002 / æµç¨‹ research_topic_approve / èœå• 1075510~1075553),并并入 config_export_all_20260928 é˜¶æ®µä¸€ï¼›ç”³æŠ¥äººå– ums_member(软关联)、立项通知复用 system_notice å…¬å‘Šã€æ‹¨æ¬¾å‡­è¯èµ° system é™„件中心 recordType=research_fund_record;2026-09-29 å¢žé‡ï¼šè¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯¹æŽ¥è´¢åŠ¡æŠ¥é”€â€”â€”æ–°å»º yudao-module-finance-api(FinanceReimbursementApi:建单+提交/改单+重提/删除/查询四方法,本地 Spring Bean è°ƒç”¨ï¼Œæ— éœ€ Feign),课题侧登记拨款时 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement(系统生成 1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他 5、发票金额=拨付金额、发票日期=拨付日期、说明=「课题〔名〕第 N æœŸæ‹¨æ¬¾ã€ï¼‰å¹¶ç«‹å³æäº¤ BPM,回写 reimbursement_id/reimbursement_no、台账置「报销审批中(15)」;凭证改为登记时必填并双绑(research_fund_record + finance_reimbursement);新增 ResearchFundStatusListener(第 15 ä¸ªä¸šåŠ¡ç›‘å¬å™¨ï¼Œä¸Ž finance è‡ªå·±çš„ FinanceReimbursementStatusListener è®¢é˜…同一流程 key å¹¶è¡Œè§¦å‘)按审批结果回写台账:通过→已拨付(20)、驳回→已驳回(30)+驳回原因/时间、取消流程→退回待拨付(0)可改后可重新发起(发起人撤回只把实例退回发起节点、流程未结束,不触发回写,台账保持 15);新造 2 èŠ‚ç‚¹ BPMN docs/sql/finance_reimbursement_approve.bpmn(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼Œcategory id=216 å·²å­˜åœ¨ï¼‰ï¼Œ2026-09-29 å·²åœ¨ mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1(form_type=20、自定义表单创建路径 /finance/reimbursement/index、查看路径 /finance/reimbursement/detail/approve/index.vue)并并入 config_export_all_20260929 é˜¶æ®µä¸€ï¼Œä¸¤çº§å®¡æ‰¹å…¨é€šè¿‡/驳回/取消三条回写链路均已端到端验证"
  "analysisBasis": "基于 dev_business åˆ†æ”¯ 87bf4a99 æºç ï¼Œ6 ç»„并行源码走读(MES ç”Ÿäº§è´¨é‡ / MES ä»“储 / ERP / CRM+SRM / å”®åŽ+HRM / AI+BI+MDM+BPM+底座);2026-09-28 å¢žé‡è¡¥å…… yudao-module-finance ç‹¬ç«‹è´¢åŠ¡æŠ¥é”€æ¨¡å—ï¼ˆæŠ¥é”€å• + æ˜Žç»† + å‘票附件 + BPM å®¡æ‰¹ + å½’档)与 yudao-module-ums ç‹¬ç«‹ä¼šå‘˜ç®¡ç†æ¨¡å—(入会申请 + BPM ä¸¤çº§å®¡æ‰¹ + å®¡æ‰¹é€šè¿‡è‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å· + ä¼šå‘˜åˆ—表/通讯录);2026-09-28 å¢žé‡ï¼šBPM æµç¨‹å®šä¹‰ ums_membership_approve(入会审批)已在 mom-jssm éƒ¨ç½²å¹¶å¹¶å…¥ config_export_all é˜¶æ®µä¸€ï¼ˆåˆ†ç±» ums_membership_approve,秘书处初审 â†’ ç†äº‹ä¼šå®¡è®®ï¼Œä¸¤èŠ‚ç‚¹å®¡æ‰¹äººå‡ä¸ºã€Œè§’è‰²ã€ç­–ç•¥ã€è§’è‰²=超级管理员(1) å…œåº•,表单类型=自定义表单,查看路径 /ums/application/detail/approve/index.vue);同时修复 mom-jssm ç”± CREATE TABLE ... LIKE å…‹éš†å¯¼è‡´çš„ 47 ä¸ªæŒ‡å‘ ruoyi-vue-pro çš„ act_*/flw_* è·¨åº“外键(重建为指向 mom-jssm è‡ªèº«ï¼Œå­¤å„¿è¡Œé¢„检为 0);2026-09-28 å¢žé‡ï¼šæ–°å¢ž yudao-module-research ç‹¬ç«‹ç§‘研课题模块一期(课题指南发布 + åœ¨çº¿ç”³æŠ¥ + ä¸“家评审明细 + BPM ä¸‰çº§ç«‹é¡¹å®¡æ‰¹ + ç«‹é¡¹é€šçŸ¥å…¬å‘Š + ç»è´¹æ‹¨ä»˜è®°å½•,4 å¼  research_* è¡¨ / 5 ä¸ªå­—å…¸ / bpm_category id=8002 / æµç¨‹ research_topic_approve / èœå• 1075510~1075553),并并入 config_export_all_20260928 é˜¶æ®µä¸€ï¼›ç”³æŠ¥äººå– ums_member(软关联)、立项通知复用 system_notice å…¬å‘Šã€æ‹¨æ¬¾å‡­è¯èµ° system é™„件中心 recordType=research_fund_record;2026-09-29 å¢žé‡ï¼šè¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯¹æŽ¥è´¢åŠ¡æŠ¥é”€â€”â€”æ–°å»º yudao-module-finance-api(FinanceReimbursementApi:建单+提交/改单+重提/删除/查询四方法,本地 Spring Bean è°ƒç”¨ï¼Œæ— éœ€ Feign),课题侧登记拨款时 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement(系统生成 1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他 5、发票金额=拨付金额、发票日期=拨付日期、说明=「课题〔名〕第 N æœŸæ‹¨æ¬¾ã€ï¼‰å¹¶ç«‹å³æäº¤ BPM,回写 reimbursement_id/reimbursement_no、台账置「报销审批中(15)」;凭证改为登记时必填并双绑(research_fund_record + finance_reimbursement);新增 ResearchFundStatusListener(第 15 ä¸ªä¸šåŠ¡ç›‘å¬å™¨ï¼Œä¸Ž finance è‡ªå·±çš„ FinanceReimbursementStatusListener è®¢é˜…同一流程 key å¹¶è¡Œè§¦å‘)按审批结果回写台账:通过→已拨付(20)、驳回→已驳回(30)+驳回原因/时间、取消流程→退回待拨付(0)可改后可重新发起(发起人撤回只把实例退回发起节点、流程未结束,不触发回写,台账保持 15);新造 2 èŠ‚ç‚¹ BPMN docs/sql/finance_reimbursement_approve.bpmn(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼Œcategory id=216 å·²å­˜åœ¨ï¼‰ï¼Œ2026-09-29 å·²åœ¨ mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1(form_type=20、自定义表单创建路径 /finance/reimbursement/index、查看路径 /finance/reimbursement/detail/approve/index.vue)并并入 config_export_all_20260929 é˜¶æ®µä¸€ï¼Œä¸¤çº§å®¡æ‰¹å…¨é€šè¿‡/驳回/取消三条回写链路均已端到端验证;2026-09-29 å¢žé‡ï¼šæ–°å¢ž yudao-module-archive ç‹¬ç«‹æ¡£æ¡ˆç®¡ç†æ¨¡å— + yudao-module-archive-api(ArchiveDocumentApi:archiveBySource æŒ‰ sourceType+sourceId å¹‚等归档 / isArchived / getBySource ä¸‰æ–¹æ³•,本地 Spring Bean è°ƒç”¨ï¼Œæ—  Feign),覆盖书架分类(archive_category æ ‘形、code å”¯ä¸€ã€defaultSecretLevel é»˜è®¤å¯†çº§ã€6 æ¡åˆå§‹åˆ†ç±»ï¼šä¼šå‘˜/课题/会议/财务/文书/党建,财务默认密级=保密 3)、档案管理(archive_document:DA+yyyyMMdd+6 ä½æµæ°´ç¼–号、密级三档 1 å…¬å¼€ 2 å†…部 3 ä¿å¯†ã€è½½ä½“三型 1 ç”µå­ 2 å®žä½“ 3 ç”µå­+实体、实体位置四字段 shelfNo/layerNo/columnNo/boxNo + storePlace、来源四类 sourceType 1 æ‰‹å·¥ 2 è¯¾é¢˜ 3 ä¼šå‘˜ 4 è´¢åС另 sourceNo/sourceId/sourceTitle å†—余快照、状态 10 åœ¨åº“ 20 å€Ÿå‡ºä¸­ã€ç”µå­é™„ä»¶èµ° system é™„件中心 recordType=archive_document、archiveBySource å¹‚等短路 + ä¸‰æ–¹è¡¨ archive_document_id å›žå†™ä½œä¸ºå·²å½’档硬标记,重复归档由来源侧错误码拦截且 archive ä¾§ DOCUMENT_DUPLICATE_SOURCE å…œåº•)、借阅登记与审批(archive_borrow:JY+同级流水编号、借阅人信息/借阅目的/归还期限、状态 0 è‰ç¨¿ 15 å®¡æ‰¹ä¸­ 20 å€Ÿå‡ºä¸­ 30 å·²å½’还 35 å·²é©³å›žï¼Œä»… 0/35 å¯æ”¹å¯é‡æã€ä»… 0 å¯åˆ ã€ä»… 20 å¯ç™»è®°å½’还;submit å‰æ ¡éªŒåŒæ¡£æ¡ˆä¸å¾—已有 15/20 åœ¨é€”借阅,先解析流程再建单杜绝孤儿草稿;BPM æµç¨‹ archive_borrow_approve ä¸¤çº§ï¼šæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼›ç›‘听器 ArchiveBorrowStatusListener æŒ‰ businessKey å›žå†™ 20+approveTime / 35+驳回原因时间 / é€€å›ž 0,审批通过时条件更新档案 SET status=20 WHERE status=10,0 è¡Œåˆ™æŠ¥æ¡£æ¡ˆå·²è¢«å ç”¨å¹¶å›žæ»šï¼‰ã€å½’还登记(条件更新 20→30 + returnTime/归还确认人,再把档案条件更新回 10,形成闭环)、逾期实时计算(overdue=status==20 ä¸” returnDeadline<今天,不建 infra_job ä¸å‘站内信,列表「逾期」列 + /archive/borrow/overdue-page ç‹¬ç«‹å…¥å£ï¼‰ã€æ¡£æ¡ˆç»Ÿè®¡ï¼ˆ4 å¡ç‰‡ï¼šæ€»æ•°/在库/借出/逾期 + åˆ†ç±»/密级/载体三组 GROUP BY èšåˆï¼Œæ—  N+1);敏感档案用密级字段 + ç‹¬ç«‹æƒé™ç‚¹ archive:document:sensitive,/get è¶ŠæƒæŠ› DOCUMENT_SENSITIVE_NO_PERMISSION、/page ä¸Ž /export-excel å¼ºåˆ¶è¿‡æ»¤ secret_level<>3,统一走 applySecretLevelFilter / validateSecretLevelVisible ä¸¤ç§æœ‰æ–¹æ³•;新增 3 å¼ è¡¨ archive_category/archive_document/archive_borrow、6 ä¸ªå­—典(82000~82005)、bpm_category id=8003 + æ‰‹å†™ docs/sql/archive_borrow_approve.bpmn(category=archive_borrow_approve、form_type=20、创建路径 /archive/borrow/index、查看路径 /archive/borrow/detail/approve/index.vue)已在 mom-jssm éƒ¨ç½²ä¸ºç‰ˆæœ¬ 1、菜单 1075600~1075699;三方归档联动为列表页手动「归档」按钮(非事件驱动,归档不回写来源业务状态):课题 research_application status=20 å·²ç«‹é¡¹ â†’ sourceType=2 å¯†çº§ 2 å†…部 sourceNo=立项编号;会员 ums_member status=1 æ­£å¸¸ â†’ sourceType=3;财务 finance_reimbursement status=20 â†’ sourceType=4 å¯†çº§ 3 ä¿å¯†ï¼Œä¸Žæ—¢æœ‰ 20→40 + archiveTime åŒäº‹åŠ¡å åŠ ï¼›ä¸‰å¼ æ¥æºè¡¨å„æ–°å¢žå¯ç©ºåˆ— archive_document_id(archive_module_migration.sql ç”¨ information_schema å®ˆå«ä¿è¯å¹‚等)。全库表数 427→430;业务状态监听器实际计数修正为 24 ä¸ª *StatusListener(14 ä¸ªç»§æ‰¿ BpmProcessInstanceStatusEventListener)。本模块不反向依赖 ums/finance/research,仅提供 -api å­æ¨¡å—由三方手动调用;依赖方向 research/ums/finance â†’ archive-api,无环。本次同步更新的业务可视化产物:新增 mermaid/15-archive-flow.mmd,增量修订 01-business-mindmap / 08-module-relation / 09-data-flow / 10-business-object,以及 data/modules.json(新增 arc æ¨¡å—、bpm ç›‘听器计数 15→24)、objects.json(新增档案分类/档案/借阅单 3 å¯¹è±¡ + è¯¾é¢˜ç”³æŠ¥ä¹¦/会员/报销单各补 archive_document_id)、flows.json(新增 archive æµç¨‹ 9 æ­¥ï¼‰ã€roles.json(新增档案管理员/借阅人/借阅审批人/归档经办人 4 è§’色)、business.json"
}</script>
<script type="application/json" id="data-modules">{
  "modules": [
@@ -464,10 +475,19 @@
     "upstream": ["会员管理(ums_member å–申报人姓名/单位/职称/电话)", "BPM(流程定义 research_topic_approve / finance_reimbursement_approve)"],
     "downstream": ["system(system_notice ç«‹é¡¹é€šçŸ¥å…¬å‘Š)", "system(附件中心)", "财务(经费拨付生成 finance_reimbursement æŠ¥é”€å•·已对接)"],
     "coreTables": ["research_guideline", "research_application", "research_topic_review", "research_fund_record"]},
    {"key": "arc", "name": "档案管理(独立)", "color": "#7fb3d5", "tag": "ARC", "flow": "archive",
     "positioning": "学会/协会型实体与电子档案全生命周期:分类书架规整 â†’ æ¡£æ¡ˆç™»è®°ä¸Žæ£€ç´¢ â†’ å€Ÿé˜…审批 â†’ å½’还闭环 â†’ å­˜é‡ä¸Žæµè½¬ç»Ÿè®¡ï¼Œå¹¶æŽ¥æ”¶è¯¾é¢˜/会员/财务三方的归档联动",
     "problem": "课题材料、会员材料、财务凭证散落各模块无处归集;纸质档案存放位置靠线下台账,借阅与归还无留痕、逾期无人管;财务等敏感档案无密级管控",
     "features": ["档案分类树(书架·分层分列):会员/课题/会议/财务/文书/党建 6 æ¡åˆå§‹åˆ†ç±»Â·ç¼–码唯一·支持父子层级", "分类默认密级(财务分类=保密)·分类有子分类或下有档案禁删", "档案主表:电子附件 + å®žä½“位置四字段(书架号/层号/列号/册号档案盒)+存放位置描述", "档案编号 DA+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)·借阅单号 JY+同日流水", "密级三档 å…¬å¼€1/内部2/保密3 + ç‹¬ç«‹æƒé™ç‚¹ archive:document:sensitive(无权限时列表与导出强制过滤、详情报错)", "载体三型 ç”µå­1/实体2/电子+实体3", "来源四类 æ‰‹å·¥1/课题2/会员3/财务4(记录来源单号/ID/标题快照便于检索)", "全字段检索:标题/关键词 tags/来源单号/分类/密级/载体/状态/归档日期区间 + Excel å¯¼å‡º", "借阅登记(借阅人/部门/电话/日期/目的/归还期限)+BPM ä¸¤çº§å®¡æ‰¹(档案管理员审核→分管领导审批)·流程 key=archive_borrow_approve", "归还登记:实时更新档案状态回「在库」,形成完整流转闭环", "逾期未还仅查询时实时计算(overdueDays)·列表红色「逾期 N å¤©ã€Tag + /overdue-page ç‹¬ç«‹å…¥å£ + ã€Œåªçœ‹é€¾æœŸã€åˆ‡æ¢Â·ä¸å»º infra_job ä¸å‘站内信", "档案统计:总数/在库/借出中/逾期未还 + æŒ‰åˆ†ç±»/密级/载体三组分布(4 å¡ç‰‡ + 3 å›¾è¡¨)", "对外提供 yudao-module-archive-api(ArchiveDocumentApi:archiveBySource å¹‚ç­‰/ isArchived / getBySource)供课题·会员·财务三方手动归档", "附件走 system é™„件中心(recordType=archive_document / archive_borrow)"],
     "objects": ["档案分类", "档案", "借阅单"],
     "roles": ["档案管理员", "分管领导(借阅审批)", "借阅人", "归档经办人(课题/会员/财务)"],
     "upstream": ["课题管理(已立项申报书·archiveApplication)", "会员管理(正常会员·archiveMember)", "财务报销(已通过报销单·既有 archive å åŠ )", "BPM(流程定义 archive_borrow_approve)"],
     "downstream": ["system(附件中心 storage_attachment)", "来源表回写 archive_document_id"],
     "coreTables": ["archive_category", "archive_document", "archive_borrow"]},
    {"key": "bpm", "name": "协同审批", "color": "#e6a23c", "tag": "BPM", "flow": null,
     "positioning": "全系统单据审批中枢(Flowable)",
     "problem": "多模块单据需要灵活可配的多级审批",
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "15个业务监听器(同一流程 key å…è®¸å¤šä¸ªç›‘听器并行·各自按 key è¿‡æ»¤)", "表单·用户组·抄送·加签"],
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "24个业务状态监听器(*StatusListener·同一流程 key å…è®¸å¤šä¸ªç›‘听器并行·各自按 key è¿‡æ»¤Â·æ¡£æ¡ˆå€Ÿé˜… ArchiveBorrowStatusListener ä¸Žè´¢åŠ¡/课题监听器订阅不同 key äº’不干扰)", "表单·用户组·抄送·加签"],
     "objects": ["流程模型", "流程实例", "审批任务"],
     "roles": ["审批人(按流程配置)", "流程管理员"],
     "upstream": ["各业务提交审批"], "downstream": ["回调更新各业务单据状态"],
@@ -537,13 +557,16 @@
    {"name": "排班计划", "module": "设备", "table": "mes_cal_plan", "doc": "班组×班次×日期排班,供生产与考勤引用", "upstream": [], "downstream": ["生产任务", "考勤"], "keywords": "排班 ç­ç»„ ç­æ¬¡ æ—¥åކ"},
    {"name": "报告模板", "module": "质检报告", "table": "qc_report_template", "doc": "智能质检报告模板,启用0/停用1;画布内容存在 qc_report_template_version(草稿0/已发布1/已停用2,已发布不可覆盖),currentVersion æŒ‡å‘当前已发布版本;源文件经 AI å¯¼å…¥è¯†åˆ«æˆè‰ç¨¿åŽç”±äººå·¥ç¡®è®¤æ‰æˆä¸ºç‰ˆæœ¬ï¼›åŒä¸€æ¨¡æ¿é‡å¤å¯¼å…¥æŒ‰ã€æ›¿æ¢ã€‘处理——先删旧附件(连带 blob è¡Œä¸Žç£ç›˜æ–‡ä»¶ï¼‰å†ç»‘新的,不留孤儿文件;删除模板会连带清附件", "upstream": ["人工设计(GrapesJS è®¾è®¡å™¨)", "AI å¯¼å…¥è‰ç¨¿(qc-report/ai-import/draft)"], "downstream": ["模板版本", "报告实例"], "keywords": "报告模板 è´¨æ£€æŠ¥å‘Š template è®¾è®¡å™¨ ç‰ˆæœ¬ AI导入 è¯†åˆ« è‰ç¨¿ æºæ–‡ä»¶æ›¿æ¢"},
    {"name": "报告实例", "module": "质检报告", "table": "qc_report_instance", "doc": "出件产物,编号 QR+yyyyMMdd+4位流水按天独立且业务唯一,可显式指定覆盖(注意 uk_report_no ä¸åŒºåˆ†è½¯åˆ é™¤ï¼Œåˆ æŽ‰çš„号不回收);两条出件入口:通用 generate(调用方自带 context)与 generate-from-qc(数据从 MES è´¨æ£€å•取,权限码 qc-report:instance:generate å·²å…¥ system_menu id=1075416,五道校验按业务优先级只报第一处:105_000 ç±»åž‹éžæ³• / 105_001 å•据不存在 / 105_002 æœªå®Œæˆ / 105_003 æœªåˆ¤å®š / 105_005 æ¨¡æ¿ç±»åž‹ä¸ç¬¦ / 105_006 æ— å¯ç”¨æ£€éªŒé¡¹ï¼›å››å¼ è´¨æ£€å•列表的行操作「出报告」仅在 status=已完成时可见,弹窗只列 reportType ä¸€è‡´ã€æ¨¡æ¿å¯ç”¨ã€ä¸” currentVersion æŒ‡å‘的版本已发布的模板(后端 /qc-report/template/selectable åˆ¤å®šâ€”—停用版本不清空 currentVersion,前端拿分页结果自己推导会漏),结果就地展示并跳本页列表);冻结 data_snapshot(判定后数据,含后端算好的 PASS/FAIL ä¸Žåˆæ ¼çŽ‡ï¼‰ä¸Ž render_html;regenerate åªé‡æ¸²æŽ’版、数据与编号不变;business_id/business_type å­˜ MES è¡¨åï¼ˆmes_qc_iqc ç­‰ï¼‰ä¸Žå•据 ID,可反查、一单可出多份;报告头另带单据原判定 qcResult(数)/qcResultText(中文,合格/特采/不合格退货/不合格报废),与引擎三态判定并存互不覆盖;响应带 undecidableCount=快照中 result ä¸ºç©ºä¸²çš„项数与 warnings(分组标题跳过/指标已删/未录实测值/样品超3),两者都不落库、事后看不到", "upstream": ["报告模板版本", "质检单上下文(通用 generate ç”±è°ƒç”¨æ–¹ä¼ å…¥)", "MES è´¨æ£€å• IQC/IPQC/OQC/RQC(generate-from-qc ç» MesQcReportApi å–æ•°)"], "downstream": ["前端实例页 /qc/report-instance(列表·iframe预览·快照页签·导出PDF)", "PDF归档(system_storage_attachment recordType=qc_report_instance)"], "keywords": "报告实例 å‡ºä»¶ QR编号 å¿«ç…§ å¤çް regenerate PDF归档 æŠ¥å‘Šå®žä¾‹é¡µ å¯¼å‡ºPDF generate-from-qc è´¨æ£€å•出报告 å‡ºæŠ¥å‘Šå¼¹çª— qcResult undecidableCount warnings å¾…判定 åæŸ¥ ä¸šåŠ¡å•æ®ç­›é€‰"},
    {"name": "报销单", "module": "财务报销", "table": "finance_reimbursement", "doc": "单号可自定义(中间不允许空格,与已有单号重复则报错);新增时留空或纯空格由系统生成 BX+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®),修改时留空表示保持不变。status: 0草稿/15审批中/20已通过/35已驳回/40已归档;仅草稿可删、草稿与已驳回可改、仅已通过可归档;提交后由 BPM ç›‘听器按结果回写状态(取消退回草稿),审批中 processInstanceId è¢«å›žå†™ã€‚来源两条:① è´¢åŠ¡æ¨¡å—é¡µé¢äººå·¥å¡«æŠ¥ â‘¡è¯¾é¢˜ç®¡ç†ã€Œç»è´¹æ‹¨ä»˜ã€ç» FinanceReimbursementApi(yudao-module-finance-api)自动建单并立即提交(1:1、金额一致、系统固定 1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他)——后者的审批结果由课题侧 ResearchFundStatusListener è®¢é˜…同一流程回写拨款台账,报销单本身不回写任何外部单据", "upstream": ["课题管理(经费拨付·经 FinanceReimbursementApi è‡ªåŠ¨å»ºå•)", "人工填报"], "downstream": ["报销明细", "发票附件(storage_attachment·recordType=finance_reimbursement)", "课题拨款台账(仅经 BPM äº‹ä»¶å›žå†™çŠ¶æ€)"], "keywords": "报销单 æŠ¥é”€ å‘票 reimbursement BX å½’æ¡£ è´¢åŠ¡ è´¹ç”¨ è‡ªå®šä¹‰å•号 è¯¾é¢˜ç»è´¹æ‹¨ä»˜ è‡ªåŠ¨å»ºå• FinanceReimbursementApi"},
    {"name": "报销单", "module": "财务报销", "table": "finance_reimbursement", "doc": "单号可自定义(中间不允许空格,与已有单号重复则报错);新增时留空或纯空格由系统生成 BX+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®),修改时留空表示保持不变。status: 0草稿/15审批中/20已通过/35已驳回/40已归档;仅草稿可删、草稿与已驳回可改、仅已通过可归档;提交后由 BPM ç›‘听器按结果回写状态(取消退回草稿),审批中 processInstanceId è¢«å›žå†™ã€‚来源两条:① è´¢åŠ¡æ¨¡å—é¡µé¢äººå·¥å¡«æŠ¥ â‘¡è¯¾é¢˜ç®¡ç†ã€Œç»è´¹æ‹¨ä»˜ã€ç» FinanceReimbursementApi(yudao-module-finance-api)自动建单并立即提交(1:1、金额一致、系统固定 1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他)——后者的审批结果由课题侧 ResearchFundStatusListener è®¢é˜…同一流程回写拨款台账,报销单本身不回写任何外部单据;archive_document_id éžç©ºè¡¨ç¤ºå·²å½’æ¡£", "upstream": ["课题管理(经费拨付·经 FinanceReimbursementApi è‡ªåŠ¨å»ºå•)", "人工填报"], "downstream": ["报销明细", "发票附件(storage_attachment·recordType=finance_reimbursement)", "课题拨款台账(仅经 BPM äº‹ä»¶å›žå†™çŠ¶æ€)", "档案管理(既有归档动作同事务叠加·生成「保密 3」级财务档案并回写 archive_document_id)"], "keywords": "报销单 æŠ¥é”€ å‘票 reimbursement BX å½’æ¡£ è´¢åŠ¡ è´¹ç”¨ è‡ªå®šä¹‰å•号 è¯¾é¢˜ç»è´¹æ‹¨ä»˜ è‡ªåŠ¨å»ºå• FinanceReimbursementApi è´¢åŠ¡æ¡£æ¡ˆ ä¿å¯† archive_document_id"},
    {"name": "报销明细", "module": "财务报销", "table": "finance_reimbursement_item", "doc": "报销单的费用行:费用类型(1差旅/2办公/3招待/4交通/5其他)、发票号码、发票金额、发票日期、开票方、费用说明;随主表一次性提交(先删后增全量覆盖),提交时必须剥掉行 id ä¸Ž reimbursementId â€”— æ˜Žç»†åˆ é™¤æ˜¯è½¯åˆ ï¼Œå›žä¼ åŽŸ id ä¼šä¸Žå·²è½¯åˆ è¡Œä¸»é”®å†²çª", "upstream": ["报销单"], "downstream": [], "keywords": "报销明细 è´¹ç”¨æ˜Žç»† è´¹ç”¨ç±»åž‹ å‘票金额 item æ˜Žç»†è¡Œ å·®æ—…"},
    {"name": "入会申请单", "module": "会员管理", "table": "ums_application", "doc": "单号 SQ+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®),后端生成不可自定义;memberType 1个人/2单位决定字段分叉(个人用 workUnit/title,单位用 legalPerson/organizationCode);memberGrade æŒ‰ç±»åž‹å–不同字典;status: 0草稿/15审批中/20已通过/35已驳回。仅草稿可删(连带清附件)、草稿与已驳回可改可重提;提交后由 BPM ç›‘听器按结果回写(取消退回草稿),审批中回写 processInstanceId;审批通过触发自动建会员与系统账号。独立模块,不与其它业务模块产生单据关联", "upstream": ["人工填报(申请人)", "证明材料附件(storage_attachment·recordType=ums_application)"], "downstream": ["会员(审批通过自动创建)", "BPM æµç¨‹å®žä¾‹(ums_membership_approve åˆ†ç±»)"], "keywords": "入会申请 å…¥ä¼š ä¼šå‘˜ç”³è¯· application SQ ç§˜ä¹¦å¤„ ç†äº‹ä¼š å®¡æ‰¹ ä¼šå‘˜ç±»åž‹ ä¸ªäºº å•位 æ³•定代表人 ç ”究方向 ç”³è¯·ç­‰çº§"},
    {"name": "会员", "module": "会员管理", "table": "ums_member", "doc": "单号 HY+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);两个来源:①入会申请审批通过自动创建(回填 applicationId ä¸Ž userName/系统账号) â‘¡ä¼šå‘˜åˆ—表手工新增。status: 1正常/0暂停;memberGrade æŒ‰ memberType å–对应字典,校验规则是单位会员(type=2)不可选『理事长』(grade=5)。自动建档时入会日期=当天、不搬运 contactUser/unitIntro/expireDate(申请单无对应字段);自动建的系统账号用户名为联系电话且初始密码同为联系电话,部门固定为「学会会员」;手机号重复会明确报错且整单事务回滚", "upstream": ["入会申请单(审批通过)", "人工新增", "附件(storage_attachment·recordType=ums_member)"], "downstream": ["会员通讯录(按 regionCode/industry æ£€ç´¢)", "system_users(登录账号)"], "keywords": "会员 member HY ä¼šå‘˜ç¼–号 ä¼šå‘˜ç­‰çº§ ä¼šå‘˜ç±»åž‹ é€šè®¯å½• åœ°åŒº è¡Œä¸š å…¥ä¼šæ—¥æœŸ æœ‰æ•ˆæœŸ"},
    {"name": "会员", "module": "会员管理", "table": "ums_member", "doc": "单号 HY+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);两个来源:①入会申请审批通过自动创建(回填 applicationId ä¸Ž userName/系统账号) â‘¡ä¼šå‘˜åˆ—表手工新增。status: 1正常/0暂停;memberGrade æŒ‰ memberType å–对应字典,校验规则是单位会员(type=2)不可选『理事长』(grade=5)。自动建档时入会日期=当天、不搬运 contactUser/unitIntro/expireDate(申请单无对应字段);自动建的系统账号用户名为联系电话且初始密码同为联系电话,部门固定为「学会会员」;手机号重复会明确报错且整单事务回滚;archive_document_id éžç©ºè¡¨ç¤ºå·²å½’档(归档后列表页隐藏「归档」按钮)", "upstream": ["入会申请单(审批通过)", "人工新增", "附件(storage_attachment·recordType=ums_member)"], "downstream": ["会员通讯录(按 regionCode/industry æ£€ç´¢)", "档案管理(手动「归档」·仅正常状态可归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id)", "system_users(登录账号)"], "keywords": "会员 member HY ä¼šå‘˜ç¼–号 ä¼šå‘˜ç­‰çº§ ä¼šå‘˜ç±»åž‹ é€šè®¯å½• åœ°åŒº è¡Œä¸š å…¥ä¼šæ—¥æœŸ æœ‰æ•ˆæœŸ å½’æ¡£ archive_document_id"},
    {"name": "课题指南", "module": "课题管理", "table": "research_guideline", "doc": "指南编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);category 1重点课题/2一般课题;publishStatus 0草稿/20已发布/30已截止,仅草稿可改可删,publish å‘布回写 publishTime,close æˆªæ­¢ç½® 30;已产生申报单(applicationMapper.selectCountByGuidelineId å¤§äºŽ 0)的指南禁止删除并在提示中报出该指南下已有多少张申报单;正文 content ä¸ºé•¿æ–‡æœ¬ï¼›é™„ä»¶ recordType=research_guideline", "upstream": ["人工维护(学会秘书处)", "指南附件(storage_attachment·recordType=research_guideline)"], "downstream": ["课题申报书(申报时引用且必须已发布并在申报期内)"], "keywords": "课题指南 æŒ‡å— å‘布 æˆªæ­¢ é‡ç‚¹è¯¾é¢˜ ä¸€èˆ¬è¯¾é¢˜ GL æŒ‡å—编号 ç”³æŠ¥æœŸ ç”³æŠ¥èµ·æ­¢"},
    {"name": "课题申报书", "module": "课题管理", "table": "research_application", "doc": "申报编号 SB+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);guidelineId å…³è”指南、memberId å…³è” ums_member(软关联无外键,会员删除后会留下悬空 memberId éœ€å®¹é”™æ˜¾ç¤º);applicantName/workUnit/jobTitle/contactPhone/contactEmail ç”±é€‰ä¼šå‘˜è‡ªåŠ¨å¸¦å‡ºå¯æ‰‹æ”¹ï¼›category å†—余自指南且不信任前端传值;budgetAmount ç”³è¯·ç»è´¹ä¸ºæ‹¨ä»˜ä¸Šé™ï¼›status: 0草稿/15审批中/20已立项/35已驳回。仅草稿可删(连带清附件)、草稿与已驳回可改可重提;submit å‰é€æ¡æ ¡éªŒã€ŒæŒ‡å—已发布」与「现在在申报期内」,任一不满足即拒绝并按维度给出指南名与实际值;审批通过由监听器生成立项编号 researchNo(LX+同日流水)与 approveTime(已有编号幂等跳过);reviewAvgScore/reviewPass ä¸ºè¯„审汇总回写值,明细删空时清成未汇总;noticeId è®°å½•已发布的立项通知公告,重复发布被拦截", "upstream": ["课题指南(必须已发布且在申报期内)", "会员(ums_member·带出申报人信息)", "申报附件(storage_attachment·recordType=research_application)"], "downstream": ["专家评审明细", "经费拨付记录", "立项通知(system_notice å…¬å‘Š)", "BPM æµç¨‹å®žä¾‹(research_topic_approve åˆ†ç±»)", "附件中心"], "keywords": "课题申报 ç”³æŠ¥ä¹¦ ç”³æŠ¥å• SB ç”³æŠ¥ç¼–号 ç«‹é¡¹ ç«‹é¡¹ç¼–号 LX ç”³è¯·ç»è´¹ ç”³æŠ¥ä¹¦æ­£æ–‡ è¯¾é¢˜ç»„成员 ä¸“家评审 è¯„审汇总 ç«‹é¡¹é€šçŸ¥ å‘布通知 æäº¤å®¡æ‰¹ é©³å›ž é‡æ"},
    {"name": "课题申报书", "module": "课题管理", "table": "research_application", "doc": "申报编号 SB+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®);guidelineId å…³è”指南、memberId å…³è” ums_member(软关联无外键,会员删除后会留下悬空 memberId éœ€å®¹é”™æ˜¾ç¤º);applicantName/workUnit/jobTitle/contactPhone/contactEmail ç”±é€‰ä¼šå‘˜è‡ªåŠ¨å¸¦å‡ºå¯æ‰‹æ”¹ï¼›category å†—余自指南且不信任前端传值;budgetAmount ç”³è¯·ç»è´¹ä¸ºæ‹¨ä»˜ä¸Šé™ï¼›status: 0草稿/15审批中/20已立项/35已驳回。仅草稿可删(连带清附件)、草稿与已驳回可改可重提;submit å‰é€æ¡æ ¡éªŒã€ŒæŒ‡å—已发布」与「现在在申报期内」,任一不满足即拒绝并按维度给出指南名与实际值;审批通过由监听器生成立项编号 researchNo(LX+同日流水)与 approveTime(已有编号幂等跳过);reviewAvgScore/reviewPass ä¸ºè¯„审汇总回写值,明细删空时清成未汇总;noticeId è®°å½•已发布的立项通知公告,重复发布被拦截;archive_document_id éžç©ºè¡¨ç¤ºå·²å½’档(归档后列表页隐藏「归档」按钮)", "upstream": ["课题指南(必须已发布且在申报期内)", "会员(ums_member·带出申报人信息)", "申报附件(storage_attachment·recordType=research_application)"], "downstream": ["专家评审明细", "经费拨付记录", "立项通知(system_notice å…¬å‘Š)", "BPM æµç¨‹å®žä¾‹(research_topic_approve åˆ†ç±»)", "档案管理(手动「归档」·仅已立项可归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id)", "附件中心"], "keywords": "课题申报 ç”³æŠ¥ä¹¦ ç”³æŠ¥å• SB ç”³æŠ¥ç¼–号 ç«‹é¡¹ ç«‹é¡¹ç¼–号 LX ç”³è¯·ç»è´¹ ç”³æŠ¥ä¹¦æ­£æ–‡ è¯¾é¢˜ç»„成员 ä¸“家评审 è¯„审汇总 ç«‹é¡¹é€šçŸ¥ å‘布通知 æäº¤å®¡æ‰¹ é©³å›ž é‡æ å½’æ¡£ archive_document_id"},
    {"name": "专家评审明细", "module": "课题管理", "table": "research_topic_review", "doc": "一位专家一行的一主多行子表:evaluatorName/evaluatorUnit/evaluatorTitle + topicScore é€‰é¢˜ä»·å€¼ä¸Žæ„ä¹‰(权重0.3) + planScore ç ”究方案可行性(0.3) + teamScore å›¢é˜Ÿä¸Žç ”究基础(0.2) + resultScore é¢„期成果与效益(0.2) + conclusion 1推荐立项/2不推荐立项 + opinion è¯„审意见 + reviewTime。compositeScore ç”±åŽç«¯ weightedScore è®¡ç®—、ranking ç”±åŽç«¯æŒ‰åŠ æƒåˆ†é™åºé‡æŽ’ï¼Œå‡ä¸æŽ¥å—å‰ç«¯ä¼ å€¼ï¼›æ¯æ¬¡å¢žåˆ æ”¹åŽè°ƒ recalculate å›žå†™ä¸»å• reviewAvgScore(全部专家加权分均值·保留2位)与 reviewPass(均分达标 ä¸” å¤šæ•°ç»“论为推荐立项时为 1);四项分值超出 0~100 æ—¶æç¤ºæŒ‡å‡ºå…·ä½“维度与实际值;仅在申报单 status=15 å®¡æ‰¹ä¸­å¯å¢žåˆ æ”¹", "upstream": ["课题申报书(仅审批中可写)"], "downstream": ["课题申报书(回写 reviewAvgScore/reviewPass)"], "keywords": "专家评审 è¯„审明细 è¯„分 åŠ æƒ æƒé‡ æŽ’名 ranking compositeScore æŽ¨èç«‹é¡¹ è¯„审意见 å‡åˆ†"},
    {"name": "档案分类", "module": "档案管理", "table": "archive_category", "doc": "树形书架分类(parentId·0=顶级),承载「分层分列」规整:6 æ¡åˆå§‹åˆ†ç±» ä¼šå‘˜æ¡£æ¡ˆ member / è¯¾é¢˜æ¡£æ¡ˆ research / ä¼šè®®æ¡£æ¡ˆ meeting / è´¢åŠ¡æ¡£æ¡ˆ finance / æ–‡ä¹¦æ¡£æ¡ˆ document / å…šå»ºæ¡£æ¡ˆ party;code å…¨å±€å”¯ä¸€ uk_code;defaultSecretLevel ä¸ºè¯¥åˆ†ç±»ä¸‹æ¡£æ¡ˆçš„默认密级(1公开/2内部/3保密,财务分类=3);status 0启用/1停用。仅可改可删,有子分类禁删、该分类下有档案禁删、父分类不能设为自身或其子分类。会议/党建档案系统内无来源模块,仅支持手工录入档案", "upstream": ["人工维护(档案管理员)"], "downstream": ["档案(新增时带出默认密级)"], "keywords": "档案分类 ä¹¦æž¶ åˆ†å±‚分列 ä¼šå‘˜æ¡£æ¡ˆ è¯¾é¢˜æ¡£æ¡ˆ ä¼šè®®æ¡£æ¡ˆ è´¢åŠ¡æ¡£æ¡ˆ æ–‡ä¹¦æ¡£æ¡ˆ å…šå»ºæ¡£æ¡ˆ category é»˜è®¤å¯†çº§"},
    {"name": "档案", "module": "档案管理", "table": "archive_document", "doc": "档案主表,编号 DA+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)。电子与实体兼具:电子走 system é™„件中心 recordType=archive_document(表内不存文件地址)、实体记 shelfNo ä¹¦æž¶å·/layerNo å±‚号/columnNo åˆ—号/boxNo å†Œå·æ¡£æ¡ˆç›’ + storePlace å­˜æ”¾ä½ç½®æè¿°ã€‚carrierType 1电子/2实体/3电子+实体;secretLevel 1公开/2内部/3保密(为 3 ä¸”当前用户无 archive:document:sensitive æ—¶è¯¦æƒ…报错、列表与导出强制过滤);sourceType 1手工/2课题/3会员/4财务 + sourceNo/sourceId/sourceTitle è®°å½•来源快照以便检索;status 10在库/20借出中(借出中禁改、存在未归还借阅单时禁删)。来源为 2/3/4 çš„æ¡£æ¡ˆç”±ä¸‰æ–¹åˆ—表页「归档」按钮经 ArchiveDocumentApi.archiveBySource ç”Ÿæˆ(幂等),并回写来源表 archive_document_id", "upstream": ["档案分类", "档案登记(人工)", "课题归档(已立项申报书)", "会员归档(正常会员)", "财务归档(已通过报销单)"], "downstream": ["借阅单", "附件中心(recordType=archive_document)", "来源表 archive_document_id å›žå†™"], "keywords": "档案 æ–‡æ¡£ document DA æ¡£æ¡ˆç¼–号 å¯†çº§ ä¿å¯† å…¬å¼€ å†…部 æ•æ„Ÿæ¡£æ¡ˆ è½½ä½“ ç”µå­ å®žä½“ ä¹¦æž¶å· å±‚号 åˆ—号 å†Œå· æ¡£æ¡ˆç›’ å½’档日期 åœ¨åº“ å€Ÿå‡ºä¸­ æ£€ç´¢ å…³é”®è¯ tags æ¥æºå•号 å½’档联动"},
    {"name": "借阅单", "module": "档案管理", "table": "archive_borrow", "doc": "借阅与归还合一,编号 JY+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)。记录借阅人/部门/电话、借阅日期、借阅目的、归还期限(returnDeadline·逾期实时计算基准);documentNo/documentTitle ä¸ºæ¡£æ¡ˆå¿«ç…§ã€‚status 0草稿/15审批中/20借出中/30已归还/35已驳回:仅 0 ä¸Ž 35 å¯æ”¹å¯é‡æã€ä»… 0 å¯åˆ ã€ä»… 20 å¯ç™»è®°å½’还。submit å‰æ ¡éªŒåŒä¸€æ¡£æ¡ˆä¸å¾—已有 15/20 çš„借阅单(命中报已借用并带档案名与已存在借阅单号);流程 key archive_borrow_approve ä¸¤çº§(档案管理员审核→分管领导审批),前端不写死 key。审批通过由 ArchiveBorrowStatusListener å›žå†™ 20+approveTime å¹¶æ¡ä»¶æ›´æ–°æ¡£æ¡ˆ SET status=20 WHERE status=10(影响 0 è¡ŒæŠ¥æ¡£æ¡ˆå·²è¢«å ç”¨å¹¶å›žæ»š);归还登记条件更新 20→30 å¹¶å›žå†™ returnTime/归还确认人。逾期不落库,查询时实时算 overdueDays å¹¶åœ¨åˆ—表渲染红色「逾期 N å¤©ã€ï¼Œå¦æœ‰ /overdue-page ç‹¬ç«‹å…¥å£", "upstream": ["档案(仅 åœ¨åº“ 10 å¯é€‰)", "BPM(流程 archive_borrow_approve)", "人工登记"], "downstream": ["档案(状态 20 å€Ÿå‡ºä¸­ / å½’还回 10 åœ¨åº“)"], "keywords": "借阅 å€Ÿé˜…单 å€Ÿé˜…登记 å½’还 å½’还登记 é€¾æœŸ é€¾æœŸæœªè¿˜ å€Ÿé˜…人 å€Ÿé˜…目的 å½’还期限 JY å€Ÿé˜…单号 æ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ æµè½¬é—­çޝ"},
    {"name": "经费拨付记录", "module": "课题管理", "table": "research_fund_record", "doc": "一主多行的拨款台账:batchName æ‰¹æ¬¡åç§°/amount æ‹¨ä»˜é‡‘额/payDate æ‹¨ä»˜æ—¥æœŸ/payee æ”¶æ¬¾å•位/remark,凭证附件走 system é™„件中心 recordType=research_fund_record(表内不存文件地址)。status: 0待拨付/15报销审批中/20已拨付/30已驳回,由系统与 BPM é©±åŠ¨ï¼Œæ–°å¢ž/修改接口已移除 status å…¥å‚(防伪造「已拨付」)。登记即一条龙:落台账 â†’ åŒä¸€æ‰¹ blob åŒç»‘ â†’ ç» FinanceReimbursementApi è‡ªåŠ¨ç”Ÿæˆ 1:1 çš„ finance_reimbursement å¹¶ç«‹å³æäº¤ BPM â†’ å›žå†™ reimbursement_id/reimbursement_no å¹¶ç½® 15;审批结果由监听器回写:通过→20、驳回→30(+reject_reason/reject_time)、取消流程→退回 0(发起人撤回只退回发起节点、流程未结束,状态不变)。仅申报单 status=20 å·²ç«‹é¡¹å¯ç™»è®°ï¼›å‡­è¯å¿…填;金额校验:已登记金额(该单全部记录求和) + æœ¬æ¬¡é‡‘额 ä¸å¾—超出申报单 budgetAmount,超出时提示报出本次、已登记、合计、预算四项数值;仅「待拨付」可删(联动删除财务侧草稿报销单,非草稿则拒绝),「待拨付」「已驳回」可改并重新发起报销(已驳回改后清空 reject_reason/reject_time),编辑时把本行金额加回可用余额展示避免误解。与报销单为软关联(无外键),财务侧直接删单会留下悬空 reimbursement_id,此时修改报「报销单已不存在,请删除本条记录后重新登记」", "upstream": ["课题申报书(必须已立项)", "财务报销单(1:1 è‡ªåŠ¨ç”ŸæˆÂ·è½¯å…³è” reimbursement_id)"], "downstream": ["拨付凭证(storage_attachment·recordType=research_fund_record)", "财务报销单(生成并提交出账·审批结果回写本台账)"], "keywords": "经费拨付 æ‹¨æ¬¾ æ‹¨ä»˜è®°å½• æ‰¹æ¬¡ æ‹¨ä»˜é‡‘额 å‡­è¯ å¾…拨付 æŠ¥é”€å®¡æ‰¹ä¸­ å·²æ‹¨ä»˜ å·²é©³å›ž é©³å›žåŽŸå›  è´¢åŠ¡æŠ¥é”€å•å· reimbursement_id æ”¶æ¬¾å•位 èµ„助金额 é¢„ç®— è¶…额 é‡æ–°å‘起报销"}
  ]
}</script>
@@ -631,7 +654,19 @@
       {"stage": "专家评审", "action": "申报单处于审批中(15)时录入专家评分明细(一位专家一行:姓名/单位/职称 + é€‰é¢˜ä»·å€¼0.3/研究方案0.3/团队基础0.2/预期成果0.2 å››é¡¹åˆ†å€¼ + ç»“论 + æ„è§ï¼‰ï¼›åŽç«¯æ¯æ¬¡å¢žåˆ æ”¹åŽé‡ç®— compositeScore åŠ æƒåˆè®¡ã€æŒ‰åŠ æƒåˆ†é™åºé‡æŽ’ ranking,并回写主单 reviewAvgScore(全部专家均分)与 reviewPass(均分达标且多数结论为推荐立项);明细全部删除时汇总值清空为未汇总;四项分值均在 0~100 ä¹‹å¤–时提示指出具体维度与实际值", "roles": ["评审专家", "学会秘书处", "课题管理员"]},
       {"stage": "立项审批", "action": "BPM æµç¨‹ research_topic_approve é¡ºåºèµ°ä¸‰èŠ‚ç‚¹ï¼šç§˜ä¹¦å¤„å½¢å¼å®¡æŸ¥ â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ã€‚审批方法与审批人在流程定义内配置,部署后由管理员在 BPM æµç¨‹è®¾è®¡å™¨æŒ‰å­¦ä¼šå®žé™…组织调整;审批中心内的自定义表单只读回显申报单 + è¯„审明细 + æ‹¨ä»˜è®°å½•。通过→20 å·²ç«‹é¡¹ã€é©³å›žâ†’35 å·²é©³å›žã€å–消→退回 0 è‰ç¨¿", "roles": ["学会秘书处(形式审查)", "评审专家(评审节点)", "学会理事会(立项审批)"]},
       {"stage": "立项与通知", "action": "监听器 ResearchApplicationStatusListener å…ˆå›žå†™å®¡æ‰¹çŠ¶æ€ï¼Œå®¡æ‰¹é€šè¿‡åŽå†è°ƒ approveApplication:生成立项编号 LX+yyyyMMdd+6位流水 ä¸Ž approveTime(已有编号则幂等跳过)。点「发布立项通知」生成 system_notice å…¬å‘Šï¼ˆæ ‡é¢˜ã€Žå…³äºŽå…¬å¸ƒç«‹é¡¹è¯¾é¢˜çš„通知』,正文含课题名/立项编号/类别/申报人/单位/资助金额),写回 noticeId é˜²é‡å¤â€”—重复点击按已发布拦截并告知上次发布时间", "roles": ["课题管理员", "学会秘书处"]},
       {"stage": "经费拨付", "action": "仅「已立项(20)」的申报单可登记拨款记录(批次名称/拨付金额/拨付日期/收款单位/备注/凭证附件),凭证必填。登记时一条龙完成:① è¯¾é¢˜ä¾§è½ research_fund_record å°è´¦ï¼ˆå…ˆç½®å¾…拨付)② åŒä¸€æ‰¹ blob åŒç»‘到课题台账与财务报销单 â‘¢ ç» yudao-module-finance-api è‡ªåŠ¨ç”Ÿæˆ 1:1 çš„ finance_reimbursement æŠ¥é”€å•(1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他、发票金额=拨付金额、发票日期=拨付日期、费用说明=「课题〔课题名〕第N期/第N次经费拨款」)④ ç«‹å³æäº¤ BPM æŠ¥é”€å®¡æ‰¹ â‘¤ å°è´¦å›žå†™æŠ¥é”€å•号并置「报销审批中(15)」。金额校验:已登记金额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出该单申请经费,超出时提示报出本次、已登记、合计、预算四项数值。状态由系统驱动:0待拨付/15报销审批中/20已拨付/30已驳回;只有「待拨付」可删(删除联动清理财务侧草稿报销单),「待拨付」「已驳回」可改并重新发起报销(已驳回改后清空驳回原因与时间);取消流程则退回「待拨付(0)」(发起人撤回只把实例退回发起节点、流程未结束,台账保持「报销审批中(15)」,需重新提交后才继续走审批)。财务审批分类下没有已发布流程时直接报错且不建单,不留孤儿草稿报销单", "roles": ["学会财务", "课题管理员", "财务审核人", "财务主管"]}]},
     {"stage": "经费拨付", "action": "仅「已立项(20)」的申报单可登记拨款记录(批次名称/拨付金额/拨付日期/收款单位/备注/凭证附件),凭证必填。登记时一条龙完成:① è¯¾é¢˜ä¾§è½ research_fund_record å°è´¦ï¼ˆå…ˆç½®å¾…拨付)② åŒä¸€æ‰¹ blob åŒç»‘到课题台账与财务报销单 â‘¢ ç» yudao-module-finance-api è‡ªåŠ¨ç”Ÿæˆ 1:1 çš„ finance_reimbursement æŠ¥é”€å•(1 è¡Œæ˜Žç»†ï¼šè´¹ç”¨ç±»åž‹=其他、发票金额=拨付金额、发票日期=拨付日期、费用说明=「课题〔课题名〕第N期/第N次经费拨款」)④ ç«‹å³æäº¤ BPM æŠ¥é”€å®¡æ‰¹ â‘¤ å°è´¦å›žå†™æŠ¥é”€å•号并置「报销审批中(15)」。金额校验:已登记金额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出该单申请经费,超出时提示报出本次、已登记、合计、预算四项数值。状态由系统驱动:0待拨付/15报销审批中/20已拨付/30已驳回;只有「待拨付」可删(删除联动清理财务侧草稿报销单),「待拨付」「已驳回」可改并重新发起报销(已驳回改后清空驳回原因与时间);取消流程则退回「待拨付(0)」(发起人撤回只把实例退回发起节点、流程未结束,台账保持「报销审批中(15)」,需重新提交后才继续走审批)。财务审批分类下没有已发布流程时直接报错且不建单,不留孤儿草稿报销单", "roles": ["学会财务", "课题管理员", "财务审核人", "财务主管"]}]},
    {"id": "archive", "name": "档案管理流程", "mermaid": "mermaid/15-archive-flow.mmd", "color": "#7fb3d5",
     "summary": "独立档案模块(yudao-module-archive):书架分类(分层分列)规整 â†’ æ¡£æ¡ˆç™»è®°ï¼ˆç”µå­é™„ä»¶ + å®žä½“位置四字段,密级三档)→ å€Ÿé˜…登记走 BPM ä¸¤çº§å®¡æ‰¹ï¼ˆæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼‰â†’ å®¡æ‰¹é€šè¿‡æ¡£æ¡ˆè½¬ã€Œå€Ÿå‡ºä¸­ã€â†’ å½’还登记回「在库」形成流转闭环 â†’ é€¾æœŸæœªè¿˜æŸ¥è¯¢æ—¶å®žæ—¶è®¡ç®—并在列表标红 â†’ å­˜é‡ä¸Žæµè½¬ç»Ÿè®¡ï¼ˆæ€»æ•°/在库/借出中/逾期 + åˆ†ç±»Â·å¯†çº§Â·è½½ä½“三组分布)。同时接收课题(已立项)、会员(正常)、财务(已通过)三方的「归档」联动,经新建的 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™æ¥æºè¡¨ archive_document_id。会议/党建档案系统内无来源模块,仅手工录入。",
     "steps": [
       {"stage": "建分类", "action": "新建分类可挂父分类形成分类树(编码 code å…¨å±€å”¯ä¸€ï¼‰ï¼Œè®¾æŽ’序、说明、默认密级与启用状态;6 æ¡åˆå§‹åˆ†ç±»ï¼šä¼šå‘˜æ¡£æ¡ˆ/课题档案/会议档案/财务档案(默认密级=保密 3)/文书档案/党建档案。有子分类或该分类下已有档案时禁止删除;父分类不能设为自身或其子分类", "roles": ["档案管理员"]},
       {"stage": "登记档案", "action": "填标题、分类、密级(1公开/2内部/3保密)、载体(1电子/2实体/3电子+实体)、归档日期、摘要、关键词;选电子则上传附件(走 system é™„件中心,recordType=archive_document,先 /system/storage-blob/upload å– blobId å†éšä¿å­˜è¯·æ±‚çš„ blobIds æäº¤ï¼Œå…¨é‡æ›¿æ¢è¯­ä¹‰ï¼‰ï¼Œé€‰å®žä½“则填书架号/层号/列号/册号档案盒与存放位置描述;后端生成档案编号 DA+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®ï¼‰ï¼ŒçŠ¶æ€ã€Œåœ¨åº“(10)」", "roles": ["档案管理员"]},
       {"stage": "检索", "action": "按档案编号/标题/关键词 tags/来源单号/分类/密级/载体/状态/归档日期区间多条件检索,支持 Excel å¯¼å‡ºã€‚密级=保密(3) çš„æ¡£æ¡ˆï¼šå½“前用户无 archive:document:sensitive æ—¶åˆ—表与导出强制过滤掉、直连详情接口报无权限错误", "roles": ["档案管理员", "全员可查公开档"]},
       {"stage": "三方归档", "action": "课题申报书(仅已立项 20)、会员(仅正常 1)、财务报销单(仅已通过 20)的列表页各有一个「归档」按钮,经 yudao-module-archive-api çš„ archiveBySource æŒ‰ã€Œæ¥æºç±»åž‹ + æ¥æºå•据 ID」幂等生成档案(来源单号/标题作为快照存入档案,源单附件一并带入档案;财务档案密级固定为「保密」),并回写来源表 archive_document_id ä½œä¸ºå·²å½’档硬标记;重复点击由来源侧拦截并提示已生成的档案编号。财务侧「归档」与其既有 20→40 çŠ¶æ€æŽ¨è¿›åœ¨åŒä¸€äº‹åŠ¡å†…å åŠ æ‰§è¡Œ", "roles": ["课题管理员", "会员管理员", "财务"]},
       {"stage": "借阅登记", "action": "选档案(仅「在库(10)」可选)、填借阅人/部门/联系电话、借阅日期、借阅目的、归还期限;生成借阅单号 JY+yyyyMMdd+6位流水,状态「草稿(0)」。提交前校验同一档案不得存在「审批中(15)」或「借出中(20)」的借阅单,命中时按已存在的借阅单号精确报错;submit å…ˆè§£æžæµç¨‹åˆ—表(无已发布流程直接报错、不留孤儿草稿)再建单,回写 processInstanceId å¹¶ç½®ã€Œå®¡æ‰¹ä¸­(15)」", "roles": ["借阅人", "档案管理员"]},
       {"stage": "借阅审批", "action": "BPM æµç¨‹ archive_borrow_approve ä¸¤çº§é¡ºåºå®¡æ‰¹ï¼šæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼ˆå®¡æ‰¹æ–¹æ³•与审批人在流程定义内配置,前端只选流程不写死 key;审批中心经自定义表单查看路径 /archive/borrow/detail/approve/index.vue åªè¯»å›žæ˜¾å€Ÿé˜…单与档案信息)。审批通过由 ArchiveBorrowStatusListener ç½®ã€Œå€Ÿå‡ºä¸­(20)」并回写 approveTime,同时条件更新档案状态为「借出中」(影响 0 è¡Œè¯´æ˜Žæ¡£æ¡ˆå·²è¢«å…¶å®ƒæµç¨‹å ç”¨ â†’ æŠ¥é”™å¹¶å›žæ»šï¼Œä¿è¯åŒä¸€æ¡£æ¡ˆä¸ä¼šè¢«ä¸¤æ¡æµç¨‹åŒæ—¶å€Ÿå‡ºï¼‰ï¼›é©³å›žç½®ã€Œå·²é©³å›ž(35)」并回写驳回原因与时间,修改后可重新提交;取消流程退回「草稿(0)」", "roles": ["档案管理员(审核)", "分管领导(审批)"]},
       {"stage": "归还登记", "action": "仅「借出中(20)」可登记归还:填归还备注 â†’ æ¡ä»¶æ›´æ–°å€Ÿé˜…单状态到「已归还(30)」并回写 returnTime ä¸Žå½’还确认人,同时条件更新档案回「在库(10)」,形成完整流转闭环;更新 0 è¡Œè¯´æ˜ŽçŠ¶æ€å·²å˜åŒ–ï¼ŒæŠ¥æ˜Žç¡®é”™è¯¯", "roles": ["档案管理员"]},
       {"stage": "逾期提醒", "action": "不建定时任务、不发站内信:逾期仅在查询时实时计算(状态=借出中 ä¸” å½’还期限早于今天,逾期天数=今天−归还期限),借阅列表的「逾期」列渲染红色「逾期 N å¤©ã€æ ‡ç­¾ï¼›å¦æä¾› /archive/borrow/overdue-page ç‹¬ç«‹å…¥å£ï¼Œåˆ—表页有「只看逾期」切换在 /page ä¸Ž /overdue-page ä¹‹é—´åˆ‡æ¢æ•°æ®æº", "roles": ["档案管理员"]},
       {"stage": "档案统计", "action": "统计页展示档案总数、在库数、借出中数、逾期未还数四张卡片,以及按分类分布(饼图)、按密级分布(柱图)、按载体分布(饼图)三组分布,数据来自 /archive/stat/overview çš„单次聚合查询", "roles": ["档案管理员", "管理层"]}]},
    {"id": "ai", "name": "AI ä¸šåŠ¡æµç¨‹", "mermaid": "mermaid/11-ai-business-flow.mmd", "color": "#56c8d8",
     "summary": "统一模式:业务侧收集上下文(必要时调 MDM/SRM/CRM API å–名称)→组装 Prompt→chat/chatWithImage→解析 JSON→前端预填或建议;无 Function Calling;RAG çŸ¥è¯†åº“已建成但暂无业务消费。",
     "steps": [
@@ -667,6 +702,10 @@
    {"role": "入会申请人/会员管理员", "modules": ["UMS"], "operations": ["填报与修改入会申请(个人/单位)", "上传证明材料附件", "提交 BPM å®¡æ‰¹", "会员列表维护与会员通讯录检索"], "data": ["ums_application", "ums_member"], "scope": "权限码 ums:application:create/update/delete/submit Â· ums:member:create/update/delete/query Â· ums:member:directory;无数据权限隔离(全员可查通讯录,维护类按钮另凭权限码)"},
    {"role": "秘书处/理事会(审批人)", "modules": ["UMS", "BPM"], "operations": ["入会申请两级审批:秘书处初审 â†’ ç†äº‹ä¼šå®¡è®®"], "data": ["ums_application(状态回写)", "bpm_task"], "scope": "审批人由 BPM æµç¨‹å®šä¹‰é…ç½®ï¼ŒåŽç«¯ä¸å†™æ­»ï¼›é€šè¿‡åŽç³»ç»Ÿè‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å·"},
    {"role": "课题申报人/课题管理员", "modules": ["RES"], "operations": ["课题指南发布与截止(课题管理员)", "新建与修改课题申报书(选会员带出申报人信息、上传申报附件)", "提交 BPM å®¡æ‰¹ã€é©³å›žåŽä¿®æ”¹é‡æ", "每月/每批次登记经费拨付记录与凭证", "发布立项通知"], "data": ["research_guideline", "research_application", "research_topic_review", "research_fund_record"], "scope": "权限码 research:guideline:create/update/delete/publish Â· research:application:create/update/delete/submit/notice Â· research:review:create/update/delete Â· research:fund:create/update/delete;无数据权限隔离(列表不按申请人过滤),按钮凭权限码控制"},
    {"role": "档案管理员", "modules": ["ARC"], "operations": ["档案分类(书架)维护:分层建类、设默认密级、启用停用", "档案登记与多条件检索/导出(电子附件 + å®žä½“位置四字段、密级三档)", "借阅登记、提交审批", "归还登记与归还备注", "查看档案统计(存量与流转)"], "data": ["archive_category", "archive_document", "archive_borrow"], "scope": "权限码 archive:category:create/update/delete/query Â· archive:document:create/update/delete/query/export Â· archive:borrow:create/update/delete/query/submit/return Â· archive:stat:query;密级=保密(3) çš„æ¡£æ¡ˆå¦éœ€ archive:document:sensitive æ‰èƒ½æŸ¥çœ‹ï¼Œæ— è¯¥æƒé™æ—¶åˆ—表与导出强制过滤;无数据权限隔离(列表不按人过滤),按钮凭权限码控制"},
    {"role": "借阅人", "modules": ["ARC"], "operations": ["登记借阅单(选在库档案、填借阅人信息/借阅日期/借阅目的/归还期限)", "提交 BPM å€Ÿé˜…审批", "查看借阅进度(跳 BPM æµç¨‹å®žä¾‹è¯¦æƒ…)"], "data": ["archive_borrow", "archive_document(仅 åœ¨åº“ 10 å¯é€‰)"], "scope": "权限码 archive:borrow:create/query/submit;仅草稿(0)与已驳回(35)可改可重提、仅草稿可删;同一档案不得同时存在审批中(15)或借出中(20)的借阅单"},
    {"role": "档案管理员/分管领导(借阅审批人)", "modules": ["ARC", "BPM"], "operations": ["借阅审批两级:档案管理员审核 â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹", "在审批中心通过自定义表单只读查看借阅单与档案信息"], "data": ["archive_borrow(状态回写)", "archive_document(状态随审批结果条件更新)", "bpm_task"], "scope": "审批人与审批方法在 BPM æµç¨‹å®šä¹‰ archive_borrow_approve å†…配置(默认依次审批·角色兜底),后端不写死审批人,部署后由管理员在流程设计器按实际组织调整"},
    {"role": "归档经办人(课题/会员/财务)", "modules": ["ARC", "RES", "UMS", "FIN"], "operations": ["在课题申报书列表对「已立项」单据点「归档」", "在会员列表对「正常」会员点「归档」", "在报销单列表对「已通过」单据点「归档」(与其既有 20→40 å½’档同事务叠加)"], "data": ["archive_document(生成)", "research_application / ums_member / finance_reimbursement(回写 archive_document_id)"], "scope": "权限码 research:application:archive Â· ums:member:archive Â· finance:reimbursement:archive;调用 yudao-module-archive-api çš„ archiveBySource å¹‚等生成档案,重复点击由来源侧按错误码拦截并给出已生成档案编号"},
    {"role": "评审专家/学会秘书处·理事会(课题审批人)", "modules": ["RES", "BPM"], "operations": ["在审批中(15)录入本人的专家评分明细(四项分值 + ç»“论 + æ„è§)", "秘书处形式审查 â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ ä¸‰èŠ‚ç‚¹é¡ºåºå®¡æ‰¹", "在审批中心通过自定义表单只读查看申报书正文、评审明细与拨付记录"], "data": ["research_topic_review", "research_application(状态回写)", "bpm_task"], "scope": "评分录入要求申报单处于审批中(15)·由课题管理员或专家依据 research:review:* æƒé™ç æ‰§è¡Œï¼›å®¡æ‰¹äººä¸Žå®¡æ‰¹æ–¹æ³•在 BPM æµç¨‹å®šä¹‰ research_topic_approve å†…配置(默认依次审批·角色兜底),后端不写死审批人,部署后由管理员在流程设计器按学会实际组织调整"}
  ]
}</script>
@@ -725,6 +764,16 @@
      ç§˜ä¹¦å¤„理事会三级审批
      ç«‹é¡¹ç¼–号与立项通知公告
      ç»è´¹æ‹¨ä»˜è®°å½•与凭证
    æ¡£æ¡ˆç®¡ç† ARCHIVE ç‹¬ç«‹
      æ¡£æ¡ˆåˆ†ç±»ä¹¦æž¶åˆ†å±‚分列
      æ¡£æ¡ˆæ–‡æ¡£é›†ä¸­å­˜å‚¨ä¸Žæ£€ç´¢
      ç”µå­é™„件与实体位置兼具
      å¯†çº§ç®¡æŽ§ä¸Žæ•æ„Ÿæƒé™
      å€Ÿé˜…登记与两级审批
      å½’还登记与流转闭环
      é€¾æœŸæœªè¿˜å®žæ—¶æé†’
      æ¡£æ¡ˆå­˜é‡ä¸Žæµè½¬ç»Ÿè®¡
      è¯¾é¢˜ä¼šå‘˜è´¢åŠ¡ä¸‰æ–¹å½’æ¡£è”åŠ¨
    ç”Ÿäº§ç®¡ç† MES
      ä¸»ç”Ÿäº§è®¡åˆ’ MPS
      ç”Ÿäº§å·¥å•
@@ -1041,6 +1090,12 @@
  RES -->|立项通知写 system_notice å…¬å‘Š| SYS
  RES -->|经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement æŠ¥é”€å•并提交审批·走新建的 yudao-module-finance-api| FIN
  FIN -.->|报销审批结果经 BPM äº‹ä»¶å›žè°ƒ ResearchFundStatusListener å›žå†™è¯¾é¢˜æ‹¨æ¬¾å°è´¦| RES
  ARC[档案管理 ç‹¬ç«‹æ¨¡å— yudao-module-archive åˆ†ç±» æ¡£æ¡ˆ å€Ÿé˜… å½’还 ç»Ÿè®¡] -.->|复用 BPM ä¸¤çº§å®¡æ‰¹ä¸ŽçŠ¶æ€å›žè°ƒÂ·æµç¨‹ key=archive_borrow_approve| BPM
  ARC -.->|档案电子附件走附件中心 recordType=archive_document / archive_borrow| STG
  RES -->|已立项申报单归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id| ARC
  UMS -->|正常状态会员归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id| ARC
  FIN -->|已通过报销单归档·同事务叠加生成「保密」档案并回写 archive_document_id| ARC
  ARC -.->|不反向依赖 ums / finance / research·仅提供 -api å­æ¨¡å—由三方手动调归档| RES
</pre>
<pre style="display:none" class="mmd-src" data-mmd="09-data-flow">
flowchart LR
@@ -1149,6 +1204,27 @@
  RBM -->|"报销单进入同一条 BPM æµç¨‹ finance_reimbursement_approve(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹)·businessKey=报销单 ID"| RBS
  RBS -.->|"BPM ç›‘听器 ResearchFundStatusListener å›žå†™è¯¾é¢˜å°è´¦ é€šè¿‡20 é©³å›ž30(+驳回原因/时间) æ’¤å›ž0"| RFR
  RESNOTE[一期已对接财务·课题台账为拨款台账·报销单为财务出账凭证·两边以 reimbursement_id è½¯å…³è”无外键·财务侧直接删单会留下悬空 ID ç”±ä¸šåŠ¡æŠ¥é”™å…œåº•] -.-> RFR
  subgraph S7 [独立档案管理 archive]
    ACD[档案分类 archive_category æ ‘å½¢] --> ADO[档案 archive_document]
    ADO --> ABR[借阅单 archive_borrow]
    ADO --> AST[档案状态 10在库 20借出中]
    ABR --> ABS[借阅状态 0草稿 15审批中 20借出中 30已归还 35已驳回]
  end
  ACD -->|"新建档案按分类带出 default_secret_level é»˜è®¤å¯†çº§Â·è´¢åŠ¡åˆ†ç±»=3 ä¿å¯†"| ADO
  ADO -.->|"电子附件 recordType=archive_document·表内不存文件地址"| PDFARCH
  ADO -->|"档案编号 DA + yyyyMMdd + 6位流水(Redis·每日重置)·借阅单号 JY + åŒæ—¥æµæ°´"| ADO
  RAP -->|"手动「归档」经 yudao-module-archive-api.archiveBySource(sourceType=2·密级 2内部·sourceNo=立项编号·blobIds=申报书附件)·幂等"| ADO
  UMSM -->|"手动「归档」(sourceType=3·密级 2内部·sourceNo=会员编号·blobIds=会员材料)"| ADO
  RBM -->|"既有 archive å½’档同事务叠加 archiveBySource(sourceType=4·密级 3保密·sourceNo=报销单号·blobIds=发票附件)·报销单 20→40 ä¸Žç”Ÿæˆæ¡£æ¡ˆä¸¤è¡¨å¹¶å†™"| ADO
  ADO -->|"回写来源表 archive_document_id ä½œä¸ºå·²å½’档硬标记·来源侧重复点归档按其错误码拦截并给出已生成档案编号"| RAP
  ADO -->|"回写 archive_document_id"| RBM
  ABR -->|"submit å‰æ ¡éªŒ: åŒä¸€æ¡£æ¡ˆä¸å¾—已有 å®¡æ‰¹ä¸­ 15 æˆ– å€Ÿå‡ºä¸­ 20 çš„借阅单·命中报已借用(带档案名与已存在借阅单号)"| ABS
  ABS -.->|"BPM ç›‘听器 ArchiveBorrowStatusListener å›žå†™ é€šè¿‡20 é©³å›ž35 å–消退回0"| ABR
  ABS -->|"业务动作: å½’还登记 ä»…放行 20 å€Ÿå‡ºä¸­ â†’ 30 å·²å½’还·回写 returnTime ä¸Žå½’还确认人"| ABR
  ABR -->|"审批通过条件更新 archive_document SET status=20 WHERE status=10·归还条件更新回 10·影响 0 è¡Œå³æŠ¥å†²çªå¹¶å›žæ»š"| AST
  ABR -.->|"逾期不落库·查询时实时计算 status=20 ä¸” return_deadline æ—©äºŽä»Šå¤©Â·overdue-page ä¸ºç‹¬ç«‹å…¥å£éžæŸ¥è¯¢å‚æ•°"| ABS
  ADO -.->|"密级=3 ä¿å¯† ä¸”æ—  archive:document:sensitive æ—¶Â·è¯¦æƒ…报错·列表与导出强制过滤 secret_level ä¸ç­‰äºŽ 3"| SEC3[权限点 archive:document:sensitive]
  ARCNOTE[独立模块·三方归档为列表页手动按钮而非事件驱动·归档只生成档案不回写来源单据业务状态(财务侧 20→40 æ˜¯æ—¢æœ‰é€»è¾‘·本模块仅叠加)] -.-> ADO
</pre>
<pre style="display:none" class="mmd-src" data-mmd="10-business-object">
erDiagram
@@ -1196,6 +1272,11 @@
  research_application ||--o{ research_fund_record : "经费拨付记录"
  research_fund_record ||--|| finance_reimbursement : "1:1 ç”Ÿæˆå‡ºè´¦æŠ¥é”€å•(软关联 reimbursement_id)"
  ums_member ||--o{ research_application : "申报人(软关联无外键)"
  archive_category ||--o{ archive_document : "分类归档(树形书架)"
  archive_document ||--o{ archive_borrow : "借阅登记"
  research_application ||--o| archive_document : "已立项归档(archive_document_id)"
  ums_member ||--o| archive_document : "正常会员归档(archive_document_id)"
  finance_reimbursement ||--o| archive_document : "已通过报销归档(archive_document_id)"
</pre>
<pre style="display:none" class="mmd-src" data-mmd="11-ai-business-flow">
flowchart TD
@@ -1351,6 +1432,58 @@
  BOUND["独立模块 yudao-module-research·对 ums_member ä¸ºè½¯å…³è”(无外键)·对财务模块走 -api å­æ¨¡å—本地调用·仅复用 BPM å®¡æ‰¹ã€system å…¬å‘Šä¸Žé™„件中心"] --- K1
</pre>
<pre style="display:none" class="mmd-src" data-mmd="15-archive-flow">
flowchart TD
  subgraph P1 [档案分类·书架管理 archive_category]
    C1[新建分类·可选父分类形成分类树] --> C2["å¡« åˆ†ç±»åç§° Â· ç¼–码 code Â· æŽ’序 Â· åˆ†ç±»è¯´æ˜Ž Â· é»˜è®¤å¯†çº§ Â· çŠ¶æ€"]
    C2 --> C3["编码全局唯一 uk_code Â· çŠ¶æ€ 0启用 1停用 Â· é»˜è®¤å¯†çº§ 1公开 2内部 3保密"]
    C3 --> C4["初始 6 æ¡åˆ†ç±»: ä¼šå‘˜æ¡£æ¡ˆ member Â· è¯¾é¢˜æ¡£æ¡ˆ research Â· ä¼šè®®æ¡£æ¡ˆ meeting Â· è´¢åŠ¡æ¡£æ¡ˆ finance(默认密级=保密 3) Â· æ–‡ä¹¦æ¡£æ¡ˆ document Â· å…šå»ºæ¡£æ¡ˆ party"]
    C4 --> C5["可改可删 Â· æœ‰å­åˆ†ç±»ç¦åˆ  Â· è¯¥åˆ†ç±»ä¸‹æœ‰æ¡£æ¡ˆç¦åˆ  Â· çˆ¶åˆ†ç±»ä¸èƒ½æ˜¯è‡ªèº«æˆ–其子分类 Â· åœç”¨åˆ†ç±»ä¸èƒ½æ–°å¢žæ¡£æ¡ˆ"]
  end
  subgraph P2 [档案管理 archive_document]
    D1["新建档案: æ ‡é¢˜ Â· åˆ†ç±» Â· å¯†çº§ Â· è½½ä½“(1电子 2实体 3电子+实体) Â· å½’档日期 Â· æ‘˜è¦ Â· å…³é”®è¯ tags"] --> D2["实体位置四字段: ä¹¦æž¶å· shelfNo Â· å±‚号 layerNo Â· åˆ—号 columnNo Â· å†Œå·æ¡£æ¡ˆç›’ boxNo Â· å­˜æ”¾ä½ç½®æè¿° storePlace"]
    D2 --> D3["电子附件走 system é™„件中心 Â· recordType=archive_document Â· è¡¨å†…不存文件地址 Â· blobIds ä¸ºå…¨é‡æ›¿æ¢è¯­ä¹‰"]
    D3 --> D4["档案编号 DA + yyyyMMdd + 6位流水 (Redis·每日重置)"]
    D4 --> D5["状态 10 åœ¨åº“ Â· å¯æ”¹å¯åˆ "]
    D5 -->|"借阅审批通过"| D6["状态 20 å€Ÿå‡ºä¸­ Â· ç¦æ”¹ Â· å­˜åœ¨æœªå½’还借阅单时禁删"]
    D6 -->|"归还登记"| D5
  end
  subgraph P3 [三方归档联动·列表页手动「归档」按钮→yudao-module-archive-api]
    AD["ArchiveDocumentApi.archiveBySource å¹‚ç­‰: å…ˆæŒ‰ source_type + source_id æŸ¥ Â· å‘½ä¸­ç›´æŽ¥è¿”回既有档案 ID"]
    AD2["按 categoryCode å–分类(member / research / finance) Â· åˆ†ç±»ä¸å­˜åœ¨æŠ¥ DOCUMENT_SOURCE_CATEGORY_NOT_EXISTS"]
    AD3["生成 DA ç¼–号 + è½åº“ + bindAttachments(recordType=archive_document)"]
    AD4["回写来源表 archive_document_id ä½œä¸ºå·²å½’档硬标记 Â· æ¥æºä¾§é‡å¤å½’档按其自身错误码拦截并给出已生成档案编号(archive ä¾§ DOCUMENT_DUPLICATE_SOURCE ä¸ºå…œåº•)"]
    R1["课题申报书 research_application Â· ä»… status=20 å·²ç«‹é¡¹ Â· æƒé™ research:application:archive"] -->|"sourceType=2 Â· å¯†çº§ 2内部 Â· sourceNo=立项编号 Â· blobIds=申报书附件"| AD
    M1["会员 ums_member Â· ä»… status=1 æ­£å¸¸ Â· æƒé™ ums:member:archive"] -->|"sourceType=3 Â· å¯†çº§ 2内部 Â· sourceNo=会员编号"| AD
    F1["财务报销单 finance_reimbursement Â· ä»… status=20 å·²é€šè¿‡ Â· æƒé™ finance:reimbursement:archive"] -->|"sourceType=4 Â· å¯†çº§ 3保密 Â· sourceNo=报销单号 Â· blobIds=发票附件 Â· ä¸Žæ—¢æœ‰ 20→40 å½’档同事务叠加"| AD
    AD --> AD2
    AD2 --> AD3
    AD3 --> AD4
  end
  AD3 -->|"归档结果: æ–°æ¡£æ¡ˆç›´æŽ¥è½ä¸º åœ¨åº“ 10"| D5
  subgraph P4 [借阅登记与审批 archive_borrow]
    B1["登记借阅: é€‰æ¡£æ¡ˆ(仅 åœ¨åº“ 10 å¯é€‰) Â· å€Ÿé˜…人 Â· éƒ¨é—¨ Â· è”系电话 Â· å€Ÿé˜…日期 Â· å€Ÿé˜…目的 Â· å½’还期限"] --> B2["借阅单号 JY + yyyyMMdd + 6位流水 (Redis·每日重置) Â· çŠ¶æ€ 0 è‰ç¨¿"]
    B2 --> B3["submit å‰æ ¡éªŒ: åŒä¸€æ¡£æ¡ˆä¸å¾—存在 å®¡æ‰¹ä¸­ 15 æˆ– å€Ÿå‡ºä¸­ 20 çš„借阅单 Â· å‘½ä¸­æŠ¥ å·²å€Ÿç”¨(带档案名与已存在借阅单号)"]
    B3 --> B4["submit ä¼  id ä¸Ž processDefinitionKey (query å‚æ•°) Â· å…ˆè§£æžæµç¨‹åˆ—表再建单(无已发布流程则直接报错·不留孤儿草稿) Â· çŠ¶æ€ 15 å®¡æ‰¹ä¸­ Â· å›žå†™ processInstanceId"]
    B4 --> B5["BPM æµç¨‹ archive_borrow_approve ä¸¤çº§: æ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹"]
  end
  B5 -->|"审批通过"| B6["状态 20 å€Ÿå‡ºä¸­ Â· å›žå†™ approveTime Â· æ¡ä»¶æ›´æ–°æ¡£æ¡ˆ SET status=20 WHERE status=10(影响 0 è¡Œåˆ™æŠ¥ æ¡£æ¡ˆå·²è¢«å ç”¨ å¹¶å›žæ»š)"]
  B5 -->|"审批驳回"| B7["状态 35 å·²é©³å›ž Â· å›žå†™é©³å›žåŽŸå› ä¸Žæ—¶é—´ Â· ä¿®æ”¹åŽå¯é‡æ–° submit"]
  B5 -->|"取消流程"| B2
  B6 -->|"归还登记 return Â· æ¡ä»¶æ›´æ–° status=20 â†’ 30"| B8["状态 30 å·²å½’还 Â· å›žå†™ returnTime ä¸Žå½’还确认人 Â· å½’还备注"]
  B8 -->|"条件更新档案 SET status=10 WHERE status=20"| D5
  B6 -->|"仅 0 è‰ç¨¿ ä¸Ž 35 å·²é©³å›ž å¯æ”¹å¯é‡æ Â· ä»… 0 è‰ç¨¿ å¯åˆ "| B2
  B6 -->|"仅 20 å€Ÿå‡ºä¸­ å¯ç™»è®°å½’还"| B8
  OV["逾期不落库·查询时实时计算: status=20 ä¸” return_deadline æ—©äºŽä»Šå¤© Â· overdueDays = ä»Šå¤© å‡ å½’还期限"] -.-> B6
  OVP["GET /archive/borrow/overdue-page ç‹¬ç«‹å…¥å£(非查询参数) Â· åˆ—表「逾期」列渲染红色「逾期 N å¤©ã€ Â· ä¸å»º infra_job ä¸å‘站内信 Â· åˆ—表页另有「只看逾期」切换"] -.-> OV
  ST["档案统计 GET /archive/stat/overview: æ¡£æ¡ˆæ€»æ•° Â· åœ¨åº“æ•° Â· å€Ÿå‡ºä¸­æ•° Â· é€¾æœŸæœªè¿˜æ•° + æŒ‰åˆ†ç±»/按密级/按载体 ä¸‰ç»„分布"] -.-> D5
  SEC["敏感档案管控: å¯†çº§=3 ä¿å¯† ä¸”当前用户无 archive:document:sensitive æ—¶ Â· è¯¦æƒ…接口直接报错 Â· åˆ—表与导出强制过滤 secret_level ä¸ç­‰äºŽ 3"] -.-> D1
  ATT["附件 recordType = archive_document / archive_borrow"] -.-> D3
  ROL["审批中心看单: æµç¨‹å®šä¹‰è¡¨å•类型=自定义表单 Â· åˆ›å»ºè·¯å¾„ /archive/borrow/index Â· æŸ¥çœ‹è·¯å¾„ /archive/borrow/detail/approve/index.vue Â· æŒ‰ businessKey å›žæ˜¾å€Ÿé˜…单"] -.-> B5
  LIS["监听器 ArchiveBorrowStatusListener(流程 key=archive_borrow_approve Â· businessKey=借阅单 ID): é€šè¿‡â†’20 å€Ÿå‡ºä¸­ + approveTime å¹¶æ¡ä»¶æ›´æ–°æ¡£æ¡ˆ â†’ 20 Â· é©³å›žâ†’35 + åŽŸå› ä¸Žæ—¶é—´ Â· å–消→退回 0 Â· ä¸Ž finance/research ç›‘听器订阅不同 key äº’不干扰"] -.-> B5
  BOUND["独立模块 yudao-module-archive(含 -api å­æ¨¡å—)·依赖 system/bpm·不反向依赖 ums/finance/research(避免循环依赖)·三方仅依赖 archive-api æ‰‹åŠ¨è°ƒå½’æ¡£"] --- D1
</pre>
<script type="module">
const DATA = {};
async function loadJson(key){
@@ -1369,7 +1502,7 @@
const mmdSrc = {};
document.querySelectorAll('pre.mmd-src').forEach(p=>{ mmdSrc[p.dataset.mmd]=p.textContent.trim(); });
const flowColor = {sales:'var(--c-sale)',purchase:'var(--c-erp)',production:'var(--c-prod)',quality:'var(--c-qc)',warehouse:'var(--c-wm)',aftersales:'var(--c-as)',finance:'var(--c-fin)',ums:'var(--c-ums)',research:'var(--c-res)',ai:'var(--c-ai)'};
const flowColor = {sales:'var(--c-sale)',purchase:'var(--c-erp)',production:'var(--c-prod)',quality:'var(--c-qc)',warehouse:'var(--c-wm)',aftersales:'var(--c-as)',finance:'var(--c-fin)',ums:'var(--c-ums)',research:'var(--c-res)',archive:'var(--c-arch)',ai:'var(--c-ai)'};
async function boot(){
  DATA.business = await loadJson('business');
@@ -1406,8 +1539,8 @@
  </div>
  <h3 class="sec">业务模块</h3>
  <div class="grid cards">`;
  const targets = {crm:'flow-sales',erp:'flow-purchase',pro:'flow-production',wm:'flow-warehouse',qc:'flow-quality',aftersales:'flow-aftersales',fin:'flow-finance',ums:'flow-ums',res:'flow-research',ai:'ai',srm:'flow-purchase',mdm:'objects',dv:'objects',hrm:'objects',bpm:'relation'};
  const accs = {mdm:'var(--c-base)',crm:'var(--c-sale)',srm:'var(--c-sup)',erp:'var(--c-erp)',fin:'var(--c-fin)',ums:'var(--c-ums)',res:'var(--c-res)',pro:'var(--c-prod)',wm:'var(--c-wm)',qc:'var(--c-qc)',dv:'var(--c-dv)',aftersales:'var(--c-as)',hrm:'var(--c-hrm)',bpm:'var(--c-bpm)',ai:'var(--c-ai)'};
  const targets = {crm:'flow-sales',erp:'flow-purchase',pro:'flow-production',wm:'flow-warehouse',qc:'flow-quality',aftersales:'flow-aftersales',fin:'flow-finance',ums:'flow-ums',res:'flow-research',arc:'flow-archive',ai:'ai',srm:'flow-purchase',mdm:'objects',dv:'objects',hrm:'objects',bpm:'relation'};
  const accs = {mdm:'var(--c-base)',crm:'var(--c-sale)',srm:'var(--c-sup)',erp:'var(--c-erp)',fin:'var(--c-fin)',ums:'var(--c-ums)',res:'var(--c-res)',arc:'var(--c-arch)',pro:'var(--c-prod)',wm:'var(--c-wm)',qc:'var(--c-qc)',dv:'var(--c-dv)',aftersales:'var(--c-as)',hrm:'var(--c-hrm)',bpm:'var(--c-bpm)',ai:'var(--c-ai)'};
  DATA.modules.modules.forEach(m=>{
    html += `<div class="card" style="--acc:${accs[m.key]||'#2c5cff'}" data-goto="${targets[m.key]||'panorama'}">
      <h3>${esc(m.name)}<span class="tag">${esc(m.tag)}</span></h3>
docs/project-business/mermaid/01-business-mindmap.mmd
@@ -52,6 +52,16 @@
      ç§˜ä¹¦å¤„理事会三级审批
      ç«‹é¡¹ç¼–号与立项通知公告
      ç»è´¹æ‹¨ä»˜è®°å½•与凭证
    æ¡£æ¡ˆç®¡ç† ARCHIVE ç‹¬ç«‹
      æ¡£æ¡ˆåˆ†ç±»ä¹¦æž¶åˆ†å±‚分列
      æ¡£æ¡ˆæ–‡æ¡£é›†ä¸­å­˜å‚¨ä¸Žæ£€ç´¢
      ç”µå­é™„件与实体位置兼具
      å¯†çº§ç®¡æŽ§ä¸Žæ•æ„Ÿæƒé™
      å€Ÿé˜…登记与两级审批
      å½’还登记与流转闭环
      é€¾æœŸæœªè¿˜å®žæ—¶æé†’
      æ¡£æ¡ˆå­˜é‡ä¸Žæµè½¬ç»Ÿè®¡
      è¯¾é¢˜ä¼šå‘˜è´¢åŠ¡ä¸‰æ–¹å½’æ¡£è”åŠ¨
    ç”Ÿäº§ç®¡ç† MES
      ä¸»ç”Ÿäº§è®¡åˆ’ MPS
      ç”Ÿäº§å·¥å•
docs/project-business/mermaid/08-module-relation.mmd
@@ -41,3 +41,9 @@
  RES -->|立项通知写 system_notice å…¬å‘Š| SYS
  RES -->|经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement æŠ¥é”€å•并提交审批·走新建的 yudao-module-finance-api| FIN
  FIN -.->|报销审批结果经 BPM äº‹ä»¶å›žè°ƒ ResearchFundStatusListener å›žå†™è¯¾é¢˜æ‹¨æ¬¾å°è´¦| RES
  ARC[档案管理 ç‹¬ç«‹æ¨¡å— yudao-module-archive åˆ†ç±» æ¡£æ¡ˆ å€Ÿé˜… å½’还 ç»Ÿè®¡] -.->|复用 BPM ä¸¤çº§å®¡æ‰¹ä¸ŽçŠ¶æ€å›žè°ƒÂ·æµç¨‹ key=archive_borrow_approve| BPM
  ARC -.->|档案电子附件走附件中心 recordType=archive_document / archive_borrow| STG
  RES -->|已立项申报单归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id| ARC
  UMS -->|正常状态会员归档·经 yudao-module-archive-api ç”Ÿæˆæ¡£æ¡ˆå¹¶å›žå†™ archive_document_id| ARC
  FIN -->|已通过报销单归档·同事务叠加生成「保密」档案并回写 archive_document_id| ARC
  ARC -.->|不反向依赖 ums / finance / research·仅提供 -api å­æ¨¡å—由三方手动调归档| RES
docs/project-business/mermaid/09-data-flow.mmd
@@ -104,3 +104,24 @@
  RBM -->|"报销单进入同一条 BPM æµç¨‹ finance_reimbursement_approve(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹)·businessKey=报销单 ID"| RBS
  RBS -.->|"BPM ç›‘听器 ResearchFundStatusListener å›žå†™è¯¾é¢˜å°è´¦ é€šè¿‡20 é©³å›ž30(+驳回原因/时间) æ’¤å›ž0"| RFR
  RESNOTE[一期已对接财务·课题台账为拨款台账·报销单为财务出账凭证·两边以 reimbursement_id è½¯å…³è”无外键·财务侧直接删单会留下悬空 ID ç”±ä¸šåŠ¡æŠ¥é”™å…œåº•] -.-> RFR
  subgraph S7 [独立档案管理 archive]
    ACD[档案分类 archive_category æ ‘å½¢] --> ADO[档案 archive_document]
    ADO --> ABR[借阅单 archive_borrow]
    ADO --> AST[档案状态 10在库 20借出中]
    ABR --> ABS[借阅状态 0草稿 15审批中 20借出中 30已归还 35已驳回]
  end
  ACD -->|"新建档案按分类带出 default_secret_level é»˜è®¤å¯†çº§Â·è´¢åŠ¡åˆ†ç±»=3 ä¿å¯†"| ADO
  ADO -.->|"电子附件 recordType=archive_document·表内不存文件地址"| PDFARCH
  ADO -->|"档案编号 DA + yyyyMMdd + 6位流水(Redis·每日重置)·借阅单号 JY + åŒæ—¥æµæ°´"| ADO
  RAP -->|"手动「归档」经 yudao-module-archive-api.archiveBySource(sourceType=2·密级 2内部·sourceNo=立项编号·blobIds=申报书附件)·幂等"| ADO
  UMSM -->|"手动「归档」(sourceType=3·密级 2内部·sourceNo=会员编号·blobIds=会员材料)"| ADO
  RBM -->|"既有 archive å½’档同事务叠加 archiveBySource(sourceType=4·密级 3保密·sourceNo=报销单号·blobIds=发票附件)·报销单 20→40 ä¸Žç”Ÿæˆæ¡£æ¡ˆä¸¤è¡¨å¹¶å†™"| ADO
  ADO -->|"回写来源表 archive_document_id ä½œä¸ºå·²å½’档硬标记·来源侧重复点归档按其错误码拦截并给出已生成档案编号"| RAP
  ADO -->|"回写 archive_document_id"| RBM
  ABR -->|"submit å‰æ ¡éªŒ: åŒä¸€æ¡£æ¡ˆä¸å¾—已有 å®¡æ‰¹ä¸­ 15 æˆ– å€Ÿå‡ºä¸­ 20 çš„借阅单·命中报已借用(带档案名与已存在借阅单号)"| ABS
  ABS -.->|"BPM ç›‘听器 ArchiveBorrowStatusListener å›žå†™ é€šè¿‡20 é©³å›ž35 å–消退回0"| ABR
  ABS -->|"业务动作: å½’还登记 ä»…放行 20 å€Ÿå‡ºä¸­ â†’ 30 å·²å½’还·回写 returnTime ä¸Žå½’还确认人"| ABR
  ABR -->|"审批通过条件更新 archive_document SET status=20 WHERE status=10·归还条件更新回 10·影响 0 è¡Œå³æŠ¥å†²çªå¹¶å›žæ»š"| AST
  ABR -.->|"逾期不落库·查询时实时计算 status=20 ä¸” return_deadline æ—©äºŽä»Šå¤©Â·overdue-page ä¸ºç‹¬ç«‹å…¥å£éžæŸ¥è¯¢å‚æ•°"| ABS
  ADO -.->|"密级=3 ä¿å¯† ä¸”æ—  archive:document:sensitive æ—¶Â·è¯¦æƒ…报错·列表与导出强制过滤 secret_level ä¸ç­‰äºŽ 3"| SEC3[权限点 archive:document:sensitive]
  ARCNOTE[独立模块·三方归档为列表页手动按钮而非事件驱动·归档只生成档案不回写来源单据业务状态(财务侧 20→40 æ˜¯æ—¢æœ‰é€»è¾‘·本模块仅叠加)] -.-> ADO
docs/project-business/mermaid/10-business-object.mmd
@@ -43,3 +43,8 @@
  research_application ||--o{ research_fund_record : "经费拨付记录"
  research_fund_record ||--|| finance_reimbursement : "1:1 ç”Ÿæˆå‡ºè´¦æŠ¥é”€å•(软关联 reimbursement_id)"
  ums_member ||--o{ research_application : "申报人(软关联无外键)"
  archive_category ||--o{ archive_document : "分类归档(树形书架)"
  archive_document ||--o{ archive_borrow : "借阅登记"
  research_application ||--o| archive_document : "已立项归档(archive_document_id)"
  ums_member ||--o| archive_document : "正常会员归档(archive_document_id)"
  finance_reimbursement ||--o| archive_document : "已通过报销归档(archive_document_id)"
docs/project-business/mermaid/13-ums-membership-flow.mmd
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
flowchart TD
  A1[填报入会申请·选会员类型 1个人 / 2单位] --> A2[个人分支: å§“名 è”系电话 é‚®ç®± å·¥ä½œå•位 èŒç§° ç ”究方向 è¡Œä¸š åœ°åŒº]
  A1 --> A3[单位分支: å•位名称 æ³•定代表人 ç»Ÿä¸€ç¤¾ä¼šä¿¡ç”¨ä»£ç  ç ”究方向 è¡Œä¸š åœ°åŒº]
  A2 --> A4[选申请等级·按会员类型切换字典 ums_personal_grade / ums_unit_grade]
  A3 --> A4
  A4 --> A5[证明材料附件: å…ˆä¼  system storage-blob upload å– blobId]
  A5 --> S1[保存 create·附件按该单全部 blobIds ç»‘定]
  S1 --> G1["申请编号 SQ + yyyyMMdd + 6位流水(Redis·每日重置)·后端生成不可改"]
  G1 --> K1[草稿 0]
  K1 -->|update ä¿®æ”¹ç”³è¯·| K1
  K1 -->|submit é€‰å®¡æ‰¹æµç¨‹ processDefinitionKey| K2[审批中 15·回写 processInstanceId]
  K2 -->|BPM å®¡æ‰¹é€šè¿‡| K3[已通过 20]
  K2 -->|BPM å®¡æ‰¹é©³å›ž| K4[已驳回 35]
  K2 -->|BPM å®¡æ‰¹å–消| K1
  K4 -->|修改后重新 submit| K2
  K3 --> M1["审批通过钩子: è‡ªåŠ¨åˆ›å»ºä¼šå‘˜ ums_member (内部先查状态确为已通过才执行)"]
  M1 --> M2["会员编号 HY + yyyyMMdd + 6位流水(Redis)"]
  M2 --> M3["创建系统登录账号 AdminUserApi.createUser·用户名与初始密码=联系电话·昵称=申请人姓名·部门=学会会员"]
  M3 --> M4[会员落库·入会日期=当天·状态 1正常·来源申请单 applicationId å›žå¡«]
  M4 --> D2[会员列表·可手工 create / update / delete]
  D2 --> V1["等级类型校验: å•位会员(memberType=2) ä¸å¯é€‰ã€Žç†äº‹é•¿ã€(memberGrade=5) ç›´æŽ¥æ‹’绝"]
  M4 --> D1[会员通讯录·只读·按地区 regionCode ä¸Žè¡Œä¸š industry æ£€ç´¢]
  ATT1["附件 recordType = ums_application"] -.-> K1
  ATT2["附件 recordType = ums_member"] -.-> M4
  RES1["仅草稿 0 å¯ delete·连带清理附件"] -.-> K1
  RES2["仅草稿 0 ä¸Žå·²é©³å›ž 35 å¯ update·其余状态只读"] -.-> K4
  ROL["审批链: BPM æµç¨‹å®šä¹‰ ums_membership_approve(归属分类 ums_membership_approve) ç§˜ä¹¦å¤„初审 â†’ ç†äº‹ä¼šå®¡è®®Â·ä¸¤èŠ‚ç‚¹å®¡æ‰¹äºº=角色策略(超管角色兜底)·后端按分类实时取流程、不写死 key"] -.-> K2
  F1["审批中心看单: æµç¨‹å®šä¹‰è¡¨å•类型=自定义表单·查看路径 /ums/application/detail/approve/index.vue·按 businessKey å›žæ˜¾ç”³è¯·å•"] -.-> K2
  N1["独立模块·不与其他业务模块产生单据关联·仅复用 BPM å®¡æ‰¹ä¸Ž system é™„件中心"] --- K1
docs/project-business/mermaid/14-research-flow.mmd
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
flowchart TD
  subgraph P1 [课题指南发布]
    G0[新建指南·选类别 1重点课题 / 2一般课题] --> G1[填指南名称 ç”³æŠ¥èµ·æ­¢æ—¶é—´ æŒ‡å—正文 é™„ä»¶]
    G1 --> G2["指南编号 GL + yyyyMMdd + 6位流水(Redis·每日重置)"]
    G2 --> G3[草稿 0·仅草稿可改可删]
    G3 -->|已产生申报单则禁止删除| G3B["提示该指南下已有 N å¼ ç”³æŠ¥å•·需先处理申报单"]
    G3 -->|publish å‘布| G4["已发布 20·回写 publishTime"]
    G4 -->|close æˆªæ­¢| G5[已截止 30]
  end
  G4 -->|"指南已发布才可作为申报前提"| A1
  subgraph P2 [在线申报]
    A1[选指南·要求已发布 ä¸”在申报期内] --> A2[选会员 ums_member è‡ªåŠ¨å¸¦å‡º å§“名 å·¥ä½œå•位 èŒç§° ç”µè¯ é‚®ç®±]
    A2 --> A3[填课题名称 è¯¾é¢˜ç»„成员 ç”³è¯·ç»è´¹ ç”³æŠ¥ä¹¦æ­£æ–‡ ç”³æŠ¥é™„ä»¶]
    A3 --> A4["申报编号 SB + yyyyMMdd + 6位流水·类别冗余自指南不信任前端传值"]
    A4 --> K1[草稿 0]
  end
  K1 -->|update ä¿®æ”¹Â·ä»…草稿 0 ä¸Žå·²é©³å›ž 35 æ”¾è¡Œ| K1
  K1 -->|delete åˆ é™¤Â·ä»…草稿 0 æ”¾è¡ŒÂ·è¿žå¸¦æ¸…理附件| K1
  K1 -->|submit é€‰å®¡æ‰¹æµç¨‹ processDefinitionKey| K2["审批中 15·回写 processInstanceId"]
  K1 -->|"submit å‰é€æ¡æ ¡éªŒ: â‘  æŒ‡å—必须已发布 â‘¡ å½“前时间必须在申报起止期内"| KERR["任一不满足即拒绝提交·提示带指南名与实际状态或起止时间"]
  K2 -->|"仅审批中可增删改专家评分"| RK[专家评审明细 research_topic_review·一位专家一行]
  RK -->|"后端逐行重算 compositeScore = é€‰é¢˜0.3 + æ–¹æ¡ˆ0.3 + å›¢é˜Ÿ0.2 + æˆæžœ0.2"| RC[按加权分降序重排名次 ranking]
  RC -->|"回写主单: reviewAvgScore=全部专家均分 Â· reviewPass=均分达标 ä¸” å¤šæ•°ç»“论为推荐立项"| SUM[申报单评审汇总·删除最后一条明细时清空汇总]
  SUM -->|"秘书处汇总申报书与评审结果"| K3
  K2 --> K3
  subgraph P3 [立项审批 BPM research_topic_approve]
    K3["三节点顺序·审批方法与审批人在流程定义内配置"] --> KA[Activity_secretariat_review ç§˜ä¹¦å¤„形式审查]
    KA -->|通过| KB[Activity_expert_review ä¸“家评审]
    KB -->|通过| KC[Activity_council_approve å­¦ä¼šç«‹é¡¹å®¡æ‰¹]
  end
  KC -->|BPM å®¡æ‰¹é€šè¿‡| K4["已立项 20·监听器生成 ç«‹é¡¹ç¼–号 LX + yyyyMMdd + 6位流水 ä¸Ž approveTime·已有编号则幂等跳过"]
  KC -->|BPM å®¡æ‰¹é©³å›ž| K5[已驳回 35]
  KC -->|BPM å®¡æ‰¹å–消| K1
  KA -->|驳回| K5
  K5 -->|修改后重新 submit| K2
  K4 -->|publishNotice å‘布立项通知| N1["system_notice å…¬å‘ŠÂ·æ ‡é¢˜ã€Žå…³äºŽå…¬å¸ƒç«‹é¡¹è¯¾é¢˜çš„通知』·正文含课题名 ç«‹é¡¹ç¼–号 ç±»åˆ« ç”³æŠ¥äºº å•位 èµ„助金额·回写 noticeId·重复发布被拦截"]
  K4 -->|登记拨款记录 research_fund_record| F1[填批次名称 æ‹¨ä»˜é‡‘额 æ‹¨ä»˜æ—¥æœŸ æ”¶æ¬¾å•位 å¤‡æ³¨Â·å‡­è¯é™„ä»¶å¿…å¡«]
  F1 -->|"金额校验: å·²ç™»è®°é‡‘额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出申报单申请经费·超出时报出本次 å·²ç™»è®° åˆè®¡ é¢„算四项数值"| FGEN["自动生成 1:1 è´¢åŠ¡æŠ¥é”€å• finance_reimbursement(1 è¡Œæ˜Žç»†: è´¹ç”¨ç±»åž‹=其他 å‘票金额=拨付金额 å‘票日期=拨付日期 è´¹ç”¨è¯´æ˜Ž=课题名+批次)·附同一批凭证·立即提交 BPM·回写报销单号"]
  FGEN -->|回写台账状态| F3[报销审批中 15]
  F3 -->|"财务审核 Activity_finance_review"| FAPP[财务主管审批 Activity_finance_approve]
  FAPP -->|BPM å®¡æ‰¹é€šè¿‡| F4[已拨付 20]
  FAPP -->|BPM å®¡æ‰¹é©³å›ž| F5["已驳回 30·回写驳回原因与驳回时间"]
  F5 -->|"修改金额或凭证后重新发起报销"| F3
  FAPP -->|"发起人/管理员取消流程"| F2["待拨付 0·可编辑可删除"]
  F2 -->|"仅待拨付 0 å¯æ”¹å¯åˆ Â·åˆ é™¤æ—¶è”动清理财务侧草稿报销单"| F2
  F2 -->|"修改后重新发起报销"| F3
  ATT1["附件 recordType = research_guideline"] -.-> G3
  ATT2["附件 recordType = research_application"] -.-> K1
  ATT3["附件 recordType = research_fund_record·同一批 blob åŒç»‘ finance_reimbursement(按 recordType+recordId éš”离互不影响)"] -.-> F1
  ROL["审批中心看单: æµç¨‹å®šä¹‰è¡¨å•类型=自定义表单·创建路径 /research/application/index Â· æŸ¥çœ‹è·¯å¾„ /research/application/detail/approve/index.vue·按 businessKey å›žæ˜¾ç”³æŠ¥å•"] -.-> K3
  ROL2["审批人取值: ä¸‰èŠ‚ç‚¹åœ¨æµç¨‹å®šä¹‰å†…é…å¥½å®¡æ‰¹æ–¹æ³•(依次审批)与审批人策略·部署后由管理员在 BPM æµç¨‹è®¾è®¡å™¨æŒ‰å­¦ä¼šå®žé™…组织调整·提交时前端只让选流程不选审批人"] -.-> K3
  LIS2["监听器 ResearchFundStatusListener(流程 key=finance_reimbursement_approve·businessKey=报销单 ID): é€šè¿‡â†’台账已拨付 20·驳回→已驳回 30+原因与时间·取消流程→退回待拨付 0(发起人撤回仅退回发起节点、流程未结束,不触发回写)·与 finance è‡ªå·±çš„监听器订阅同一 key å¹¶è¡Œè§¦å‘"] -.-> F3
  FINAPI["跨模块走新建的 yudao-module-finance-api(FinanceReimbursementApi·本地 Bean éž HTTP): æ— å·²å‘布报销审批流程时先解析即报错·不建单·避免留下孤儿草稿报销单"] -.-> FGEN
  RES1["一期已对接财务: æ‹¨æ¬¾å¿…须走 finance_reimbursement æŠ¥é”€å•出账·课题侧台账=拨款台账·财务报销单=出账凭证·两边以 reimbursement_id è½¯å…³è”(无外键)·财务审批结果自动回写本台账"] -.-> F1
  RES2["二期范围(本图未覆盖): ç»“项验收 ç»“项证书 æˆæžœå½’档知识库 é‡‡çº³æƒ…况与社会效益标注"] -.-> K4
  BOUND["独立模块 yudao-module-research·对 ums_member ä¸ºè½¯å…³è”(无外键)·对财务模块走 -api å­æ¨¡å—本地调用·仅复用 BPM å®¡æ‰¹ã€system å…¬å‘Šä¸Žé™„件中心"] --- K1
docs/project-business/mermaid/15-archive-flow.mmd
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
flowchart TD
  subgraph P1 [档案分类·书架管理 archive_category]
    C1[新建分类·可选父分类形成分类树] --> C2["å¡« åˆ†ç±»åç§° Â· ç¼–码 code Â· æŽ’序 Â· åˆ†ç±»è¯´æ˜Ž Â· é»˜è®¤å¯†çº§ Â· çŠ¶æ€"]
    C2 --> C3["编码全局唯一 uk_code Â· çŠ¶æ€ 0启用 1停用 Â· é»˜è®¤å¯†çº§ 1公开 2内部 3保密"]
    C3 --> C4["初始 6 æ¡åˆ†ç±»: ä¼šå‘˜æ¡£æ¡ˆ member Â· è¯¾é¢˜æ¡£æ¡ˆ research Â· ä¼šè®®æ¡£æ¡ˆ meeting Â· è´¢åŠ¡æ¡£æ¡ˆ finance(默认密级=保密 3) Â· æ–‡ä¹¦æ¡£æ¡ˆ document Â· å…šå»ºæ¡£æ¡ˆ party"]
    C4 --> C5["可改可删 Â· æœ‰å­åˆ†ç±»ç¦åˆ  Â· è¯¥åˆ†ç±»ä¸‹æœ‰æ¡£æ¡ˆç¦åˆ  Â· çˆ¶åˆ†ç±»ä¸èƒ½æ˜¯è‡ªèº«æˆ–其子分类 Â· åœç”¨åˆ†ç±»ä¸èƒ½æ–°å¢žæ¡£æ¡ˆ"]
  end
  subgraph P2 [档案管理 archive_document]
    D1["新建档案: æ ‡é¢˜ Â· åˆ†ç±» Â· å¯†çº§ Â· è½½ä½“(1电子 2实体 3电子+实体) Â· å½’档日期 Â· æ‘˜è¦ Â· å…³é”®è¯ tags"] --> D2["实体位置四字段: ä¹¦æž¶å· shelfNo Â· å±‚号 layerNo Â· åˆ—号 columnNo Â· å†Œå·æ¡£æ¡ˆç›’ boxNo Â· å­˜æ”¾ä½ç½®æè¿° storePlace"]
    D2 --> D3["电子附件走 system é™„件中心 Â· recordType=archive_document Â· è¡¨å†…不存文件地址 Â· blobIds ä¸ºå…¨é‡æ›¿æ¢è¯­ä¹‰"]
    D3 --> D4["档案编号 DA + yyyyMMdd + 6位流水 (Redis·每日重置)"]
    D4 --> D5["状态 10 åœ¨åº“ Â· å¯æ”¹å¯åˆ "]
    D5 -->|"借阅审批通过"| D6["状态 20 å€Ÿå‡ºä¸­ Â· ç¦æ”¹ Â· å­˜åœ¨æœªå½’还借阅单时禁删"]
    D6 -->|"归还登记"| D5
  end
  subgraph P3 [三方归档联动·列表页手动「归档」按钮→yudao-module-archive-api]
    AD["ArchiveDocumentApi.archiveBySource å¹‚ç­‰: å…ˆæŒ‰ source_type + source_id æŸ¥ Â· å‘½ä¸­ç›´æŽ¥è¿”回既有档案 ID"]
    AD2["按 categoryCode å–分类(member / research / finance) Â· åˆ†ç±»ä¸å­˜åœ¨æŠ¥ DOCUMENT_SOURCE_CATEGORY_NOT_EXISTS"]
    AD3["生成 DA ç¼–号 + è½åº“ + bindAttachments(recordType=archive_document)"]
    AD4["回写来源表 archive_document_id ä½œä¸ºå·²å½’档硬标记 Â· æ¥æºä¾§é‡å¤å½’档按其自身错误码拦截并给出已生成档案编号(archive ä¾§ DOCUMENT_DUPLICATE_SOURCE ä¸ºå…œåº•)"]
    R1["课题申报书 research_application Â· ä»… status=20 å·²ç«‹é¡¹ Â· æƒé™ research:application:archive"] -->|"sourceType=2 Â· å¯†çº§ 2内部 Â· sourceNo=立项编号 Â· blobIds=申报书附件"| AD
    M1["会员 ums_member Â· ä»… status=1 æ­£å¸¸ Â· æƒé™ ums:member:archive"] -->|"sourceType=3 Â· å¯†çº§ 2内部 Â· sourceNo=会员编号"| AD
    F1["财务报销单 finance_reimbursement Â· ä»… status=20 å·²é€šè¿‡ Â· æƒé™ finance:reimbursement:archive"] -->|"sourceType=4 Â· å¯†çº§ 3保密 Â· sourceNo=报销单号 Â· blobIds=发票附件 Â· ä¸Žæ—¢æœ‰ 20→40 å½’档同事务叠加"| AD
    AD --> AD2
    AD2 --> AD3
    AD3 --> AD4
  end
  AD3 -->|"归档结果: æ–°æ¡£æ¡ˆç›´æŽ¥è½ä¸º åœ¨åº“ 10"| D5
  subgraph P4 [借阅登记与审批 archive_borrow]
    B1["登记借阅: é€‰æ¡£æ¡ˆ(仅 åœ¨åº“ 10 å¯é€‰) Â· å€Ÿé˜…人 Â· éƒ¨é—¨ Â· è”系电话 Â· å€Ÿé˜…日期 Â· å€Ÿé˜…目的 Â· å½’还期限"] --> B2["借阅单号 JY + yyyyMMdd + 6位流水 (Redis·每日重置) Â· çŠ¶æ€ 0 è‰ç¨¿"]
    B2 --> B3["submit å‰æ ¡éªŒ: åŒä¸€æ¡£æ¡ˆä¸å¾—存在 å®¡æ‰¹ä¸­ 15 æˆ– å€Ÿå‡ºä¸­ 20 çš„借阅单 Â· å‘½ä¸­æŠ¥ å·²å€Ÿç”¨(带档案名与已存在借阅单号)"]
    B3 --> B4["submit ä¼  id ä¸Ž processDefinitionKey (query å‚æ•°) Â· å…ˆè§£æžæµç¨‹åˆ—表再建单(无已发布流程则直接报错·不留孤儿草稿) Â· çŠ¶æ€ 15 å®¡æ‰¹ä¸­ Â· å›žå†™ processInstanceId"]
    B4 --> B5["BPM æµç¨‹ archive_borrow_approve ä¸¤çº§: æ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹"]
  end
  B5 -->|"审批通过"| B6["状态 20 å€Ÿå‡ºä¸­ Â· å›žå†™ approveTime Â· æ¡ä»¶æ›´æ–°æ¡£æ¡ˆ SET status=20 WHERE status=10(影响 0 è¡Œåˆ™æŠ¥ æ¡£æ¡ˆå·²è¢«å ç”¨ å¹¶å›žæ»š)"]
  B5 -->|"审批驳回"| B7["状态 35 å·²é©³å›ž Â· å›žå†™é©³å›žåŽŸå› ä¸Žæ—¶é—´ Â· ä¿®æ”¹åŽå¯é‡æ–° submit"]
  B5 -->|"取消流程"| B2
  B6 -->|"归还登记 return Â· æ¡ä»¶æ›´æ–° status=20 â†’ 30"| B8["状态 30 å·²å½’还 Â· å›žå†™ returnTime ä¸Žå½’还确认人 Â· å½’还备注"]
  B8 -->|"条件更新档案 SET status=10 WHERE status=20"| D5
  B6 -->|"仅 0 è‰ç¨¿ ä¸Ž 35 å·²é©³å›ž å¯æ”¹å¯é‡æ Â· ä»… 0 è‰ç¨¿ å¯åˆ "| B2
  B6 -->|"仅 20 å€Ÿå‡ºä¸­ å¯ç™»è®°å½’还"| B8
  OV["逾期不落库·查询时实时计算: status=20 ä¸” return_deadline æ—©äºŽä»Šå¤© Â· overdueDays = ä»Šå¤© å‡ å½’还期限"] -.-> B6
  OVP["GET /archive/borrow/overdue-page ç‹¬ç«‹å…¥å£(非查询参数) Â· åˆ—表「逾期」列渲染红色「逾期 N å¤©ã€ Â· ä¸å»º infra_job ä¸å‘站内信 Â· åˆ—表页另有「只看逾期」切换"] -.-> OV
  ST["档案统计 GET /archive/stat/overview: æ¡£æ¡ˆæ€»æ•° Â· åœ¨åº“æ•° Â· å€Ÿå‡ºä¸­æ•° Â· é€¾æœŸæœªè¿˜æ•° + æŒ‰åˆ†ç±»/按密级/按载体 ä¸‰ç»„分布"] -.-> D5
  SEC["敏感档案管控: å¯†çº§=3 ä¿å¯† ä¸”当前用户无 archive:document:sensitive æ—¶ Â· è¯¦æƒ…接口直接报错 Â· åˆ—表与导出强制过滤 secret_level ä¸ç­‰äºŽ 3"] -.-> D1
  ATT["附件 recordType = archive_document / archive_borrow"] -.-> D3
  ROL["审批中心看单: æµç¨‹å®šä¹‰è¡¨å•类型=自定义表单 Â· åˆ›å»ºè·¯å¾„ /archive/borrow/index Â· æŸ¥çœ‹è·¯å¾„ /archive/borrow/detail/approve/index.vue Â· æŒ‰ businessKey å›žæ˜¾å€Ÿé˜…单"] -.-> B5
  LIS["监听器 ArchiveBorrowStatusListener(流程 key=archive_borrow_approve Â· businessKey=借阅单 ID): é€šè¿‡â†’20 å€Ÿå‡ºä¸­ + approveTime å¹¶æ¡ä»¶æ›´æ–°æ¡£æ¡ˆ â†’ 20 Â· é©³å›žâ†’35 + åŽŸå› ä¸Žæ—¶é—´ Â· å–消→退回 0 Â· ä¸Ž finance/research ç›‘听器订阅不同 key äº’不干扰"] -.-> B5
  BOUND["独立模块 yudao-module-archive(含 -api å­æ¨¡å—)·依赖 system/bpm·不反向依赖 ums/finance/research(避免循环依赖)·三方仅依赖 archive-api æ‰‹åŠ¨è°ƒå½’æ¡£"] --- D1
docs/research_fund_finance_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
# è¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯¹æŽ¥è´¢åŠ¡æŠ¥é”€ - å‰ç«¯è”调方案
> æ¶‰åŠæ¨¡å—:`yudao-module-research`(课题管理)↔ `yudao-module-finance`(财务报销),跨模块通过新建的 `yudao-module-finance-api` æœ¬åœ° Bean è°ƒç”¨ã€‚
> åŽç«¯æŽ¥å£å‰ç¼€ï¼š`/research/fund-record`
> é™„件关联:课题侧 `recordType = research_fund_record`,财务侧 `recordType = finance_reimbursement`,`application = file`(同一批 blob åŒç»‘,互不影响)
## æ¶‰åŠé¡µé¢
| æ–‡ä»¶ | è¯´æ˜Ž |
|------|------|
| `src/api/research/fund/index.ts` | `FundRecord` ç±»åž‹è¡¥ 4 ä¸ªå­—段;`createFundRecord` / `updateFundRecord` æ³¨é‡Šæ›´æ–° |
| `src/views/research/application/data.ts` | `FundStatus` æ–°å¢ž `APPROVING: 15`、`REJECTED: 30`;`useFundColumns` æ–°å¢žã€Œè´¢åŠ¡æŠ¥é”€å•å·ã€ã€Œé©³å›žåŽŸå› ã€ä¸¤åˆ— |
| `src/views/research/application/modules/fund-form.vue` | ç§»é™¤ã€Œæ‹¨ä»˜çŠ¶æ€ã€ä¸‹æ‹‰ï¼ˆçŠ¶æ€æ”¹ä¸ºç³»ç»Ÿé©±åŠ¨ï¼‰ï¼›ã€Œæ‹¨ä»˜å‡­è¯ã€æ”¹å¿…å¡«ï¼›ç¼–è¾‘å·²é©³å›žè®°å½•æ—¶å±•ç¤ºé©³å›žåŽŸå›  |
| `src/views/research/application/modules/fund-panel.vue` | ç¼–辑/删除按钮改为仅「待拨付」可见;「已驳回」行新增「重新发起报销」按钮;按状态补顶部提示 |
| `src/views/research/application/detail/approve/index.vue` | **无需改**(内嵌 `fund-panel`,字典驱动展示自动兼容新状态) |
前端 `finance` æ¨¡å—页面**无需改动**:报销单由后端自动创建并提交,财务人员在既有的「财务报销 â†’ æŠ¥é”€å•」列表里正常审批即可。
**字典已由 SQL è„šæœ¬è¡¥é½**,前端继续用 `getDictOptions(DICT_TYPE.RESEARCH_FUND_STATUS)` / `CellDict` å–值,无需新建枚举:
| å­—典类型 | åç§° | å–值 |
|----------|------|------|
| `research_fund_status` | ç»è´¹æ‹¨ä»˜çŠ¶æ€ | 0 å¾…拨付 / 15 æŠ¥é”€å®¡æ‰¹ä¸­ / 20 å·²æ‹¨ä»˜ / 30 å·²é©³å›ž |
> å­—典项 id æ®µï¼š`8100401` å¾…拨付 / `8100403` æŠ¥é”€å®¡æ‰¹ä¸­ / `8100402` å·²æ‹¨ä»˜ / `8100404` å·²é©³å›žã€‚
**按钮权限标识**(无变化,沿用一期):
`research:fund:create`、`research:fund:update`、`research:fund:delete`、`research:fund:query`
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
```
课题详情 â†’ ç»è´¹æ‹¨ä»˜é¢æ¿
        â”‚
        ç™»è®°æ‹¨æ¬¾ï¼ˆæ‹¨ä»˜æ‰¹æ¬¡ã€æ‹¨ä»˜é‡‘额、拨付日期、收款单位、备注、拨付凭证)
        â”‚  â† å‡­è¯å¿…填:先上传拿 blobId,再把全部 blobId æ”¾è¿›æœ¬æ¬¡è¯·æ±‚体一起提交
        â–¼
   create æŽ¥å£ï¼ˆä¸€æ¡é¾™ï¼Œå‰ç«¯åªéœ€è°ƒä¸€æ¬¡ï¼‰
        â”œâ”€â”€ 1. è¯¾é¢˜ä¾§è½ research_fund_record å°è´¦ï¼ˆçŠ¶æ€å…ˆç½®ã€Œå¾…æ‹¨ä»˜ã€ï¼‰
        â”œâ”€â”€ 2. åŒä¸€æ‰¹ blob ç»‘定到课题台账(research_fund_record)
        â”œâ”€â”€ 3. è‡ªåŠ¨åˆ›å»ºè´¢åŠ¡æŠ¥é”€å• finance_reimbursement(1:1、金额一致)
        â”‚      æ ‡é¢˜ = è¯¾é¢˜ã€”课题名〕第N期/第N次经费拨款
        â”‚      æ˜Žç»†å›ºå®š 1 è¡Œï¼šè´¹ç”¨ç±»åž‹=其他(5)、发票金额=拨款金额、发票日期=拨付日期、费用说明=标题
        â”œâ”€â”€ 4. ç«‹å³æäº¤ BPM å®¡æ‰¹ï¼ˆfinance_reimbursement_approve)
        â””── 5. å°è´¦å›žå†™ reimbursementId / reimbursementNo,状态置「报销审批中(15)」
        â–¼
财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼ˆåœ¨è´¢åŠ¡æ¨¡å—çš„æŠ¥é”€å•å®¡æ‰¹ä¸­å¿ƒå®Œæˆï¼‰
        â”‚
        â”œâ”€â”€ é€šè¿‡  â†’ å°è´¦è‡ªåŠ¨å˜ã€Œå·²æ‹¨ä»˜(20)」
        â”œâ”€â”€ é©³å›ž  â†’ å°è´¦è‡ªåŠ¨å˜ã€Œå·²é©³å›ž(30)」,并回写驳回原因 + é©³å›žæ—¶é—´
        â””── æ’¤å›ž/取消 â†’ å°è´¦é€€å›žã€Œå¾…拨付(0)」,可编辑后重新发起
```
**信息带入(关键)**
| ä»Ž | åˆ° | å¸¦å…¥å­—段 |
|----|----|----------|
| è¯¾é¢˜ç”³æŠ¥ä¹¦ | ç»è´¹æ‹¨ä»˜ç™»è®°å¼¹çª— | è¯¾é¢˜åç§°ã€ç«‹é¡¹ç¼–号、申请经费(用于余额提示)、可登记性(仅「已立项」可登记) |
| ç»è´¹æ‹¨ä»˜ç™»è®° | è‡ªåŠ¨ç”Ÿæˆçš„è´¢åŠ¡æŠ¥é”€å• | æ ‡é¢˜ï¼ˆè¯¾é¢˜å + æ‰¹æ¬¡ï¼‰ã€æŠ¥é”€æ€»é‡‘额 = æ‹¨ä»˜é‡‘额、申请日期 = æ‹¨ä»˜æ—¥æœŸã€å¤‡æ³¨ã€è´¹ç”¨è¯´æ˜Žã€ç”³è¯·äºº = å½“前登录用户 |
| ç»è´¹æ‹¨ä»˜ç™»è®° | è´¢åŠ¡æŠ¥é”€å•å‡­è¯ | åŒä¸€æ‰¹ `blobIds` åŒç»‘到课题台账与财务报销单 |
| è´¢åŠ¡æŠ¥é”€å®¡æ‰¹ç»“æžœ | è¯¾é¢˜ç»è´¹æ‹¨ä»˜å°è´¦ | çŠ¶æ€ï¼ˆ20/30/0)、驳回原因、驳回时间 |
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/research/fund-record/create` | ç™»è®°ç»è´¹æ‹¨ä»˜ï¼ˆåŽç«¯è‡ªåŠ¨åˆ›å»ºå¹¶æäº¤è´¢åŠ¡æŠ¥é”€å®¡æ‰¹ï¼‰ |
| PUT | `/research/fund-record/update` | ä¿®æ”¹å¹¶é‡æ–°å‘起报销(仅「待拨付」「已驳回」可改) |
| DELETE | `/research/fund-record/delete?id=` | åˆ é™¤æ‹¨æ¬¾è®°å½•(仅「待拨付」可删,联动清理财务侧草稿报销单) |
| GET | `/research/fund-record/list?applicationId=` | æŸè¯¾é¢˜ç”³æŠ¥ä¹¦ä¸‹çš„经费拨付记录列表 |
**请求参数(create / update å…±ç”¨åŒä¸€ä¸ª VO):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | update å¿…å¡« | æ‹¨æ¬¾è®°å½•编号 |
| applicationId | Long | æ˜¯ | è¯¾é¢˜ç”³æŠ¥ä¹¦ ID |
| batchName | String | å¦ | æ‹¨ä»˜æ‰¹æ¬¡ï¼Œå¦‚「第一期」;留空时标题按 `第N次` è‡ªåŠ¨ç¼–å· |
| amount | BigDecimal | æ˜¯ | æ‹¨ä»˜é‡‘额(元),不得为负 |
| payDate | LocalDate(`yyyy-MM-dd`) | å¦ | æ‹¨ä»˜æ—¥æœŸï¼›ç•™ç©ºæ—¶åŽç«¯æŒ‰å½“天处理 |
| payee | String | å¦ | æ”¶æ¬¾å•位/账户名 |
| remark | String | å¦ | å¤‡æ³¨ |
| blobIds | Long[] | **是** | æ‹¨ä»˜å‡­è¯é™„ä»¶ blobId åˆ—表;全量替换语义,清空必须传空数组 |
> âš  **`status` å­—段已从请求体中移除**:拨付状态由系统驱动(登记 â†’ æŠ¥é”€å®¡æ‰¹ä¸­ â†’ å·²æ‹¨ä»˜/已驳回),前端不要再传,也不要在表单里放状态下拉。
**响应字段新增(`/list` è¿”回的每条记录):**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| reimbursementId | Long | å…³è”财务报销单 ID |
| reimbursementNo | String | å…³è”财务报销单号(登记成功后回填) |
| rejectReason | String | è´¢åŠ¡æŠ¥é”€é©³å›žåŽŸå› ï¼ˆBPM å›žå†™ï¼‰ |
| rejectTime | LocalDateTime | è´¢åŠ¡æŠ¥é”€é©³å›žæ—¶é—´ï¼ˆBPM å›žå†™ï¼‰ |
**响应示例:**
```json
{
  "code": 0,
  "data": [
    {
      "id": 3,
      "applicationId": 2,
      "batchName": "第一期",
      "amount": 50000.0,
      "payDate": "2026-12-01",
      "payee": "XX大学",
      "status": 15,
      "reimbursementId": 7,
      "reimbursementNo": "BX20260929000007",
      "rejectReason": "",
      "rejectTime": null,
      "remark": "第一期拨款",
      "creatorName": "超级管理员",
      "createTime": "2026-09-29 10:00:00",
      "attachmentList": [],
      "hasAttachment": true
    }
  ]
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| status | åˆ—表「拨付状态」列 | å­—å…¸ `research_fund_status` æ¸²æŸ“,0/15/20/30 å››æ€ |
| reimbursementNo | åˆ—表「财务报销单号」列 | ç™»è®°æˆåŠŸåŽå›žå¡«ï¼›ä¸ºç©ºæ˜¾ç¤º `-`;财务人员在财务模块按此单号查单 |
| rejectReason | åˆ—表「驳回原因」列;编辑弹窗顶部 Alert | ä»…「已驳回」有值;未填时后端回写「财务未填写驳回原因」 |
| rejectTime | ä»…后端存储 | åˆ—表不展示,供排查/追溯 |
| hasAttachment | åˆ—表「拨付凭证」列 | å‡­è¯å·²å¿…填,仅「已上传 / ç¼ºå¤±ã€ä¸¤æ€ |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| å•据关系 | ä¸€æ¡æ‹¨æ¬¾è®°å½• âŸ· ä¸€å¼ è´¢åŠ¡æŠ¥é”€å•ï¼Œ**1:1、金额一致**;一个批次一张单 |
| ç™»è®°å³æäº¤ | è¯¾é¢˜ä¾§ç‚¹ä¿å­˜åŽç«‹å³è‡ªåŠ¨æäº¤ BPM,**不需要**再去财务模块提交一次 |
| å¯ç™»è®°å‰æ | åªæœ‰**已立项**的课题申报书才能登记经费拨付 |
| å‡­è¯å¿…å¡« | æ‹¨ä»˜å‡­è¯å¿…填,会随自动生成的财务报销单一起送交财务审批 |
| é‡‘额上限 | æœ¬æ¬¡é‡‘额 + è¯¥ç”³æŠ¥å•已登记金额 â‰¤ ç”³è¯·é¢„算(`budgetAmount`) |
| ä¿®æ”¹å‰æ | åªæœ‰ã€Œå¾…拨付(0)」「已驳回(30)」可修改并重新发起报销;「报销审批中」需财务端先撤回或等待结果 |
| é©³å›žé‡å‘ | ã€Œå·²é©³å›žã€è®°å½•改金额/凭证/批次后重新发起,原有金额、凭证、批次不丢失,驳回原因与时间被清空 |
| åˆ é™¤å‰æ | åªæœ‰ã€Œå¾…拨付(0)」可删除;若关联的财务报销单已不是「草稿」,删除会被拒绝并提示联系财务确认 |
| å®¡æ‰¹å›žå†™ | æŠ¥é”€é€šè¿‡ â†’ å°è´¦ã€Œå·²æ‹¨ä»˜(20)」;驳回 â†’ ã€Œå·²é©³å›ž(30)」+ åŽŸå› /时间;撤回或取消 â†’ é€€å›žã€Œå¾…拨付(0)」 |
| æ— æµç¨‹å…œåº• | è´¢åŠ¡å®¡æ‰¹åˆ†ç±»ä¸‹æ²¡æœ‰å·²å‘å¸ƒæµç¨‹æ—¶ï¼Œç™»è®°ç›´æŽ¥å¤±è´¥å¹¶æç¤ºåŽ» BPM åˆ›å»ºå‘布流程,**不会留下孤儿草稿报销单**(后端事务回滚) |
## æ³¨æ„äº‹é¡¹
- **必须先部署财务报销审批流程**:`bpm_category` ä¸­ `finance_reimbursement_approve` åˆ†ç±»å·²å­˜åœ¨ï¼Œä½†å…¶ä¸‹å¿…须有一个**已发布**的流程定义(2 èŠ‚ç‚¹ï¼šè´¢åŠ¡å®¡æ ¸ â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰ï¼Œå¦åˆ™ç™»è®°æ‹¨æ¬¾ä¼šæŠ¥ã€Œè¯·å…ˆåœ¨ BPM åˆ›å»ºå¹¶å‘布报销审批流程后重试」。
- **拨付明细由系统生成,不可编辑**:报销单的明细固定 1 è¡Œï¼ˆè´¹ç”¨ç±»åž‹=其他、发票金额=拨款金额、发票日期=拨付日期),前端不要在课题侧做明细编辑。
- **状态只读**:拨付状态由系统与 BPM é©±åŠ¨ï¼Œå‰ç«¯ä»»ä½•è¡¨å•éƒ½ä¸è¦å†æäº¤ `status`。
- **删除是"两清"**:删待拨付记录时会联动删除财务侧的草稿报销单;财务侧已提交/已通过的报销单必须先撤回或处理,否则删除被拒。
- **软关联无外键**:台账与报销单靠 `reimbursement_id` å…³è”。若财务侧**直接删除**了报销单,台账会留下悬空 ID,此时再次修改会提示「关联的财务报销单已不存在,请删除本条拨款记录后重新登记」。
- **附件为全量替换语义**:修改记录时 `blobIds` ä¼ çš„æ˜¯å…¨éƒ¨å‡­è¯ï¼›ä¸ä¼ æˆ–传空数组会清空凭证(且后端会先校验必填并直接拒绝)。
- **列表页余额提示仅供参考**:可用余额 = ç”³è¯·ç»è´¹ âˆ’ å·²ç™»è®°æ‹¨ä»˜åˆè®¡ï¼Œæœ€ç»ˆä»¥æœåŠ¡ç«¯ç´¯è®¡æ ¡éªŒä¸ºå‡†ï¼ˆè¶…å‡ºä¼šè¿”å›žå¸¦ã€Œæœ¬æ¬¡/已登记/合计/上限」四要素的精确报错)。
docs/sql/_archive_bpm_export.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
-- ===== æ¡£æ¡ˆå€Ÿé˜…审批(archive_borrow_approve)BPM æµç¨‹å®šä¹‰ï¼ˆ2 ä¸ªé¡ºåºèŠ‚ç‚¹ï¼šæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼‰ =====
-- ç”Ÿæˆæ¥æºï¼šmom-jssm åº“,经 BPM æ¨¡åž‹è®¾è®¡å™¨éƒ¨ç½²ï¼ˆPOST /bpm/model/create + /bpm/model/deploy)
-- åˆ†ç±» bpm_category(id=8003, code=archive_borrow_approve) è§ archive_module.sql
-- åˆ é™¤é¡ºåºï¼ˆå­˜åœ¨å¤–键):act_re_model â†’ act_ge_bytearray â†’ act_re_procdef â†’ act_re_deployment
DELETE FROM `act_re_model` WHERE `KEY_` = 'archive_borrow_approve';
DELETE FROM `act_ge_bytearray` WHERE `ID_` IN ('89bbc291-bbbe-11f1-a8aa-00155dd68500','8bad8073-bbbe-11f1-a8aa-00155dd68500','8bc8d0a4-bbbe-11f1-a8aa-00155dd68500');
DELETE FROM `act_re_procdef` WHERE `KEY_` = 'archive_borrow_approve';
DELETE FROM `act_re_deployment` WHERE `ID_` = '8bad5962-bbbe-11f1-a8aa-00155dd68500';
DELETE FROM `bpm_process_definition_info` WHERE `category` = 'archive_borrow_approve';
INSERT INTO `act_re_deployment` (`ID_`, `NAME_`, `CATEGORY_`, `KEY_`, `TENANT_ID_`, `DEPLOY_TIME_`, `DERIVED_FROM_`, `DERIVED_FROM_ROOT_`, `PARENT_DEPLOYMENT_ID_`, `ENGINE_VERSION_`) VALUES ('8bad5962-bbbe-11f1-a8aa-00155dd68500', '档案借阅审批', 'archive_borrow_approve', 'archive_borrow_approve', '', '2026-09-29 12:30:48.677', NULL, NULL, '8bad5962-bbbe-11f1-a8aa-00155dd68500', NULL);
INSERT INTO `act_re_procdef` (`ID_`, `REV_`, `CATEGORY_`, `NAME_`, `KEY_`, `VERSION_`, `DEPLOYMENT_ID_`, `RESOURCE_NAME_`, `DGRM_RESOURCE_NAME_`, `DESCRIPTION_`, `HAS_START_FORM_KEY_`, `HAS_GRAPHICAL_NOTATION_`, `SUSPENSION_STATE_`, `TENANT_ID_`, `ENGINE_VERSION_`, `DERIVED_FROM_`, `DERIVED_FROM_ROOT_`, `DERIVED_VERSION_`) VALUES ('archive_borrow_approve:1:8bcc7a25-bbbe-11f1-a8aa-00155dd68500', 2, 'archive_borrow_approve', '档案借阅审批', 'archive_borrow_approve', 1, '8bad5962-bbbe-11f1-a8aa-00155dd68500', 'archive_borrow_approve.bpmn', 'archive_borrow_approve.archive_borrow_approve.png', NULL, 0, 1, 1, '', NULL, NULL, NULL, 0);
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('8bad8073-bbbe-11f1-a8aa-00155dd68500', 1, 'archive_borrow_approve.bpmn', '8bad5962-bbbe-11f1-a8aa-00155dd68500', X'3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D225554462D38223F3E0A3C646566696E6974696F6E7320786D6C6E733D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C2220786D6C6E733A7873693D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612D696E7374616E63652220786D6C6E733A7873643D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612220786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220786D6C6E733A62706D6E64693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F44492220786D6C6E733A6F6D6764633D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44432220786D6C6E733A6F6D6764693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44492220747970654C616E67756167653D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D61222065787072657373696F6E4C616E67756167653D22687474703A2F2F7777772E77332E6F72672F313939392F585061746822207461726765744E616D6573706163653D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C223E0A20203C70726F636573732069643D22617263686976655F626F72726F775F617070726F766522206E616D653D22E6A1A3E6A188E5809FE99885E5AEA1E689B92220697345786563757461626C653D2274727565223E0A202020203C73746172744576656E742069643D2253746172744576656E7422206E616D653D22E5BC80E5A78B223E3C2F73746172744576656E743E0A202020203C757365725461736B2069643D225374617274557365724E6F646522206E616D653D22E58F91E8B5B7E4BABA223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B33365D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68F90E4BAA42220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2266616C7365222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2266616C7365222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2266616C7365222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2266616C7365222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2266616C7365222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B325D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F617263686976655F61646D696E5F72657669657722206E616D653D22E6A1A3E6A188E7AEA1E79086E59198E5AEA1E6A0B8223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F6C65616465725F617070726F766522206E616D653D22E58886E7AEA1E9A286E5AFBCE5AEA1E689B9223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C656E644576656E742069643D22456E644576656E7422206E616D653D22E7BB93E69D9F223E3C2F656E644576656E743E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D312220736F757263655265663D2253746172744576656E7422207461726765745265663D225374617274557365724E6F6465223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D322220736F757263655265663D225374617274557365724E6F646522207461726765745265663D2241637469766974795F617263686976655F61646D696E5F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D332220736F757263655265663D2241637469766974795F617263686976655F61646D696E5F72657669657722207461726765745265663D2241637469766974795F6C65616465725F617070726F7665223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D342220736F757263655265663D2241637469766974795F6C65616465725F617070726F766522207461726765745265663D22456E644576656E74223E3C2F73657175656E6365466C6F773E0A20203C2F70726F636573733E0A20203C62706D6E64693A42504D4E4469616772616D2069643D2242504D4E4469616772616D5F617263686976655F626F72726F775F617070726F7665223E0A202020203C62706D6E64693A42504D4E506C616E652062706D6E456C656D656E743D22617263686976655F626F72726F775F617070726F7665222069643D2242504D4E506C616E655F617263686976655F626F72726F775F617070726F7665223E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2253746172744576656E74222069643D2242504D4E53686170655F53746172744576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D22302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D225374617274557365724E6F6465222069643D2242504D4E53686170655F5374617274557365724E6F6465223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D2238302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F617263686976655F61646D696E5F726576696577222069643D2242504D4E53686170655F41637469766974795F617263686976655F61646D696E5F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223233302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F6C65616465725F617070726F7665222069643D2242504D4E53686170655F41637469766974795F6C65616465725F617070726F7665223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223338302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D22456E644576656E74222069643D2242504D4E53686170655F456E644576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D223533302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D31222069643D2242504D4E456467655F73657175656E6365466C6F772D31223E0A20202020202020203C6F6D6764693A776179706F696E7420783D2233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D2238302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D32222069643D2242504D4E456467655F73657175656E6365466C6F772D32223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223138302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D33222069643D2242504D4E456467655F73657175656E6365466C6F772D33223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223333302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223338302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D34222069643D2242504D4E456467655F73657175656E6365466C6F772D34223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223438302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223533302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A202020203C2F62706D6E64693A42504D4E506C616E653E0A20203C2F62706D6E64693A42504D4E4469616772616D3E0A3C2F646566696E6974696F6E733E0A', 0);
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('8bc8d0a4-bbbe-11f1-a8aa-00155dd68500', 1, 'archive_borrow_approve.archive_borrow_approve.png', '8bad5962-bbbe-11f1-a8aa-00155dd68500', X'89504E470D0A1A0A0000000D494844520000023A000000460806000000C5373E5300000D7E49444154785EED9DEF4F1CC719C7378E54E545FE832AAF92FE1FAD94282F7811B5D271E743D83EEE90C10164C9BF24CB56A1F84D5184915AB96ADF44B69A0A15C936C4C6766C30A60EB8D88AD4065C8803C1716D7206FC0B146C30B79D87B2D2646E81BDBD9BBD9987EF47FA08B3B33BB3BEF9F230B777B7380E280AD7757F363535F5F7E1E1E1E5BEBE3EF7EAD5AB3062C5E39E1B1C1C7CD4DFDFFF913A3FB6825C955FE40AEA9063AE007344D1E812A175B3D9ACBBB4B4E4BE7AF50A462C3DEEF4F80F0C0C3C1785A4429D231B41AECA2F720575C831578039F4CC8842AB861946EFCCCCCC9C281C23EA1CD90872658EC815D421A75C01E6D0E55F3C3332439A0751385EAA736423C89539225750879C72059843AFB9AA0186E593E6439D231B41AECC12B9823AE4922BC09CA08563F1D98C3B75FB5377ECDAEFD6A47FD336753F589C5C0A07726596C81572A5432EB902CC095238169E3E7447AF1C77FF75F1D04FA46DD4A6EE0FC3CBA570205766895C21573AE4922BC09C2085E3C1584F5ED1F0FCEFD8E779FBC3F072291CC895592257C8950EB9E40A302748E1F8CFF5DFE7150C4F6A53F787E1E55238902BB344AE902B1D72C915604E90C2317AB539AF6078529BBA3F0C2F97C2815C992572855CE9904BAE00735038CC924BE140AECC12B942AE74C825578039410A077D6A412D189ED4A6EE0FC3CBA570205766895C21573AE4922BC09C2085E3DE977FCC2B189ED4A6EE0FC3CBA570205766895C21573AE4922BC09C208563FEE1BFDDD12F7E9B5734681BB5A9FBC3F072291CC895592257C8950EB9E40A302748E120BFBB7326AF70D036753F589C5C0A07726596C81572A5432EB90A4A2C167B2F1E8F1F12F624128911E103A1BBFE7584B6533BEDA71EAB1DD775770C0F0FFFE6CC99335FB4B4B4CC1D397264A9BEBE3E4727585757B772F0E0C11762DBB7ADADAD9FA452A977D4E33913A870BC7CE9DE1BFA535EE1A06DD496B73F0C2D97C2815C992572855CE9904BAE36432C5ADE146B85F4CE9D3BC7D61735815CDF3F4DC7AB7D9614B1C079EBF2E5CB7F397EFCF8722693714F9D3AE5DEBA75CB9D9C9C74E7E7E75D82BED2F7B49DDAD3E9F46A6363E3546D6DED876A7F1CD9AA70D09D44BFF9F20F7945C393DA70B7D1D2C9A570205766895C21573AE492AB8D100B950AB160B9AB2E620A717DC153A1F65D12C4C2E5E3D6D6D6A5FDFBF7BBC3C3C3EEEAEAEADAC2662B683FDABFA9A96965DFBE7D5FC5E3F177D5BE39B161E110CF7C66EEF5BB5F5F3E9A572C54691FDA17CF968A974BE140AECC12B942AE74C825572ACDCDCD3BC402A54D5DB4ECD9B3C7EDE8E8700707077D2F98D0766AA7FDD463A93FEA571D2B1462CC37AE5CB9F2D9DEBD7BDDDEDE5EF7F5EBD73F5DC904848EA3E3C5092F8A13FC401D870B7E8563AB67451B89674BC5CBA570205766895CE58B5C152F975CC9C462B1B7E3F1F8457991924EA7DDEEEE6E777979595D2AF842FBD1FE749CDC0FF54BFDAB631684E8FF8DB367CFFEB3BEBEDE1D1F1F57C70E05F593C9647E4C269369753C0EF8158E20CF8A36928E55FB83C1E55238902BB344AEFC45AE8A934BAE3CE88A8B58905C901727EDEDEDEEC2C282BA3408041D47C72B57772E14756587AEE4D4D5D5B9D96C561DAF28A8BF542AB5505959F9BE3AA6EDF8150EB51814AADA1F0C2E97C2815C992572B5B16A7F30B85C72E521BF5C25FEED767575B9B95C4E5D1214041D4FFD507F52DF6DEAD881181A1AFA981639A5BA92A322FACDEDDAB5EB69593E36A611BFC201CB2797C2815C9925720575C8255784588054C8575E6871524AA83FE5CA4E616F50167DBC75E2C489257A4F8D4EBABBBB9F2693C9CBEAF83683C261965C0A07726596C815D421975CD147C0E54F57D1CB4DC55EC951A1FEE497B1E8D358057DF4FCD2A54B7FA64F57857DE37150A8FF4C26335B5959F92BF51C6C0585C32CB9140EE4CA2C912BA8432EB9120B8FB4B700A13710877D4FCE5650BFCA1B9483BDF7571CBBE3D8B163CBF491F028B879F3E6337172FF50CFC3565038CC924BE140AECC12B9823AE4922BF966803D3D3DEAAFFD9242FDCB5775D473F1656868E8D7B5B5B581EF93532C344E5555D5B3582CF673F55C4C423C88D785BF54B7ABA07098A5E98503B9B253E40AEA9043AE92C9E42FBC8507DDFF86FE5F3AA1FEE5FBECD0F8EA39E571FAF4E9AB7447E328696969198DC7E3F5EAB99884F7206E35D1281C666941E140AE2C14B9823AE4902BB1FDA0B7DFC99327D55FF75AA071A4733BA89E531EADADADB351BD6CE571EDDA35BACC75563D1793901EC44D271A85C32C2D2A1CC89545225750871C7225BEEFF6DAE9CEC65140E348E7D42D9F8F2F870F1F5E9A9A9A52FBD1CAC4C4C4B438B9DBEAB99884CF04FB4E340A87595A5838902B0B44AEA00E39E44A7CBDED6D8F6A2D41E348E7B2F55AA2AEAE2EF7E4C913B51FADCCCFCF2F88937BA89E8B49F84CACEADA44A37098A5C58503B93258E40AEA9049AEE6BCEFA35A4BD038D2396CBD96A8AEAE76575656D47EB422C67BE1F38059290A8759AAF363ABC89559AAF363ABC89559AAF363BB51AD25681C69DC97EABA268FBD7BF7AE44B50AF3989D9DFD361164155646D40994BC9EC0A56063B5F819127265B0C815D421875C89AF0FBDED51AD250ABEA273E0C0811751BDAEE671F7EEDD3B8920AFAB9591CD2656C6F4C2214ED1D752B59BA68585A32CB9727CE694E4D25E6A91AB703A3E7344DAD2AE5B0EB94AD8F01E9DA3478F7E13F5A7AECE9D3BD793B0E7535779132B1375E12854C7E78756DE566CBB695A5438CA9A2BC7670EE56DB6B7975AE42A9C8ECF9CC8DB4C6FD72D875C256CF8D4554B4BCB2751DF47A7A1A1A1D782FBE86C38B13251178E42757C7E68E56DC5B69BA60585C3885C393E73286FB3BDBDD42257E1747CE644DE667ABB6E39E42A61C37D7452A9D43B994C6635C23B23FF204E6CD6F43B230725EAC251A88ECF0FADBCADD876D334BD70044577AE1C9F3994B7D9DE5E6A91AB703A3E73226F33BD5DB71C72558E3B23EFDEBD3BE78D19E8CEC8446363E37751BD7CD5D9D9F9D704FED655643A3E3FB4F2B662DB4D9343E12074E7CAF19943799BEDEDA516B90AA7E33327F236D3DB75CB2557C6FFAD2BA2B6B6F6C3A6A6A69508FE7AF9037172F7F0D7CBA3D3F1F9A195B715DB6E9A5C0A87EE5C393E73286FB3BDBDD42257E1747CE644DE667ABB6EB9E42A11E15F2FAFA9A959F5C6A271D573D994868686AF7A7B7B736AC72524D7D6D6F6A938B15E756C9B89BA7014AAE3F3432B6F2BB6DD34B9140EDDB9727CE650DE667B7BA945AEC2E9F8CC89BCCDF476DD72C9552C167B53BEAAD3DEDEEEE672A55D4E507FD4AF7C3587C655CF6553C401EFA552A985F1F171B5FF9270E3C68DD3E2E466681C756C9B89BA7014AA38455F4BD56E9A5C0A87EE5C393E734A72692FB5C855381D9F39226D69D72D975C11E2F77B8574A5C5EDEAEA5297014541FDC9FDD378EA390442AC903EA8A9A9F9319BCDAA6314C5F4F4F4E7E2A41E555656BEAF8E693B51170EB8B95C0A07726596C815D421975C798835449B74C5656D7152EC951D3A9EFAA1FEA4BEDBD4B10B22994CA653A9D48B525DD959BF9243774EAC51C7E2000A875972291CC89559225750875C72E5D1DCDCBC43FCAEBF205F79A1979BC2BE67878E935FAE5AF7028DA38E5D3074E5A5BABAFA697777F7D3B06F50A6371EAFBF27E7078E57723C5038CC924BE140AECC12B9823AE4922B99582CF6763C1EBF282F4EE80DCA623DE12E2F2FAB4B055F683FDA5F79E3B14BFD52FFEA98A1111DBE9B4C26AF643299D99B376F3E0B7A9F1DB15FB6B3B3F3337152F78497A81FB56F4EA070982597C2815C9925720575C825572A74C5457E19CB93EEB3D3D1D1B17667E3C9C949777E7E7E6DDD405FE97BDA4EEDF27D723CA9BF925CC9F1833E0A2E0619ACAAAA7A2E06F9BAAFAF6F746262E2FBB9B9B945717E0B8F1F3F9E1A1B1BBB73FEFCF9EEC6C6C64B89FFFFC9F6414E1F21DF0C140EB3E452386CCAD5E2E2A23B3A3A5AB0749CDA97A92257D18B5CD98F580B54C89FC60AE3FAF1E1DE785C2874276331585D3C1EA7BF533522A47BE2D089D0D791F5ED755CEE781C149B0AC776904BE1B02957F4CBC5F1F9E4CA56D2716A5FA68A5C452F72C503FA08B8581BA40B5DF0ACEF9F2EF823E4A0F4D85438B6835C0A874DB9C2336F7B40AECC924BAE8242B79789C7E38736BB6042EDDC6E43633D36158EED2097C2815C9925720575C825578039281C66C9A570205766895C411D72C915600E0A875972291CC89559225750875C72059883C261965C0A874DB9C27B29EC01B9324B2EB902CCB1A9706C07B9140E9B7245BF5C1C9F4FBF6C251DA7F665AAC855F422570018824D85633BC8A570D8942B3CF3B607E4CA2CB9E40A30C7A6C2B11DE45238902BB344AEA00EB9E40A300785C32CB9140EE4CA2C912BA8432EB902CC41E1304B2E8503B9324BE40AEA904BAE00735038CC924BE1B02957782F853D205766C9255780397D7D7DB9A5A5A5BC00C3E815F3F048148E97EA1CD9884DB9A25F2E8ECFA75FB6928E53FB3251E4AA3C22570018C2E0E0E0A36C369B176218BDF7EFDFEF148563449D231BB12957DC9F792357E511B902C010FAFBFB3F1A1818783E33333367CB33256E8AC77D667A7AFA6FA2687C2FAC50E7C84690ABF28B5C411D72CC15D806505869652E7C45AFB9C2C8A5C79D1E7F564583FE3FEBFF2FE4AA3C225750872C7305000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005010FF03FB7F2E0A575276F10000000049454E44AE426082', 1);
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('89bbc291-bbbe-11f1-a8aa-00155dd68500', 1, 'source', NULL, X'3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D225554462D38223F3E0A3C646566696E6974696F6E7320786D6C6E733D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C2220786D6C6E733A7873693D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612D696E7374616E63652220786D6C6E733A7873643D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612220786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220786D6C6E733A62706D6E64693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F44492220786D6C6E733A6F6D6764633D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44432220786D6C6E733A6F6D6764693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44492220747970654C616E67756167653D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D61222065787072657373696F6E4C616E67756167653D22687474703A2F2F7777772E77332E6F72672F313939392F585061746822207461726765744E616D6573706163653D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C223E0A20203C70726F636573732069643D22617263686976655F626F72726F775F617070726F766522206E616D653D22E6A1A3E6A188E5809FE99885E5AEA1E689B92220697345786563757461626C653D2274727565223E0A202020203C73746172744576656E742069643D2253746172744576656E7422206E616D653D22E5BC80E5A78B223E3C2F73746172744576656E743E0A202020203C757365725461736B2069643D225374617274557365724E6F646522206E616D653D22E58F91E8B5B7E4BABA223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B33365D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68F90E4BAA42220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2266616C7365222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2266616C7365222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2266616C7365222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2266616C7365222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2266616C7365222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B325D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F617263686976655F61646D696E5F72657669657722206E616D653D22E6A1A3E6A188E7AEA1E79086E59198E5AEA1E6A0B8223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F6C65616465725F617070726F766522206E616D653D22E58886E7AEA1E9A286E5AFBCE5AEA1E689B9223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C656E644576656E742069643D22456E644576656E7422206E616D653D22E7BB93E69D9F223E3C2F656E644576656E743E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D312220736F757263655265663D2253746172744576656E7422207461726765745265663D225374617274557365724E6F6465223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D322220736F757263655265663D225374617274557365724E6F646522207461726765745265663D2241637469766974795F617263686976655F61646D696E5F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D332220736F757263655265663D2241637469766974795F617263686976655F61646D696E5F72657669657722207461726765745265663D2241637469766974795F6C65616465725F617070726F7665223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D342220736F757263655265663D2241637469766974795F6C65616465725F617070726F766522207461726765745265663D22456E644576656E74223E3C2F73657175656E6365466C6F773E0A20203C2F70726F636573733E0A20203C62706D6E64693A42504D4E4469616772616D2069643D2242504D4E4469616772616D5F617263686976655F626F72726F775F617070726F7665223E0A202020203C62706D6E64693A42504D4E506C616E652062706D6E456C656D656E743D22617263686976655F626F72726F775F617070726F7665222069643D2242504D4E506C616E655F617263686976655F626F72726F775F617070726F7665223E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2253746172744576656E74222069643D2242504D4E53686170655F53746172744576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D22302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D225374617274557365724E6F6465222069643D2242504D4E53686170655F5374617274557365724E6F6465223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D2238302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F617263686976655F61646D696E5F726576696577222069643D2242504D4E53686170655F41637469766974795F617263686976655F61646D696E5F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223233302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F6C65616465725F617070726F7665222069643D2242504D4E53686170655F41637469766974795F6C65616465725F617070726F7665223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223338302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D22456E644576656E74222069643D2242504D4E53686170655F456E644576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D223533302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D31222069643D2242504D4E456467655F73657175656E6365466C6F772D31223E0A20202020202020203C6F6D6764693A776179706F696E7420783D2233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D2238302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D32222069643D2242504D4E456467655F73657175656E6365466C6F772D32223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223138302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D33222069643D2242504D4E456467655F73657175656E6365466C6F772D33223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223333302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223338302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D34222069643D2242504D4E456467655F73657175656E6365466C6F772D34223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223438302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223533302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A202020203C2F62706D6E64693A42504D4E506C616E653E0A20203C2F62706D6E64693A42504D4E4469616772616D3E0A3C2F646566696E6974696F6E733E0A', NULL);
INSERT INTO `act_re_model` (`ID_`, `REV_`, `NAME_`, `KEY_`, `CATEGORY_`, `CREATE_TIME_`, `LAST_UPDATE_TIME_`, `VERSION_`, `META_INFO_`, `DEPLOYMENT_ID_`, `EDITOR_SOURCE_VALUE_ID_`, `EDITOR_SOURCE_EXTRA_VALUE_ID_`, `TENANT_ID_`) VALUES ('89a8afc0-bbbe-11f1-a8aa-00155dd68500', 3, '档案借阅审批', 'archive_borrow_approve', 'archive_borrow_approve', '2026-09-29 12:30:45.291', '2026-09-29 12:30:50.065', 1, '{"allowCancelRunningProcess":null,"allowWithdrawTask":null,"autoApprovalType":null,"description":"档案借阅两级审批(档案管理员审核→分管领导审批)","formCustomCreatePath":"/archive/borrow/index","formCustomViewPath":"/archive/borrow/detail/approve/index.vue","formId":null,"formType":20,"icon":null,"managerUserIds":[1],"printTemplateSetting":null,"processAfterTriggerSetting":null,"processBeforeTriggerSetting":null,"processIdRule":null,"sort":1790656245282,"startDeptIds":null,"startUserIds":null,"summarySetting":null,"taskAfterTriggerSetting":null,"taskBeforeTriggerSetting":null,"titleSetting":null,"type":10,"visible":true}', '8bad5962-bbbe-11f1-a8aa-00155dd68500', '89bbc291-bbbe-11f1-a8aa-00155dd68500', NULL, '');
INSERT INTO `bpm_process_definition_info` (`id`, `process_definition_id`, `model_id`, `model_type`, `category`, `icon`, `description`, `form_type`, `form_id`, `form_conf`, `form_fields`, `form_custom_create_path`, `form_custom_view_path`, `simple_model`, `visible`, `sort`, `start_user_ids`, `start_dept_ids`, `manager_user_ids`, `allow_cancel_running_process`, `allow_withdraw_task`, `process_id_rule`, `auto_approval_type`, `title_setting`, `summary_setting`, `process_before_trigger_setting`, `process_after_trigger_setting`, `task_before_trigger_setting`, `task_after_trigger_setting`, `print_template_setting`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (43, 'archive_borrow_approve:1:8bcc7a25-bbbe-11f1-a8aa-00155dd68500', '89a8afc0-bbbe-11f1-a8aa-00155dd68500', 10, 'archive_borrow_approve', NULL, '档案借阅两级审批(档案管理员审核→分管领导审批)', 20, NULL, NULL, NULL, '/archive/borrow/index', '/archive/borrow/detail/approve/index.vue', NULL, 1, 1790656245282, '', NULL, '1', 1, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '1', '2026-09-29 12:30:49', '1', '2026-09-29 12:30:49', 0);
docs/sql/_check_merge.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
// æ ¡éªŒåˆå¹¶åŽçš„ SQL æ–‡ä»¶ï¼šè¯­å¥åˆ‡åˆ†ã€å¼•号闭合、括号平衡、首关键字白名单
const fs = require('node:fs');
// å…ˆå‰”除整行注释(本文件中注释均为整行,且注释内会出现示例 SQL çš„分号)
const raw = fs.readFileSync(__dirname + '/config_export_all_20260928.sql', 'utf8');
const s = raw
  .split(/\r?\n/)
  .filter((l) => !l.trimStart().startsWith('--'))
  .join('\n');
let inS = false;
let start = 0;
let depth = 0;
let maxDepth = 0;
let statements = 0;
const keywords = new Set();
const ESC = '\\';
for (let i = 0; i < s.length; i++) {
  const c = s[i];
  if (!inS) {
    if (c === "'") {
      inS = true;
    } else if (c === '(') {
      depth++;
      if (depth > maxDepth) maxDepth = depth;
    } else if (c === ')') {
      depth--;
      if (depth < 0) throw new Error(`第 ${i} å­—符处括号提前闭合`);
    } else if (c === ';') {
      const st = s.slice(start, i).trim();
      if (st) {
        const body = st
          .split(/\r?\n/)
          .filter((l) => l.trim() && !l.trim().startsWith('--'))
          .join('\n');
        const m = body.match(/^(\w+)/);
        if (!m) throw new Error(`语句无法识别首关键字:${st.slice(0, 80)}`);
        keywords.add(m[1].toUpperCase());
        statements++;
      }
      start = i + 1;
    }
  } else if (c === "'") {
    if (s[i - 1] === ESC && s[i - 2] !== ESC) {
      // åæ–œæ è½¬ä¹‰çš„引号,仍在字符串内
    } else if (s[i + 1] === "'") {
      i++; // '' è½¬ä¹‰
    } else {
      inS = false;
    }
  }
}
console.log('语句数     :', statements);
console.log('括号结尾深度:', depth, '(应为 0)');
console.log('最大嵌套深度:', maxDepth);
console.log('未闭合字符串:', inS, '(应为 false)');
console.log('首关键字集合:', [...keywords].sort().join(' '));
docs/sql/_finance_bpm_export.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
-- ===== æŠ¥é”€å®¡æ‰¹ï¼ˆfinance_reimbursement_approve)BPM æµç¨‹å®šä¹‰ï¼ˆ2 ä¸ªé¡ºåºèŠ‚ç‚¹ï¼šè´¢åŠ¡å®¡æ ¸ â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰ =====
-- ç”Ÿæˆæ¥æºï¼šmom-jssm åº“,经 BPM æ¨¡åž‹è®¾è®¡å™¨éƒ¨ç½²ï¼ˆPOST /bpm/model/create + /bpm/model/deploy),部署时间 2026-09-29 11:28:34
-- åˆ†ç±» bpm_category(id=216, code=finance_reimbursement_approve) å·²å­˜åœ¨ï¼›è¡¨å•类型=自定义表单(form_type=20)
-- åˆ é™¤é¡ºåºï¼ˆå­˜åœ¨å¤–键):act_re_model â†’ act_ge_bytearray â†’ act_re_procdef â†’ act_re_deployment
DELETE FROM `act_re_model` WHERE ID_ = 'd934d9d5-bbb5-11f1-a8aa-00155dd68500';
INSERT INTO `act_re_model` (`ID_`, `REV_`, `NAME_`, `KEY_`, `CATEGORY_`, `CREATE_TIME_`, `LAST_UPDATE_TIME_`, `VERSION_`, `META_INFO_`, `DEPLOYMENT_ID_`, `EDITOR_SOURCE_VALUE_ID_`, `EDITOR_SOURCE_EXTRA_VALUE_ID_`, `TENANT_ID_`) VALUES ('d934d9d5-bbb5-11f1-a8aa-00155dd68500', 3, '报销审批', 'finance_reimbursement_approve', 'finance_reimbursement_approve', '2026-09-29 11:28:33.279', '2026-09-29 11:28:35.781', 1, '{"allowCancelRunningProcess":null,"allowWithdrawTask":null,"autoApprovalType":null,"description":"报销单两级审批(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰","formCustomCreatePath":"/finance/reimbursement/index","formCustomViewPath":"/finance/reimbursement/detail/approve/index.vue","formId":null,"formType":20,"icon":null,"managerUserIds":[1],"printTemplateSetting":null,"processAfterTriggerSetting":null,"processBeforeTriggerSetting":null,"processIdRule":null,"sort":1790652513269,"startDeptIds":null,"startUserIds":null,"summarySetting":null,"taskAfterTriggerSetting":null,"taskBeforeTriggerSetting":null,"titleSetting":null,"type":10,"visible":true}', 'd9acc8f7-bbb5-11f1-a8aa-00155dd68500', 'd93ec4e6-bbb5-11f1-a8aa-00155dd68500', NULL, '');
-- [act_re_model] 1 è¡Œ
DELETE FROM `act_ge_bytearray` WHERE ID_ IN ('d9acc8f8-bbb5-11f1-a8aa-00155dd68500','da62d289-bbb5-11f1-a8aa-00155dd68500','d93ec4e6-bbb5-11f1-a8aa-00155dd68500');
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('d93ec4e6-bbb5-11f1-a8aa-00155dd68500', 1, 'source', NULL, 0x3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D225554462D38223F3E0A3C646566696E6974696F6E7320786D6C6E733D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C2220786D6C6E733A7873693D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612D696E7374616E63652220786D6C6E733A7873643D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612220786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220786D6C6E733A62706D6E64693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F44492220786D6C6E733A6F6D6764633D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44432220786D6C6E733A6F6D6764693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44492220747970654C616E67756167653D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D61222065787072657373696F6E4C616E67756167653D22687474703A2F2F7777772E77332E6F72672F313939392F585061746822207461726765744E616D6573706163653D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C223E0A20203C70726F636573732069643D2266696E616E63655F7265696D62757273656D656E745F617070726F766522206E616D653D22E68AA5E99480E5AEA1E689B92220697345786563757461626C653D2274727565223E0A202020203C73746172744576656E742069643D2253746172744576656E7422206E616D653D22E5BC80E5A78B223E3C2F73746172744576656E743E0A202020203C757365725461736B2069643D225374617274557365724E6F646522206E616D653D22E58F91E8B5B7E4BABA223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B33365D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68F90E4BAA42220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2266616C7365222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2266616C7365222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2266616C7365222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2266616C7365222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2266616C7365222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B325D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F66696E616E63655F72657669657722206E616D653D22E8B4A2E58AA1E5AEA1E6A0B8223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F66696E616E63655F617070726F766522206E616D653D22E8B4A2E58AA1E4B8BBE7AEA1E5AEA1E689B9223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C656E644576656E742069643D22456E644576656E7422206E616D653D22E7BB93E69D9F223E3C2F656E644576656E743E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D312220736F757263655265663D2253746172744576656E7422207461726765745265663D225374617274557365724E6F6465223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D322220736F757263655265663D225374617274557365724E6F646522207461726765745265663D2241637469766974795F66696E616E63655F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D332220736F757263655265663D2241637469766974795F66696E616E63655F72657669657722207461726765745265663D2241637469766974795F66696E616E63655F617070726F7665223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D342220736F757263655265663D2241637469766974795F66696E616E63655F617070726F766522207461726765745265663D22456E644576656E74223E3C2F73657175656E6365466C6F773E0A20203C2F70726F636573733E0A20203C62706D6E64693A42504D4E4469616772616D2069643D2242504D4E4469616772616D5F66696E616E63655F7265696D62757273656D656E745F617070726F7665223E0A202020203C62706D6E64693A42504D4E506C616E652062706D6E456C656D656E743D2266696E616E63655F7265696D62757273656D656E745F617070726F7665222069643D2242504D4E506C616E655F66696E616E63655F7265696D62757273656D656E745F617070726F7665223E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2253746172744576656E74222069643D2242504D4E53686170655F53746172744576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D22302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D225374617274557365724E6F6465222069643D2242504D4E53686170655F5374617274557365724E6F6465223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D2238302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F66696E616E63655F726576696577222069643D2242504D4E53686170655F41637469766974795F66696E616E63655F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223233302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F66696E616E63655F617070726F7665222069643D2242504D4E53686170655F41637469766974795F66696E616E63655F617070726F7665223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223338302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D22456E644576656E74222069643D2242504D4E53686170655F456E644576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D223533302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D31222069643D2242504D4E456467655F73657175656E6365466C6F772D31223E0A20202020202020203C6F6D6764693A776179706F696E7420783D2233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D2238302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D32222069643D2242504D4E456467655F73657175656E6365466C6F772D32223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223138302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D33222069643D2242504D4E456467655F73657175656E6365466C6F772D33223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223333302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223338302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D34222069643D2242504D4E456467655F73657175656E6365466C6F772D34223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223438302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223533302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A202020203C2F62706D6E64693A42504D4E506C616E653E0A20203C2F62706D6E64693A42504D4E4469616772616D3E0A3C2F646566696E6974696F6E733E0A, NULL);
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('d9acc8f8-bbb5-11f1-a8aa-00155dd68500', 1, 'finance_reimbursement_approve.bpmn', 'd9acc8f7-bbb5-11f1-a8aa-00155dd68500', 0x3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D225554462D38223F3E0A3C646566696E6974696F6E7320786D6C6E733D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C2220786D6C6E733A7873693D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612D696E7374616E63652220786D6C6E733A7873643D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612220786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220786D6C6E733A62706D6E64693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F44492220786D6C6E733A6F6D6764633D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44432220786D6C6E733A6F6D6764693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44492220747970654C616E67756167653D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D61222065787072657373696F6E4C616E67756167653D22687474703A2F2F7777772E77332E6F72672F313939392F585061746822207461726765744E616D6573706163653D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C223E0A20203C70726F636573732069643D2266696E616E63655F7265696D62757273656D656E745F617070726F766522206E616D653D22E68AA5E99480E5AEA1E689B92220697345786563757461626C653D2274727565223E0A202020203C73746172744576656E742069643D2253746172744576656E7422206E616D653D22E5BC80E5A78B223E3C2F73746172744576656E743E0A202020203C757365725461736B2069643D225374617274557365724E6F646522206E616D653D22E58F91E8B5B7E4BABA223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B33365D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68F90E4BAA42220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2266616C7365222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2266616C7365222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2266616C7365222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2266616C7365222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2266616C7365222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B325D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F66696E616E63655F72657669657722206E616D653D22E8B4A2E58AA1E5AEA1E6A0B8223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F66696E616E63655F617070726F766522206E616D653D22E8B4A2E58AA1E4B8BBE7AEA1E5AEA1E689B9223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C656E644576656E742069643D22456E644576656E7422206E616D653D22E7BB93E69D9F223E3C2F656E644576656E743E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D312220736F757263655265663D2253746172744576656E7422207461726765745265663D225374617274557365724E6F6465223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D322220736F757263655265663D225374617274557365724E6F646522207461726765745265663D2241637469766974795F66696E616E63655F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D332220736F757263655265663D2241637469766974795F66696E616E63655F72657669657722207461726765745265663D2241637469766974795F66696E616E63655F617070726F7665223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D342220736F757263655265663D2241637469766974795F66696E616E63655F617070726F766522207461726765745265663D22456E644576656E74223E3C2F73657175656E6365466C6F773E0A20203C2F70726F636573733E0A20203C62706D6E64693A42504D4E4469616772616D2069643D2242504D4E4469616772616D5F66696E616E63655F7265696D62757273656D656E745F617070726F7665223E0A202020203C62706D6E64693A42504D4E506C616E652062706D6E456C656D656E743D2266696E616E63655F7265696D62757273656D656E745F617070726F7665222069643D2242504D4E506C616E655F66696E616E63655F7265696D62757273656D656E745F617070726F7665223E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2253746172744576656E74222069643D2242504D4E53686170655F53746172744576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D22302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D225374617274557365724E6F6465222069643D2242504D4E53686170655F5374617274557365724E6F6465223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D2238302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F66696E616E63655F726576696577222069643D2242504D4E53686170655F41637469766974795F66696E616E63655F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223233302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F66696E616E63655F617070726F7665222069643D2242504D4E53686170655F41637469766974795F66696E616E63655F617070726F7665223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223338302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D22456E644576656E74222069643D2242504D4E53686170655F456E644576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D223533302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D31222069643D2242504D4E456467655F73657175656E6365466C6F772D31223E0A20202020202020203C6F6D6764693A776179706F696E7420783D2233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D2238302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D32222069643D2242504D4E456467655F73657175656E6365466C6F772D32223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223138302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D33222069643D2242504D4E456467655F73657175656E6365466C6F772D33223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223333302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223338302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D34222069643D2242504D4E456467655F73657175656E6365466C6F772D34223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223438302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223533302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A202020203C2F62706D6E64693A42504D4E506C616E653E0A20203C2F62706D6E64693A42504D4E4469616772616D3E0A3C2F646566696E6974696F6E733E0A, 0);
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('da62d289-bbb5-11f1-a8aa-00155dd68500', 1, 'finance_reimbursement_approve.finance_reimbursement_approve.png', 'd9acc8f7-bbb5-11f1-a8aa-00155dd68500', 0x89504E470D0A1A0A0000000D494844520000023A000000460806000000C5373E5300000D7D49444154785EED9DDD6F14D719872744AA7291FFA0CA55D2FFA39512E5C217512BAD7759C4C77AD7C2261821F1252150ED9A9B5A1120B5A26A6F22505359B504988081800DB8C4A68022B5B12984E098507016DB7C19C560C34ECF4B3DD2C9D9C19E9DDD33FB9ED7BF477A643C67E69C61CFCFAFCFCEEE8E3D0F5485EFFB3F1B1B1BFBFBF0F0F05C7F7FBF7FE6CC1998B0EA712F0D0E0EDE1B1818F8C89C1F5741AEEA2F72056D2831574038AA68F4A8D0FAC562D19F9D9DF59F3F7F0E13961E777AFCCF9F3FFF58159206738E5C04B9AABFC815B4A1C45C01E1D033230AAD196698BC13131353AA705C36E7C845902B3E2257D0869272058443977FF1CC8887340FAA703C33E7C845902B3E2257D0869272058443AFB99A0186F593E6C39C231741AE78895C411B4AC915104ED4C2F1F4D1843F76E5537FF4ECEF5E49FFA66DE67EB03AA5140EE48A97C81572654329B902C2895238661EDEF5474EEFF6FF7562DB4FA46DD466EE0FE32BA5702057BC44AE902B1B4AC915104E94C27167F45859D108FCEFE8E765FBC3F84A291CC8152F912BE4CA865272058413A570FCE7DCEFCB0A4620B599FBC3F84A291CC8152F912BE4CA865272058413A5708C9C692F2B1881D466EE0FE32BA5702057BC44AE902B1B4AC915100E0A072FA5140EE48A97C81572654329B902C2895238E8530B66C108A436737F185F298503B9E22572855CD9504AAE8070A2148E9B5FFEB1AC6004529BB93F8CAF94C2815CF112B942AE6C2825574038510AC7F4DD7FFB235FFCB6AC68D0366A33F787F1955238902B5E2257C8950DA5E40A08274AE120BFBB7AA8AC70D036733F589D520A0772C54BE40AB9B2A1945C4525954ABD974EA7B7298F653299CBCA3B4A7FE1EB65DA4EEDB49F79AC757CDF5F313C3CFC9B43870E7DD1D1D131B563C78ED9D6D6D6129D604B4BCBFCD6AD5B9FA86DDF7676767E92CBE5DE318F974CA4C2F1EC997F73E84F658583B6515BD9FE30B6520A0772C54BE40AB9B2A1945C2D865AB4BCA9D60AF9952B578E2E2C6A22B9B07F9E8E37FBAC296A81F3D6A953A7FEB27BF7EEB942A1E01F3870C0BF74E9927FEBD62D7F7A7ADA27E82B7D4FDBA93D9FCFBF6C6B6B1B6B6E6EFED0EC4F224B150EBA93E8375FFEA1AC6804521BEE365A3BA5140EE48A97C81572654329B97A1D6AA1D2A0162CD7CC454C252E2C781ACCBE6B825AB87CDCD9D939BB79F3667F7878D87FF9F2E5AB85CD52D07EB4FFA64D9BE6376CD8F0553A9D7ED7EC5B12AF2D1CEA99CFC4CD01FFEB533BCB8A8529ED43FBE2D952F54A291CC8152F912BE4CA86527265D2DEDEBE422D50BACC45CBBA75EBFCFDFBF7FB838383A1174C683BB5D37EE6B1D41FF56B8E150B35E61BA74F9FFE6CFDFAF57E5F5F9FFFE2C58B9FAE642242C7D1F1EA849FAA13FCC01C470A618563A96745AF13CF96AA574AE140AE78895C958B5C55AF945CE9A452A9B7D3E9F4097D9192CFE7FDDEDE5E7F6E6ECE5C2A8442FBD1FE749CDE0FF54BFD9B635684EAFF8DC3870FFFB3B5B5D5BF7EFDBA39762CA89F42A1F063369BCD9BE34920AC70447956F43AE958B33F185D298503B9E22572152E72559D52721540575CD482E4B8BE38D9BB77AF3F3333632E0D2241C7D1F1C6D59DE3555DD9A12B392D2D2D7EB15834C7AB0AEA2F97CBCD343636BE6F8EE93A6185C32C06956AF607A32BA5702057BC44AE5EAFD91F8CAE945C05E82F57A97FFB3D3D3D7EA9543297041541C7533FD49FD677973976248686863EA6454EADAEE498A87E4B6BD6AC7958978F8D5924AC70C0FA29A5702057BC44AEA00DA5E48A500B9006FDCA0B2D4E6A09F5675CD9A9EC0DCAAA8FB7F6ECD9334BEFA9B1496F6FEFC36C367BCA1CDF6550387829A5702057BC44AEA00DA5E48A3E02AE7FBA8A5E6EAAF64A8E09F5A7BF8C459FC6AAE8A3E7274F9EFC337DBA2AEE1B8FA342FD170A85C9C6C6C65F99E7E02A281CBC945238902B5E2257D0865272A5161EF96001426F208EFB9E9CA5A07E8D3728477BEFAF3A76C5AE5DBBE6E823E14970F1E2C547EAE4FE619E87ABA070F0524AE140AE78895C411B4AC9957E33C063C78E99BFF66B0AF5AF5FD531CF2594A1A1A15F37373747BE4F4EB5D038AB56AD7A944AA57E6E9E0B27D483784EF94B73BB090A072FB9170EE4CA4D912B684309B9CA66B3BF08161E74FF1BFA7FD984FAD7EFB343E39BE754C6C18307CFD01D8D93A4A3A363249D4EB79AE7C289E0415C6AA2513878E940E140AE1C14B9823694902BB57D6BB0DFBE7DFBCC5FF756A071B473DB6A9E53199D9D9D9349BD6C1570F6EC59BACC75D83C174E680FE2A2138DC2C14B870A0772E590C815B4A1845CA9EF7B8376BAB37112D038DA39F5EAE713CAF6EDDB67C7C6C6CC7EAC72E3C68D71757257CC73E144C804874E340A072F1D2C1CC895032257D0861272A5BE5E09B627B596A071B473597A2DD1D2D2527AF0E081D98F55A6A7A767D4C9DD35CF851321136BFA6AA2513878E970E140AE188B5C411B0AC9D554F07D526B091A473B87A5D712AB57AFF6E7E7E7CD7EACA2C67B12F28039290A072FCDF97155E48A97E6FCB82A72C54B737E5C37A9B5048DA38DFBCC5CD794B17EFDFAF9A4566101939393DF66A2ACC2EA8839819AE732B814CC56879F2121578C45AEA00D25E44A7DBD1B6C4F6A2D51F1159D2D5BB63C49EA75B5806BD7AE5DCD44795DAD8E2C36B13ADC0B873AC5506BD5CE4D070B8713B9F242324026D55E6F91AB787A21734ABAD26E5B09B9CAB8F01E9D9D3B777E93F4A7AE8E1C39722CE3CEA7AECA265627E9C251A95EC80FADBEADDA766E3A54389CCA951732E7FA36DBEDF516B98AA7173287FA36EEEDB69590AB8C0B9FBAEAE8E8F824E9FBE86CDCB8B1CF81FBE8BC766275922E1C95EA85FCD0EADBAA6DE7A60385C3C95C792173AE6FB3DD5E6F91AB787A2173A86FE3DE6E5B09B9CAB8701F9D5C2EF74EA1507899E09D917F502736C9FDCEC85149BA7054AA17F243AB6FABB69D9BDC0B4754B8E5CA0B99737D9BEDF67A8B5CC5D30B99437D1BF776DB4AC8553DEE8CBC76EDDA523066A43B23136D6D6DDF25F5F2557777F75F33F85B5789E985FCD0EADBAA6DE7A684C24170CB951732E7FA36DBEDF516B98AA7173287FA36EEEDB695922BF67FEB8A686E6EFE70D3A64DF309FCF5F23BEAE46EE2AF9727A717F243AB6FABB69D9B520A07B75C792173AE6FB3DD5E6F91AB787A2173A86FE3DE6E5B29B9CA24F8D7CB9B9A9A5E0663D1B8E6B92CCAC68D1BBFEAEBEB2B991DD790525757D7A7EAC4FACCB15D26E9C251A95EC80FADBEADDA766E4A291CDC72E585CCB9BECD767BBD45AEE2E985CCA1BE8D7BBB6DA5E42A954ABDA95FD5D9BB77AF5F2AD5763941FD51BFFAD51C1AD73C97455107BC97CBE566AE5FBF6EF65F132E5CB870509DDC048D638EED3249178E4A55A7186AADDAB929A57070CB9517920132A9F67A8B5CC5D30B9953D29576DB4AC915A17EBF3768575AFC9E9E1E73195015D49FDE3F8D679E4324D40AE983A6A6A61F8BC5A23946558C8F8F7FAE4EEA5E6363E3FBE698AE9374E1808B2BA5702057BC44AEA00DA5E42A40AD21BAB42B2EAF1627D55ED9A1E3A91FEA4FEBBBCB1CBB22B2D96C3E97CB3DA9D5959D852B3974E7C426732C09A070F0524AE140AE78895C411B4AC955407B7BFB0AF5BBFEB87EE5855E6E8AFB9E1D3A4E7FB96AC1E3348E3976C5D09597D5AB573FECEDED7D18F70DCAF4C6E385F7E4FC20F14A4E000A072FA5140EE48A97C815B4A1945CE9A452A9B7D3E9F4097D71426F5056EB097F6E6ECE5C2A8442FBD1FEC61B8F7DEA97FA37C78C8DEAF0DD6C367BBA50284C5EBC78F151D4FBECA8FD8ADDDDDD9FA993BAA93C49FD987D4B02858397520A0772C54BE40ADA504AAE4CE88A8BFE325620DD6767FFFEFDAFEE6C7CEBD62D7F7A7AFAD5BA81BED2F7B49DDAF5FBE404527F35B99213067D145C0D32B86AD5AAC76A90AFFBFBFB476EDCB8F1FDD4D4D453757E33F7EFDF1F1B1D1DBD7AF4E8D1DEB6B6B69399FFFFC9F641491F215F0C140E5E4A291C2EE5EAE9D3A7FEC8C848C5D271665F5C45AE9217B9721FB51668D03F8D15C785E3E3BDF1B852E84EC66AB096743A4D7FA7EAB292EE894327425F2F2F6C6F9172C7E3A8B854389683520A874BB9A25F2E5EC8275796928E33FBE22A7295BCC8950CE823E06A6D90AF74C1B3B07FBEE28F9083DAE352E1580E4A291C2EE50ACFBCDD01B9E2A5945C45856E2F934EA7B72D76C184DAA5DD86C6795C2A1CCB41298503B9E22572056D2825574038281CBC945238902B5E2257D0865272058483C2C14B298503B9E22572056D2825574038281CBC9452385CCA15DE4BE10EC8152FA5E40A08C7A5C2B11C9452385CCA15FD72F1423EFDB294749CD9175791ABE445AE0060824B85633928A570B8942B3CF37607E48A9752720584E352E1580E4A291CC8152F912B684329B902C241E1E0A594C2815CF112B9823694922B201C140E5E4A291CC8152F912B684329B902C241E1E0A594C2E152AEF05E0A7740AE782925574038FDFDFDA5D9D9D9B200C3E455F3704F158E67E61CB9884BB9A25F2E5EC8A75F96928E33FBE22872551F912B009830383878AF582C96851826EFEDDBB7BB55E1B86CCE918BB8942BE9CFBC91ABFA885C01C0848181818FCE9F3FFF78626262CA95674AD2548FFBC4F8F8F8DF54D1F85ED960CE918B2057F517B9823694982BB00CA0B0D2CA5CF99C5E7385894B8F3B3DFEA28A06FD7F16FE5FC8557D44AEA00D45E60A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A022FE0703BF003102A0BB720000000049454E44AE426082, 1);
-- [act_ge_bytearray] 3 è¡Œ
DELETE FROM `act_re_procdef` WHERE KEY_ = 'finance_reimbursement_approve';
INSERT INTO `act_re_procdef` (`ID_`, `REV_`, `CATEGORY_`, `NAME_`, `KEY_`, `VERSION_`, `DEPLOYMENT_ID_`, `RESOURCE_NAME_`, `DGRM_RESOURCE_NAME_`, `DESCRIPTION_`, `HAS_START_FORM_KEY_`, `HAS_GRAPHICAL_NOTATION_`, `SUSPENSION_STATE_`, `TENANT_ID_`, `ENGINE_VERSION_`, `DERIVED_FROM_`, `DERIVED_FROM_ROOT_`, `DERIVED_VERSION_`) VALUES ('da66f13a-bbb5-11f1-a8aa-00155dd68500', 2, 'finance_reimbursement_approve', '报销审批', 'finance_reimbursement_approve', 1, 'd9acc8f7-bbb5-11f1-a8aa-00155dd68500', 'finance_reimbursement_approve.bpmn', 'finance_reimbursement_approve.finance_reimbursement_approve.png', NULL, 0, 1, 1, '', NULL, NULL, NULL, 0);
-- [act_re_procdef] 1 è¡Œ
DELETE FROM `act_re_deployment` WHERE ID_ = 'd9acc8f7-bbb5-11f1-a8aa-00155dd68500';
INSERT INTO `act_re_deployment` (`ID_`, `NAME_`, `CATEGORY_`, `KEY_`, `TENANT_ID_`, `DEPLOY_TIME_`, `DERIVED_FROM_`, `DERIVED_FROM_ROOT_`, `PARENT_DEPLOYMENT_ID_`, `ENGINE_VERSION_`) VALUES ('d9acc8f7-bbb5-11f1-a8aa-00155dd68500', '报销审批', 'finance_reimbursement_approve', 'finance_reimbursement_approve', '', '2026-09-29 11:28:34.065', NULL, NULL, 'd9acc8f7-bbb5-11f1-a8aa-00155dd68500', NULL);
-- [act_re_deployment] 1 è¡Œ
DELETE FROM `bpm_process_definition_info` WHERE model_id = 'd934d9d5-bbb5-11f1-a8aa-00155dd68500';
INSERT INTO `bpm_process_definition_info` (`id`, `process_definition_id`, `model_id`, `model_type`, `category`, `icon`, `description`, `form_type`, `form_id`, `form_conf`, `form_fields`, `form_custom_create_path`, `form_custom_view_path`, `simple_model`, `visible`, `sort`, `start_user_ids`, `start_dept_ids`, `manager_user_ids`, `allow_cancel_running_process`, `allow_withdraw_task`, `process_id_rule`, `auto_approval_type`, `title_setting`, `summary_setting`, `process_before_trigger_setting`, `process_after_trigger_setting`, `task_before_trigger_setting`, `task_after_trigger_setting`, `print_template_setting`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (42, 'da66f13a-bbb5-11f1-a8aa-00155dd68500', 'd934d9d5-bbb5-11f1-a8aa-00155dd68500', 10, 'finance_reimbursement_approve', NULL, '报销单两级审批(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰', 20, NULL, NULL, NULL, '/finance/reimbursement/index', '/finance/reimbursement/detail/approve/index.vue', NULL, b'1', 1790652513269, '', NULL, '1', b'1', b'1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '1', '2026-09-29T11:28:36', '1', '2026-09-29T11:28:36', b'0');
-- [bpm_process_definition_info] 1 è¡Œ
docs/sql/_merge_archive.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,294 @@
// ä¸€æ¬¡æ€§è„šæœ¬ï¼šæŠŠæ¡£æ¡ˆç®¡ç†æ¨¡å—ï¼ˆyudao-module-archive)并入 config_export_all_20260929.sql çš„阶段一
// ç”¨æ³•:node _merge_archive.js
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const DIR = __dirname;
const TARGET = path.join(DIR, 'config_export_all_20260929.sql');
const MODULE = path.join(DIR, 'archive_module.sql');
const BPM = path.join(DIR, '_archive_bpm_export.sql');
const TMP = path.join(os.homedir(), 'AppData', 'Local', 'Temp');
const raw = fs.readFileSync(TARGET, 'utf8');
if (!raw.includes('\r\n')) throw new Error('目标文件不是 CRLF');
let doc = raw.replace(/\r\n/g, '\n');
const norm = (s) => s.replace(/\r\n/g, '\n');
const moduleLines = norm(fs.readFileSync(MODULE, 'utf8')).split('\n');
const bpmLines = norm(fs.readFileSync(BPM, 'utf8')).split('\n');
/** æ–­è¨€é”šç‚¹å”¯ä¸€ */
function anchor(text) {
  const n = doc.split(text).length - 1;
  if (n !== 1) throw new Error(`锚点出现 ${n} æ¬¡ï¼ŒæœŸæœ› 1 æ¬¡ï¼š${text.slice(0, 90)}`);
}
/** åœ¨é”šç‚¹å‰æ’入(插入内容与锚点之间留一个空行) */
function insertBefore(text, before) {
  anchor(text);
  doc = doc.replace(text, before + '\n\n' + text);
}
/** æ›¿æ¢ä¸€æ¬¡ */
function replaceOnce(from, to) {
  anchor(from);
  doc = doc.replace(from, to);
}
// ---------------------------------------------------------------- æ•°æ®è¡Œé£Žæ ¼å½’一
/** æŒ‰é¡¶å±‚逗号切分 VALUES å…ƒç»„(尊重单引号与反斜杠转义) */
function splitTuple(s) {
  const out = [];
  let cur = '';
  let inS = false;
  for (let i = 0; i < s.length; i++) {
    const c = s[i];
    if (inS) {
      cur += c;
      if (c === '\\' && i + 1 < s.length) {
        cur += s[++i];
      } else if (c === "'") {
        if (s[i + 1] === "'") cur += s[++i];
        else inS = false;
      }
    } else if (c === "'") {
      inS = true;
      cur += c;
    } else if (c === ',') {
      out.push(cur.trim());
      cur = '';
    } else {
      cur += c;
    }
  }
  if (cur.trim()) out.push(cur.trim());
  return out;
}
/**
 * æŠŠä»Žåº“ JDBC å¯¼å‡ºçš„ INSERT è¡Œæ”¹å†™æˆæœ¬æ–‡ä»¶çš„æ—¢æœ‰é£Žæ ¼ï¼š
 * å­—符串/数字/日期一律加单引号,bit åˆ— deleted å‡º X'00',NULL ä¿æŒ NULL。
 */
function restyle(line) {
  const m = line.match(/^INSERT INTO `([^`]+)` \(([^)]+)\) VALUES \((.*)\);$/);
  if (!m) throw new Error(`无法解析数据行:${line.slice(0, 90)}`);
  const [, table, colText, valsText] = m;
  const cols = colText.split(',').map((c) => c.trim().replace(/`/g, ''));
  const vals = splitTuple(valsText);
  if (cols.length !== vals.length) {
    throw new Error(`${table} åˆ—æ•° ${cols.length} ä¸Žå€¼æ•° ${vals.length} ä¸ç¬¦`);
  }
  const out = vals.map((v, i) => {
    if (v === 'NULL') return 'NULL';
    if (cols[i] === 'deleted') {
      if (v !== '0') throw new Error(`${table}.deleted æœŸæœ› 0,实际 ${v}`);
      return "X'00'";
    }
    return v.startsWith("'") ? v : `'${v}'`;
  });
  return `INSERT INTO \`${table}\` (${cols.map((c) => '`' + c + '`').join(', ')}) VALUES (${out.join(', ')});`;
}
function readRows(file) {
  const text = norm(fs.readFileSync(path.join(TMP, file), 'utf8'));
  const rows = text
    .split('\n')
    .map((l) => l.trim())
    .filter((l) => l.startsWith('INSERT INTO '))
    .map(restyle);
  return rows;
}
const CATEGORY_ROWS = readRows('archive_category_rows.sql');
const DICT_TYPE_ROWS = readRows('archive_dict_type_rows.sql');
const DICT_DATA_ROWS = readRows('archive_dict_data_rows.sql');
const BPM_CATEGORY_ROWS = readRows('archive_bpm_category_row.sql');
if (CATEGORY_ROWS.length !== 6) throw new Error(`分类数据 ${CATEGORY_ROWS.length} è¡Œï¼ŒæœŸæœ› 6`);
if (DICT_TYPE_ROWS.length !== 6) throw new Error(`字典类型 ${DICT_TYPE_ROWS.length} è¡Œï¼ŒæœŸæœ› 6`);
if (DICT_DATA_ROWS.length !== 19) throw new Error(`字典数据 ${DICT_DATA_ROWS.length} è¡Œï¼ŒæœŸæœ› 19`);
if (BPM_CATEGORY_ROWS.length !== 1) throw new Error(`BPM åˆ†ç±» ${BPM_CATEGORY_ROWS.length} è¡Œï¼ŒæœŸæœ› 1`);
// ================================================================ 1. è¡¨æ•°é‡ 427 â†’ 430
replaceOnce(
  '-- èŒƒå›´ï¼šå…¨åº“ BASE TABLE å…± 427 å¼ ï¼ˆ0918 çš„ 419 å¼  + 2 å¼  finance_* + 2 å¼  ums_* + 4 å¼  research_*),全部输出 CREATE TABLE;',
  '-- èŒƒå›´ï¼šå…¨åº“ BASE TABLE å…± 430 å¼ ï¼ˆ0918 çš„ 419 å¼  + 2 å¼  finance_* + 2 å¼  ums_* + 4 å¼  research_* + 3 å¼  archive_*),全部输出 CREATE TABLE;',
);
// ============================================= 2. å¸¦æ•°æ®è¡¨æ¸…单补 â‘¦ï¼ˆæ¡£æ¡ˆç®¡ç†æ¨¡å—)
insertBefore(
  '--     â‘¥ AI é…ç½®ï¼ˆä»…模型+key)+ åŸºç¡€è®¾æ–½é…ç½®ç®¡ç†ï¼šai_model、ai_api_key、infra_config、infra_job、infra_file_config、infra_data_source_config、infra_codegen_table、infra_codegen_column',
  '--     â‘¦ æ¡£æ¡ˆç®¡ç†æ¨¡å—(1 å¼ å¸¦åˆå§‹æ•°æ® + 2 å¼ ä»…结构):archive_category(初始 6 æ¡åˆ†ç±»ï¼‰ã€archive_document、archive_borrow',
);
// ============================================================== 3. è¡¥ä¸ç™»è®°å—
const PREAMBLE = [
  '-- è¡¥ä¸å†…容(2026-09-29,档案管理模块 yudao-module-archive):',
  '--   â‘  è¡¥ 3 å¼  archive_* è¡¨ç»“构(档案分类 / æ¡£æ¡ˆä¸»è¡¨ / å€Ÿé˜…单),其中 archive_category å¸¦åˆå§‹ 6 æ¡åˆ†ç±»æ•°æ®ï¼š',
  '--      archive_category、archive_document、archive_borrow',
  '--   â‘¡ è¡¥ 6 ä¸ªå­—典类型 + 19 æ¡å­—典数据(并入阶段一的 system_dict_type / system_dict_data):',
  '--      archive_category_status(启用/停用)',
  '--      archive_document_status(在库/借出中)',
  '--      archive_secret_level(公开/内部/保密)',
  '--      archive_carrier_type(电子/实体/电子+实体)',
  '--      archive_source_type(手工录入/课题归档/会员归档/财务归档)',
  '--      archive_borrow_status(草稿/审批中/借出中/已归还/已驳回)',
  '--   â‘¢ è¡¥ 1 æ¡ BPM åˆ†ç±»ï¼ˆå¹¶å…¥é˜¶æ®µä¸€çš„ bpm_category):',
  '--      id=8003 \'档案借阅审批\' / code=\'archive_borrow_approve\'',
  '--   â‘£ è¡¥ 1 æ¡ BPM æµç¨‹å®šä¹‰ï¼ˆæ¡£æ¡ˆå€Ÿé˜…两级审批:档案管理员审核→分管领导审批),',
  '--      åˆ†åˆ«è½ act_re_deployment / act_re_procdef / act_ge_bytearray(3 æ¡) / act_re_model /',
  '--      bpm_process_definition_info äº”张表,流程 key=\'archive_borrow_approve\'。',
  '--      bpm_process_definition_info.form_type=20(自定义表单),',
  '--      form_custom_create_path=/archive/borrow/index、',
  '--      form_custom_view_path=/archive/borrow/detail/approve/index.vue。',
  '--      âš  è¯¥æµç¨‹çš„ BPMN ä¸ºæ‰‹å†™ä¸¤èŠ‚ç‚¹ï¼Œ**不是** bpm_deploy_flow_from_template ç”Ÿæˆçš„单节点流程。',
  '--   â‘¤ è¡¥ 3 å¼ æ¥æºè¡¨å„ 1 åˆ— archive_document_id(bigint NULL,归档联动回写档案 ID):',
  '--      research_application(课题申报单)、ums_member(会员)、finance_reimbursement(报销单)。',
  '--      åˆ—可空、不影响既有数据;「是否已归档」以该列为硬标记(另有 archive_document ä¾§',
  '--      source_type+source_id å¹‚等兜底)。',
  '--   â‘¥ è¡¥ 24 æ¡æ¡£æ¡ˆç®¡ç†èœå•:1075600「档案管理」(**顶级目录**,parent_id=0,path=/archive)',
  '--      + 1075601「档案分类」/ 1075602「档案管理」/ 1075603「借阅登记」/ 1075604「档案统计」',
  '--      + 1075610~1075613(分类查询/创建/更新/删除)、',
  '--        1075620~1075625(档案查询/创建/更新/删除/导出/保密档案查看)、',
  '--        1075630~1075635(借阅查询/登记/修改/删除/提交审批/归还登记)、',
  '--        1075640(档案统计查询)、',
  '--        1075536(课题申报归档,挂 1075512 è¯¾é¢˜ç”³æŠ¥ä¸‹ï¼‰ã€',
  '--        1075504(会员归档,挂 1075492 ä¼šå‘˜åˆ—表下)。',
  '--      è´¢åŠ¡å½’æ¡£å¤ç”¨æ—¢æœ‰ finance:reimbursement:archive(1075424),不新增权限点。',
  '--      **菜单段本文件无需为此改动**:阶段② çš„写法是 `INSERT INTO system_menu SELECT *',
  '--      FROM \\`mom-jssm\\`.system_menu WHERE deleted=b\'0\'`,是执行期跨库全量拷贝,',
  '--      åªè¦æ¥æºåº“ mom-jssm æœ‰è¿™å‡ è¡Œå°±ä¼šè¢«å¸¦è¿‡åŽ» â€”— ä¸Žé˜¶æ®µä¸€çš„字面量 INSERT ä¸åŒã€‚',
  '--      åœ¨æ­¤ç™»è®°åªä¸ºè¯´æ˜Žã€Œè¿™ç‰ˆæ–‡ä»¶ç¡®å®žè¦†ç›–了该变更」,避免后来者以为漏了。',
  '--   âš  æ¡£æ¡ˆæ¨¡å—çš„ BPMN éƒ¨ç½²äº§ç‰©ï¼ˆâ‘£ï¼‰ä¸º**先部署后导出**:新库初始化时依赖本文件的字面量 INSERT,',
  '--     è‹¥é‡è·‘导出请勿遗漏五张表(含 act_ge_bytearray çš„ 3 æ¡ï¼šbpmn / png / source)。',
].join('\n');
insertBefore('-- åŽ†å²è¡¥ä¸å†…å®¹ï¼ˆ2026-09-18):', PREAMBLE);
// ======================================== 4. 3 å¼  archive_* è¡¨ DDL + åˆå§‹åˆ†ç±»æ•°æ®
function ddlBlock(fromLine, toLine, tableName, rows) {
  const body = moduleLines.slice(fromLine - 1, toLine).join('\n');
  const titles = rows ? `导出 ${rows.length} è¡Œ` : '仅 DDL,无数据';
  return [`-- ============ è¡¨ï¼š${tableName}(${titles}) ============`, body, ...(rows || []), ''].join('\n');
}
const ARCHIVE_DDL = [
  '-- ***********************************************************************',
  '-- â‘¦ æ¡£æ¡ˆç®¡ç†æ¨¡å—(yudao-module-archive:分类树 + æ¡£æ¡ˆä¸»è¡¨ + å€Ÿé˜…单)',
  '-- ***********************************************************************',
  '',
  ddlBlock(9, 28, 'archive_category', CATEGORY_ROWS),
  ddlBlock(31, 67, 'archive_document', null),
  ddlBlock(70, 104, 'archive_borrow', null),
].join('\n');
insertBefore(
  ['-- ***********************************************************************', '-- å…¶ä½™ 382 å¼ è¡¨ï¼ˆä»…表结构,无数据)', '-- ***********************************************************************'].join('\n'),
  ARCHIVE_DDL,
);
// ===================================== 5. ä¸‰å¼ æ¥æºè¡¨å„è¡¥ 1 åˆ— archive_document_id
const ARCHIVE_COL =
  "  `archive_document_id` bigint DEFAULT NULL COMMENT '归档生成的档案 ID(archive_document.id,归档联动回写)',";
// 5.1 research_application(放在 notice_id ä¹‹åŽï¼‰
replaceOnce(
  "  `notice_id` bigint DEFAULT NULL COMMENT '已发布的立项通知公告 ID',",
  "  `notice_id` bigint DEFAULT NULL COMMENT '已发布的立项通知公告 ID',\n" + ARCHIVE_COL,
);
// 5.2 ums_member(放在 status ä¹‹åŽï¼›ä¸¤è¡Œé”šç‚¹ä»¥æ¶ˆæ­§ï¼‰
replaceOnce(
  "  `expire_date` date DEFAULT NULL COMMENT '有效期至',\n" +
    "  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-正常 0-暂停',",
  "  `expire_date` date DEFAULT NULL COMMENT '有效期至',\n" +
    "  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-正常 0-暂停',\n" +
    ARCHIVE_COL,
);
// 5.3 finance_reimbursement(放在 archive_time ä¹‹åŽï¼›ä¸¤è¡Œé”šç‚¹ä»¥æ¶ˆæ­§ï¼‰
replaceOnce(
  "  `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'BPM流程实例ID',\n" +
    "  `archive_time` datetime DEFAULT NULL COMMENT '归档时间',",
  "  `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'BPM流程实例ID',\n" +
    "  `archive_time` datetime DEFAULT NULL COMMENT '归档时间',\n" +
    ARCHIVE_COL,
);
// "其余 382 å¼ è¡¨" â†’ 384(⑦ å·²æ’å…¥ 3 å¼ ï¼Œå…¶ä¸­ 2 å¼ æ— æ•°æ®æ•…不进带数据清单)
replaceOnce(
  ['-- ***********************************************************************', '-- å…¶ä½™ 382 å¼ è¡¨ï¼ˆä»…表结构,无数据)', '-- ***********************************************************************'].join('\n'),
  ['-- ***********************************************************************', '-- å…¶ä½™ 384 å¼ è¡¨ï¼ˆä»…表结构,无数据)', '-- ***********************************************************************'].join('\n'),
);
// ===================================================== 6. BPM éƒ¨ç½²äº§ç‰©ï¼ˆ7 æ¡ INSERT)
const bpmInserts = bpmLines.filter((l) => l.startsWith('INSERT INTO '));
if (bpmInserts.length !== 7) throw new Error(`BPM INSERT æ¡æ•° ${bpmInserts.length},期望 7`);
const pick = (table) => bpmInserts.filter((l) => l.startsWith(`INSERT INTO \`${table}\``));
const insProcdef = pick('act_re_procdef');
const insDeployment = pick('act_re_deployment');
const insBytearray = pick('act_ge_bytearray');
const insModel = pick('act_re_model');
const insProcInfo = pick('bpm_process_definition_info');
if (
  insProcdef.length !== 1 ||
  insDeployment.length !== 1 ||
  insBytearray.length !== 3 ||
  insModel.length !== 1 ||
  insProcInfo.length !== 1
) {
  throw new Error('BPM INSERT åˆ†ç±»æ•°é‡ä¸ç¬¦');
}
// 6.1 act_re_procdef(23 â†’ 24 è¡Œï¼‰
insertBefore('-- ============ è¡¨ï¼šact_re_deployment(导出 23 è¡Œï¼‰ ============', insProcdef.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šact_re_procdef(导出 23 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_re_procdef(导出 24 è¡Œï¼‰ ============',
);
// 6.2 act_re_deployment(23 â†’ 24 è¡Œï¼‰
insertBefore('-- ============ è¡¨ï¼šact_ge_bytearray(导出 28 è¡Œï¼‰ ============', insDeployment.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šact_re_deployment(导出 23 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_re_deployment(导出 24 è¡Œï¼‰ ============',
);
// 6.3 act_ge_bytearray(28 â†’ 31 è¡Œï¼Œbpmn / png / source ä¸‰æ¡ï¼‰
insertBefore('-- ============ è¡¨ï¼šact_re_model(导出 23 è¡Œï¼‰ ============', insBytearray.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šact_ge_bytearray(导出 28 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_ge_bytearray(导出 31 è¡Œï¼‰ ============',
);
// 6.4 act_re_model(23 â†’ 24 è¡Œï¼‰
insertBefore('-- ============ è¡¨ï¼šbpm_process_definition_info(导出 23 è¡Œï¼‰ ============', insModel.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šact_re_model(导出 23 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_re_model(导出 24 è¡Œï¼‰ ============',
);
// 6.5 bpm_process_definition_info(23 â†’ 24 è¡Œï¼Œæ’在 â‘¡ åˆ†ç»„横幅之前)
insertBefore(
  ['-- ***********************************************************************', '-- â‘¡ ååŒï¼ˆä¸šåŠ¡é™„ä»¶é…ç½® + åˆ†ç±» + è¡¨å• + æ‰©å±•配置)(DDL + æ•°æ®ï¼‰', '-- ***********************************************************************'].join(
    '\n',
  ),
  insProcInfo.join('\n'),
);
replaceOnce(
  '-- ============ è¡¨ï¼šbpm_process_definition_info(导出 23 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šbpm_process_definition_info(导出 24 è¡Œï¼‰ ============',
);
// ===================================================== 7. bpm_category è¡¥ 8003(25 â†’ 26 è¡Œï¼‰
insertBefore('-- ============ è¡¨ï¼šbpm_form(导出 4 è¡Œï¼‰ ============', BPM_CATEGORY_ROWS.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šbpm_category(导出 25 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šbpm_category(导出 26 è¡Œï¼‰ ============',
);
// ================================================ 8. å­—典类型 6 æ¡ï¼ˆ254 â†’ 260 è¡Œï¼‰
insertBefore('-- ============ è¡¨ï¼šsystem_dict_data(导出 1205 è¡Œï¼‰ ============', DICT_TYPE_ROWS.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šsystem_dict_type(导出 254 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šsystem_dict_type(导出 260 è¡Œï¼‰ ============',
);
// ================================================ 9. å­—典数据 19 æ¡ï¼ˆ1205 â†’ 1224 è¡Œï¼‰
insertBefore('-- ============ è¡¨ï¼šsystem_tenant(导出 3 è¡Œï¼‰ ============', DICT_DATA_ROWS.join('\n'));
replaceOnce(
  '-- ============ è¡¨ï¼šsystem_dict_data(导出 1205 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šsystem_dict_data(导出 1224 è¡Œï¼‰ ============',
);
// ---------------------------------------------------------------- å†™å‡º
doc = doc.replace(/\n/g, '\r\n');
fs.writeFileSync(TARGET, doc, 'utf8');
console.log(`合并完成 â†’ ${path.basename(TARGET)}(${doc.split('\r\n').length} è¡Œï¼‰`);
docs/sql/_merge_research.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,235 @@
// ä¸€æ¬¡æ€§è„šæœ¬ï¼šæŠŠè¯¾é¢˜ç®¡ç†æ¨¡å—ï¼ˆyudao-module-research)的补丁并入 config_export_all_20260928.sql çš„阶段一
const fs = require('node:fs');
const path = require('node:path');
const dir = __dirname;
const TARGET = path.join(dir, 'config_export_all_20260928.sql');
const BPM = path.join(dir, '_research_bpm_export.sql');
const MODULE = path.join(dir, 'research_module.sql');
const EOL = '\r\n';
let doc = fs.readFileSync(TARGET, 'utf8');
const bpmLines = fs.readFileSync(BPM, 'utf8').split(/\r?\n/);
const moduleLines = fs.readFileSync(MODULE, 'utf8').split(/\r?\n/);
if (!doc.includes(EOL)) throw new Error('目标文件不是 CRLF');
/** æ–­è¨€é”šç‚¹å”¯ä¸€ */
function anchor(text) {
  const n = doc.split(text).length - 1;
  if (n !== 1) throw new Error(`锚点出现 ${n} æ¬¡ï¼ŒæœŸæœ› 1 æ¬¡ï¼š${text.slice(0, 80)}`);
}
/** åœ¨é”šç‚¹å‰æ’å…¥ */
function insertBefore(text, before) {
  anchor(text);
  doc = doc.replace(text, before + EOL + EOL + text);
}
/** æ›¿æ¢ä¸€æ¬¡ */
function replaceOnce(from, to) {
  anchor(from);
  doc = doc.replace(from, to);
}
// ---------------------------------------------------------------- 1. è¡¨æ•°é‡
replaceOnce(
  '-- èŒƒå›´ï¼šå…¨åº“ BASE TABLE å…± 423 å¼ ï¼ˆ0918 çš„ 419 å¼  + 2 å¼  finance_* + 2 å¼  ums_*),全部输出 CREATE TABLE;',
  '-- èŒƒå›´ï¼šå…¨åº“ BASE TABLE å…± 427 å¼ ï¼ˆ0918 çš„ 419 å¼  + 2 å¼  finance_* + 2 å¼  ums_* + 4 å¼  research_*),全部输出 CREATE TABLE;',
);
// ------------------------------------------------- 2. å‰è¨€è¡¥ä¸ç™»è®°å—
const PREAMBLE = [
  '-- è¡¥ä¸å†…容(2026-09-28,课题管理模块 yudao-module-research):',
  '--   â‘  è¡¥ 4 å¼  research_* è¡¨ç»“构(课题指南 / è¯¾é¢˜ç”³æŠ¥ä¹¦ / ä¸“家评审明细 / ç»è´¹æ‹¨ä»˜è®°å½•,仅建空结构,无数据):',
  '--      research_guideline、research_application、research_topic_review、research_fund_record',
  '--   â‘¡ è¡¥ 5 ä¸ªå­—典类型 + 13 æ¡å­—典数据(并入阶段一的 system_dict_type / system_dict_data):',
  '--      research_guideline_category(重点课题/一般课题)',
  '--      research_guideline_status(草稿/已发布/已截止)',
  '--      research_application_status(草稿/审批中/已立项/已驳回)',
  '--      research_review_conclusion(推荐立项/不推荐立项)',
  '--      research_fund_status(待拨付/已拨付)',
  '--   â‘¢ è¡¥ 1 æ¡ BPM åˆ†ç±»ï¼ˆå¹¶å…¥é˜¶æ®µä¸€çš„ bpm_category):',
  '--      id=8002 \'课题立项审批\' / code=\'research_topic_approve\'',
  '--   â‘£ è¡¥ 1 æ¡ BPM æµç¨‹å®šä¹‰ï¼ˆè¯¾é¢˜ç”³æŠ¥ä¸‰çº§å®¡æ‰¹ï¼šç§˜ä¹¦å¤„形式审查→专家评审→学会立项审批),',
  '--      åˆ†åˆ«è½ act_re_deployment / act_re_procdef / act_ge_bytearray(3 æ¡) / act_re_model /',
  '--      bpm_process_definition_info äº”张表,流程 key=\'research_topic_approve\'。',
  '--      bpm_process_definition_info.form_type=20(自定义表单),',
  '--      form_custom_create_path=/research/application/index、',
  '--      form_custom_view_path=/research/application/detail/approve/index.vue。',
  '--      âš  è¯¥æµç¨‹çš„ BPMN ä¸ºæ‰‹å†™ä¸‰èŠ‚ç‚¹ï¼Œ**不是** bpm_deploy_flow_from_template ç”Ÿæˆçš„单节点流程。',
  '--   â‘¤ è¡¥ 22 æ¡è¯¾é¢˜ç®¡ç†èœå•:1075510「课题管理」(**顶级目录**,parent_id=0,path=/research)',
  '--      + 1075511「课题指南」/ 1075512「课题申报」(菜单,挂 1075510 ä¸‹ï¼‰',
  '--      + 1075520~1075524(指南查询/创建/更新/删除/发布截止)、',
  '--        1075530~1075535(申报查询/创建/更新/删除/提交审批/发布立项通知)、',
  '--        1075540~1075543(专家评审查询/录入/修改/删除)、',
  '--        1075550~1075553(经费拨付查询/登记/修改/删除)。',
  '--      **菜单段本文件无需为此改动**:阶段② çš„写法是 `INSERT INTO system_menu SELECT *',
  '--      FROM \\`mom-jssm\\`.system_menu WHERE deleted=b\'0\'`,是执行期跨库全量拷贝,',
  '--      åªè¦æ¥æºåº“ mom-jssm æœ‰è¿™å‡ è¡Œå°±ä¼šè¢«å¸¦è¿‡åŽ» â€”— ä¸Žé˜¶æ®µä¸€çš„字面量 INSERT ä¸åŒã€‚',
  '--      åœ¨æ­¤ç™»è®°åªä¸ºè¯´æ˜Žã€Œè¿™ç‰ˆæ–‡ä»¶ç¡®å®žè¦†ç›–了该变更」,避免后来者以为漏了。',
  '',
].join(EOL);
insertBefore('-- åŽ†å²è¡¥ä¸å†…å®¹ï¼ˆ2026-09-18):', PREAMBLE);
// --------------------------------------------- 3. 4 å¼  research_* è¡¨ DDL
// ä»Ž research_module.sql æŠ½å– CREATE TABLE æ®µï¼ˆç¬¬ 10~30 / 34~70 / 74~96 / 100~116 è¡Œï¼Œ1-based)
function ddlBlock(fromLine, toLine, tableName) {
  const body = moduleLines.slice(fromLine - 1, toLine).join(EOL);
  return [
    `-- ============ è¡¨ï¼š${tableName}(仅 DDL,无数据) ============`,
    body,
    '',
  ].join(EOL);
}
const RESEARCH_DDL = [
  ddlBlock(10, 30, 'research_guideline'),
  ddlBlock(34, 70, 'research_application'),
  ddlBlock(74, 96, 'research_topic_review'),
  ddlBlock(100, 116, 'research_fund_record'),
].join(EOL);
insertBefore('-- ============ è¡¨ï¼šyudao_demo01_contact(仅 DDL,无数据) ============', RESEARCH_DDL);
// --------------------------------------------- 4. BPM æµç¨‹ INSERT(7 æ¡è¯­å¥ï¼‰
// _research_bpm_export.sql çš„第 10~16 è¡Œï¼ˆ1-based)
const bpmInserts = bpmLines.slice(9, 16).filter((l) => l.trim() !== '');
if (bpmInserts.length !== 7) throw new Error(`BPM INSERT æ¡æ•° ${bpmInserts.length},期望 7`);
const pick = (prefix) => {
  const hit = bpmInserts.filter((l) => l.startsWith(prefix));
  return hit;
};
const insProcdef = pick('INSERT INTO `act_re_procdef`');
const insDeployment = pick('INSERT INTO `act_re_deployment`');
const insBytearray = pick('INSERT INTO `act_ge_bytearray`');
const insModel = pick('INSERT INTO `act_re_model`');
const insProcInfo = pick('INSERT INTO `bpm_process_definition_info`');
if (insProcdef.length !== 1 || insDeployment.length !== 1 || insBytearray.length !== 3 || insModel.length !== 1 || insProcInfo.length !== 1) {
  throw new Error('BPM INSERT åˆ†ç±»æ•°é‡ä¸ç¬¦');
}
// 4.1 act_re_procdef
replaceOnce(
  '-- ============ è¡¨ï¼šact_re_deployment(导出 21 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_re_deployment(导出 22 è¡Œï¼‰ ============',
);
insertBefore(
  '-- ============ è¡¨ï¼šact_re_deployment(导出 22 è¡Œï¼‰ ============',
  [...insProcdef, ''].join(EOL),
);
// 4.2 act_re_deployment
replaceOnce(
  '-- ============ è¡¨ï¼šact_ge_bytearray(导出 22 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_ge_bytearray(导出 25 è¡Œï¼‰ ============',
);
insertBefore(
  '-- ============ è¡¨ï¼šact_ge_bytearray(导出 25 è¡Œï¼‰ ============',
  [...insDeployment, ''].join(EOL),
);
// 4.3 act_ge_bytearray(bpmn / diagram / source ä¸‰æ¡ï¼‰
replaceOnce(
  '-- ============ è¡¨ï¼šact_re_model(导出 21 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šact_re_model(导出 22 è¡Œï¼‰ ============',
);
insertBefore(
  '-- ============ è¡¨ï¼šact_re_model(导出 22 è¡Œï¼‰ ============',
  [...insBytearray, ''].join(EOL),
);
// 4.4 act_re_model
replaceOnce(
  '-- ============ è¡¨ï¼šbpm_process_definition_info(导出 21 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šbpm_process_definition_info(导出 22 è¡Œï¼‰ ============',
);
insertBefore(
  '-- ============ è¡¨ï¼šbpm_process_definition_info(导出 22 è¡Œï¼‰ ============',
  [...insModel, ''].join(EOL),
);
// 4.5 bpm_process_definition_info(插在 â‘¡ åˆ†ç»„横幅之前)
const BANNER2 = [
  '-- ***********************************************************************',
  '-- â‘¡ ååŒï¼ˆä¸šåŠ¡é™„ä»¶é…ç½® + åˆ†ç±» + è¡¨å• + æ‰©å±•配置)(DDL + æ•°æ®ï¼‰',
  '-- ***********************************************************************',
].join(EOL);
insertBefore(BANNER2, [...insProcInfo, ''].join(EOL));
// 4.6 bpm_category è¡¥ 1 è¡Œ
replaceOnce(
  '-- ============ è¡¨ï¼šbpm_form(导出 4 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šbpm_form(导出 4 è¡Œï¼‰ ============',
);
insertBefore(
  '-- ============ è¡¨ï¼šbpm_form(导出 4 è¡Œï¼‰ ============',
  [
    'INSERT INTO `bpm_category` (`id`, `name`, `code`, `description`, `status`, `sort`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)',
    'VALUES (\'8002\', \'课题立项审批\', \'research_topic_approve\', \'课题申报三级审批(秘书处形式审查→专家评审→学会立项审批)\', \'0\', \'3\', \'1\', \'2026-09-28 20:00:00\', \'1\', \'2026-09-28 20:00:00\', X\'00\');',
    '',
  ].join(EOL),
);
replaceOnce(
  '-- ============ è¡¨ï¼šbpm_category(导出 24 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šbpm_category(导出 25 è¡Œï¼‰ ============',
);
// --------------------------------------- 5. å­—典类型 5 æ¡ + å­—典数据 13 æ¡
const DICT_TYPES = [
  ['81000', '课题指南类别', 'research_guideline_category', '0', '重点课题/一般课题分类标注'],
  ['81001', '课题指南状态', 'research_guideline_status', '0', '课题指南发布状态:草稿/已发布/已截止'],
  ['81002', '课题申报状态', 'research_application_status', '0', '课题申报书审批状态:草稿/审批中/已立项/已驳回'],
  ['81003', '专家评审结论', 'research_review_conclusion', '0', '专家评审推荐立项/不推荐立项'],
  ['81004', '经费拨付状态', 'research_fund_status', '0', '课题经费待拨付/已拨付'],
];
const dictTypeRows = DICT_TYPES.map(
  ([id, name, type, status, remark]) =>
    `('${id}', '${name}', '${type}', '${status}', '${remark}', '1', '2026-09-28 20:00:00', '1', '2026-09-28 20:00:00', X'00', NULL),`,
).join(EOL);
insertBefore(
  '-- ============ è¡¨ï¼šsystem_dict_data(导出 1192 è¡Œï¼‰ ============',
  [
    'INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`)',
    'VALUES',
    dictTypeRows.replace(/,$/, ';'),
    '',
  ].join(EOL),
);
replaceOnce(
  '-- ============ è¡¨ï¼šsystem_dict_type(导出 249 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šsystem_dict_type(导出 254 è¡Œï¼‰ ============',
);
const DICT_DATA = [
  ['8100001', 1, '重点课题', '1', 'research_guideline_category', 'danger'],
  ['8100002', 2, '一般课题', '2', 'research_guideline_category', 'default'],
  ['8100101', 1, '草稿', '0', 'research_guideline_status', 'default'],
  ['8100102', 2, '已发布', '20', 'research_guideline_status', 'success'],
  ['8100103', 3, '已截止', '30', 'research_guideline_status', 'info'],
  ['8100201', 1, '草稿', '0', 'research_application_status', 'default'],
  ['8100202', 2, '审批中', '15', 'research_application_status', 'processing'],
  ['8100203', 3, '已立项', '20', 'research_application_status', 'success'],
  ['8100204', 4, '已驳回', '35', 'research_application_status', 'danger'],
  ['8100301', 1, '推荐立项', '1', 'research_review_conclusion', 'success'],
  ['8100302', 2, '不推荐立项', '2', 'research_review_conclusion', 'danger'],
  ['8100401', 1, '待拨付', '0', 'research_fund_status', 'warning'],
  ['8100402', 2, '已拨付', '20', 'research_fund_status', 'success'],
];
const dictDataRows = DICT_DATA.map(
  ([id, sort, label, value, type, color]) =>
    `('${id}', '${sort}', '${label}', '${value}', '${type}', '0', '${color}', '', '', '1', '2026-09-28 20:00:00', '1', '2026-09-28 20:00:00', X'00'),`,
).join(EOL);
insertBefore(
  '-- ============ è¡¨ï¼šsystem_tenant(导出 3 è¡Œï¼‰ ============',
  [
    'INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)',
    'VALUES',
    dictDataRows.replace(/,$/, ';'),
    '',
  ].join(EOL),
);
replaceOnce(
  '-- ============ è¡¨ï¼šsystem_dict_data(导出 1192 è¡Œï¼‰ ============',
  '-- ============ è¡¨ï¼šsystem_dict_data(导出 1205 è¡Œï¼‰ ============',
);
// ---------------------------------------------------------------- å†™å‡º
fs.writeFileSync(TARGET, doc, 'utf8');
console.log('合并完成');
docs/sql/_patch_0929.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
// ç”± config_export_all_20260928.sql ç”Ÿæˆ config_export_all_20260929.sql(增量补丁)
// ç”¨æ³•:node _patch_0929.js
import fs from 'node:fs';
import path from 'node:path';
const DIR = process.cwd();
const SRC = path.join(DIR, 'config_export_all_20260928.sql');
const DST = path.join(DIR, 'config_export_all_20260929.sql');
const EXPORT = path.join(DIR, '_finance_bpm_export.sql');
const raw = fs.readFileSync(SRC, 'utf8');
const crlf = raw.includes('\r\n');
let sql = crlf ? raw.replace(/\r\n/g, '\n') : raw;
const exp = fs.readFileSync(EXPORT, 'utf8').replace(/\r\n/g, '\n');
function sub(oldStr, newStr, label) {
  const n = sql.split(oldStr).length - 1;
  if (n !== 1) throw new Error(`[${label}] æœŸæœ›å”¯ä¸€åŒ¹é…ï¼Œå®žé™… ${n} æ¬¡`);
  sql = sql.replace(oldStr, newStr);
  console.log(`  OK  ${label}`);
}
// ---------- ä»Žéƒ¨ç½²å¯¼å‡ºæ–‡ä»¶é‡Œå– INSERT è¡Œ ----------
function insertsOf(table) {
  return exp
    .split('\n')
    .filter((l) => l.startsWith(`INSERT INTO \`${table}\``))
    .map((l) => l.trim());
}
const procdefIns = insertsOf('act_re_procdef');
const deployIns = insertsOf('act_re_deployment');
const byteIns = insertsOf('act_ge_bytearray');
const modelIns = insertsOf('act_re_model');
const infoIns = insertsOf('bpm_process_definition_info');
if (procdefIns.length !== 1 || deployIns.length !== 1 || byteIns.length !== 3 || modelIns.length !== 1 || infoIns.length !== 1) {
  throw new Error(`导出文件行数不符:procdef=${procdefIns.length} deploy=${deployIns.length} byte=${byteIns.length} model=${modelIns.length} info=${infoIns.length}`);
}
console.log('1) æ–‡ä»¶å¤´');
sub('-- æ–‡ä»¶ï¼šconfig_export_all_20260928.sql', '-- æ–‡ä»¶ï¼šconfig_export_all_20260929.sql', '文件名');
sub(
  '-- æœ¬æ¬¡ä¸º**补丁版**:基于 0918 æ–‡ä»¶å¢žé‡ä¿®æ”¹ï¼Œæœªé‡è·‘全库导出(避免把无关表一起搅动)。',
  '-- æœ¬æ¬¡ä¸º**补丁版**:20260929 åŸºäºŽ 20260928 å¢žé‡ä¿®æ”¹ï¼ˆ20260928 åˆåŸºäºŽ 0918),未重跑全库导出(避免把无关表一起搅动)。',
  '补丁说明',
);
sub(
  '--      research_fund_status(待拨付/已拨付)',
  '--      research_fund_status(待拨付/已拨付;2026-09-29 æ‰©ä¸º å¾…拨付/报销审批中/已拨付/已驳回)',
  '头部字典说明',
);
sub(
  '--      âš  è¯¥æµç¨‹çš„ BPMN ä¸ºæ‰‹å†™ä¸‰èŠ‚ç‚¹ï¼Œ**不是** bpm_deploy_flow_from_template ç”Ÿæˆçš„单节点流程。',
  '--      âš  è¯¥æµç¨‹çš„ BPMN ä¸ºæ‰‹å†™ä¸‰èŠ‚ç‚¹ï¼Œ**不是** bpm_deploy_flow_from_template ç”Ÿæˆçš„单节点流程。\n' +
    '-- è¡¥ä¸å†…容(2026-09-29,课题经费拨付对接财务报销):\n' +
    '--   â‘  research_fund_record å¢ž 4 åˆ—(reimbursement_id / reimbursement_no / reject_reason / reject_time)\n' +
    '--      + status åˆ—注释改为「0-待拨付 15-报销审批中 20-已拨付 30-已驳回」+ idx_reimbursement_id ç´¢å¼•。\n' +
    '--      **无新增表**:全库仍 427 å¼ ï¼ˆfinance_reimbursement è¡¨å·²åœ¨ 0928 ç‰ˆå»ºç«‹ï¼‰ã€‚\n' +
    '--   â‘¡ research_fund_status å­—典由 2 æ¡æ‰©ä¸º 4 æ¡ï¼ˆå¾…拨付 0 / æŠ¥é”€å®¡æ‰¹ä¸­ 15 / å·²æ‹¨ä»˜ 20 / å·²é©³å›ž 30),\n' +
    '--      system_dict_type.remark åŒæ­¥æ”¹ä¸ºã€Œè¯¾é¢˜ç»è´¹å¾…拨付/报销审批中/已拨付/已驳回」。\n' +
    '--   â‘¢ è¡¥ 1 æ¡ BPM æµç¨‹å®šä¹‰ï¼ˆè´¢åŠ¡æŠ¥é”€ä¸¤çº§å®¡æ‰¹ï¼šè´¢åŠ¡å®¡æ ¸ â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰ï¼Œæµç¨‹ key=\'finance_reimbursement_approve\',\n' +
    '--      åˆ†åˆ«è½ act_re_procdef / act_re_deployment / act_ge_bytearray(3 æ¡) / act_re_model /\n' +
    '--      bpm_process_definition_info äº”张表(bpm_process_definition_info.id=42)。\n' +
    '--      form_type=20(自定义表单),form_custom_create_path=/finance/reimbursement/index、\n' +
    '--      form_custom_view_path=/finance/reimbursement/detail/approve/index.vue。\n' +
    '--      bpm_category(id=216, code=\'finance_reimbursement_approve\') å·²å­˜åœ¨ï¼Œæ— éœ€æ–°å¢žã€‚\n' +
    '--      âš  è¯¥æµç¨‹çš„ BPMN ä¸ºæ‰‹å†™ä¸¤èŠ‚ç‚¹ï¼Œ**不是** bpm_deploy_flow_from_template ç”Ÿæˆçš„单节点流程。\n' +
    '--   â‘£ **阶段二不动**:本次无菜单/角色/用户变更。',
  '新增补丁段',
);
sub(
  '--       âš  æœ¬ 0928 ç‰ˆä¸ºåœ¨å…¶ä¹‹ä¸Šçš„补丁,**尚未在空库整份跑过**;上线/初始化前请先在可覆盖空库验证。',
  '--       âš  æœ¬ 0929 ç‰ˆä¸ºåœ¨å…¶ä¹‹ä¸Šçš„补丁,**尚未在空库整份跑过**;上线/初始化前请先在可覆盖空库验证。',
  '验证说明',
);
console.log('2) research_fund_record è¡¨ç»“æž„');
sub(
  "  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待拨付 20-已拨付',\n" +
    "  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',\n" +
    "  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',\n" +
    '  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\',\n' +
    "  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',\n" +
    '  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\',\n' +
    "  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',\n" +
    '  PRIMARY KEY (`id`) USING BTREE,\n' +
    '  KEY `idx_application_id` (`application_id`) USING BTREE\n' +
    ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题-经费拨付记录';",
  "  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待拨付 15-报销审批中 20-已拨付 30-已驳回',\n" +
    "  `reimbursement_id` bigint DEFAULT NULL COMMENT '关联财务报销单 ID(finance_reimbursement.id)',\n" +
    "  `reimbursement_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '关联财务报销单号',\n" +
    "  `reject_reason` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '报销驳回原因(BPM å›žå†™ï¼‰',\n" +
    "  `reject_time` datetime DEFAULT NULL COMMENT '报销驳回时间',\n" +
    "  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',\n" +
    "  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',\n" +
    '  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT \'创建时间\',\n' +
    "  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',\n" +
    '  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT \'更新时间\',\n' +
    "  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',\n" +
    '  PRIMARY KEY (`id`) USING BTREE,\n' +
    '  KEY `idx_application_id` (`application_id`) USING BTREE,\n' +
    '  KEY `idx_reimbursement_id` (`reimbursement_id`) USING BTREE\n' +
    ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题-经费拨付记录';",
  'DDL 4 åˆ— + æ³¨é‡Š + ç´¢å¼•',
);
console.log('3) å­—å…¸');
sub(
  "('81004', '经费拨付状态', 'research_fund_status', '0', '课题经费待拨付/已拨付', '1', '2026-09-28 20:00:00', '1', '2026-09-28 20:00:00', X'00', NULL);",
  "('81004', '经费拨付状态', 'research_fund_status', '0', '课题经费待拨付/报销审批中/已拨付/已驳回', '1', '2026-09-29 10:00:00', '1', '2026-09-29 10:00:00', X'00', NULL);",
  'dict_type remark',
);
const ts = '2026-09-29 10:00:00';
sub(
  `('8100401', '1', '待拨付', '0', 'research_fund_status', '0', 'warning', '', '', '1', '2026-09-28 20:00:00', '1', '2026-09-28 20:00:00', X'00'),\n` +
    `('8100402', '2', '已拨付', '20', 'research_fund_status', '0', 'success', '', '', '1', '2026-09-28 20:00:00', '1', '2026-09-28 20:00:00', X'00');`,
  `('8100401', '1', '待拨付', '0', 'research_fund_status', '0', 'warning', '', '', '1', '${ts}', '1', '${ts}', X'00'),\n` +
    `('8100403', '2', '报销审批中', '15', 'research_fund_status', '0', 'processing', '', '', '1', '${ts}', '1', '${ts}', X'00'),\n` +
    `('8100402', '3', '已拨付', '20', 'research_fund_status', '0', 'success', '', '', '1', '${ts}', '1', '${ts}', X'00'),\n` +
    `('8100404', '4', '已驳回', '30', 'research_fund_status', '0', 'danger', '', '', '1', '${ts}', '1', '${ts}', X'00');`,
  'dict_data 4 æ¡',
);
console.log('4) BPM äº”张表');
function appendBefore(marker, lines, label) {
  const i = sql.indexOf(marker);
  if (i < 0) throw new Error(`[${label}] æœªæ‰¾åˆ°é”šç‚¹`);
  sql = sql.slice(0, i) + lines.join('\n') + '\n\n' + sql.slice(i);
  console.log(`  OK  ${label}`);
}
appendBefore(
  '-- ============ è¡¨ï¼šact_re_deployment(导出 22 è¡Œï¼‰ ============',
  procdefIns,
  'act_re_procdef',
);
appendBefore(
  '-- ============ è¡¨ï¼šact_ge_bytearray(导出 25 è¡Œï¼‰ ============',
  deployIns,
  'act_re_deployment',
);
appendBefore('-- ============ è¡¨ï¼šact_re_model(导出 22 è¡Œï¼‰ ============', byteIns, 'act_ge_bytearray');
appendBefore(
  '-- ============ è¡¨ï¼šbpm_process_definition_info(导出 22 è¡Œï¼‰ ============',
  modelIns,
  'act_re_model',
);
appendBefore(
  '-- ***********************************************************************\n-- â‘¡ ååŒï¼ˆä¸šåŠ¡é™„ä»¶é…ç½® + åˆ†ç±» + è¡¨å• + æ‰©å±•配置)',
  infoIns,
  'bpm_process_definition_info',
);
console.log('5) è¡¨å¤´è¡Œæ•°');
sub('表:act_re_procdef(导出 22 è¡Œï¼‰', '表:act_re_procdef(导出 23 è¡Œï¼‰', 'procdef è¡Œæ•°');
sub('表:act_re_deployment(导出 22 è¡Œï¼‰', '表:act_re_deployment(导出 23 è¡Œï¼‰', 'deployment è¡Œæ•°');
sub('表:act_ge_bytearray(导出 25 è¡Œï¼‰', '表:act_ge_bytearray(导出 28 è¡Œï¼‰', 'bytearray è¡Œæ•°');
sub('表:act_re_model(导出 22 è¡Œï¼‰', '表:act_re_model(导出 23 è¡Œï¼‰', 'model è¡Œæ•°');
sub(
  '表:bpm_process_definition_info(导出 22 è¡Œï¼‰',
  '表:bpm_process_definition_info(导出 23 è¡Œï¼‰',
  'procinfo è¡Œæ•°',
);
const out = crlf ? sql.replace(/\n/g, '\r\n') : sql;
fs.writeFileSync(DST, out, 'utf8');
console.log(`\n已写出 ${DST}(${sql.split('\n').length} è¡Œï¼ŒEOL=${crlf ? 'CRLF' : 'LF'})`);
docs/sql/_research_bpm_export.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
-- ===== è¯¾é¢˜ç«‹é¡¹å®¡æ‰¹ï¼ˆresearch_topic_approve)BPM æµç¨‹å®šä¹‰ï¼ˆ3 ä¸ªé¡ºåºèŠ‚ç‚¹ï¼šç§˜ä¹¦å¤„å½¢å¼å®¡æŸ¥ â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ï¼‰ =====
-- ç”Ÿæˆæ¥æºï¼šmom-jssm åº“,经 BPM æ¨¡åž‹è®¾è®¡å™¨éƒ¨ç½²ï¼ˆPOST /bpm/model/create + /bpm/model/deploy)
-- åˆ†ç±» bpm_category(id=8002, code=research_topic_approve) è§ research_module.sql
-- åˆ é™¤é¡ºåºï¼ˆå­˜åœ¨å¤–键):act_re_model â†’ act_ge_bytearray â†’ act_re_procdef â†’ act_re_deployment
DELETE FROM `act_re_model` WHERE `KEY_` = 'research_topic_approve';
DELETE FROM `act_ge_bytearray` WHERE `ID_` IN ('558289c7-bb1d-11f1-81f2-00155dd68500','575d6449-bb1d-11f1-81f2-00155dd68500','5790a94a-bb1d-11f1-81f2-00155dd68500');
DELETE FROM `act_re_procdef` WHERE `KEY_` = 'research_topic_approve';
DELETE FROM `act_re_deployment` WHERE `ID_` = '575d6448-bb1d-11f1-81f2-00155dd68500';
DELETE FROM `bpm_process_definition_info` WHERE `category` = 'research_topic_approve';
INSERT INTO `act_re_deployment` (`ID_`, `NAME_`, `CATEGORY_`, `KEY_`, `TENANT_ID_`, `DEPLOY_TIME_`, `DERIVED_FROM_`, `DERIVED_FROM_ROOT_`, `PARENT_DEPLOYMENT_ID_`, `ENGINE_VERSION_`) VALUES ('575d6448-bb1d-11f1-81f2-00155dd68500', '课题立项审批', 'research_topic_approve', 'research_topic_approve', '', '2026-09-28 17:16:51.938', NULL, NULL, NULL, NULL);
INSERT INTO `act_re_procdef` (`ID_`, `REV_`, `CATEGORY_`, `NAME_`, `KEY_`, `VERSION_`, `DEPLOYMENT_ID_`, `RESOURCE_NAME_`, `DGRM_RESOURCE_NAME_`, `DESCRIPTION_`, `HAS_START_FORM_KEY_`, `HAS_GRAPHICAL_NOTATION_`, `SUSPENSION_STATE_`, `TENANT_ID_`, `ENGINE_VERSION_`, `DERIVED_FROM_`, `DERIVED_FROM_ROOT_`, `DERIVED_VERSION_`) VALUES ('research_topic_approve:1:57953d2b-bb1d-11f1-81f2-00155dd68500', '2', 'research_topic_approve', '课题立项审批', 'research_topic_approve', '1', '575d6448-bb1d-11f1-81f2-00155dd68500', 'research_topic_approve.bpmn', 'research_topic_approve.research_topic_approve.png', NULL, '0', '1', '1', '', NULL, NULL, NULL, '0');
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('575d6449-bb1d-11f1-81f2-00155dd68500', '1', 'research_topic_approve.bpmn', '575d6448-bb1d-11f1-81f2-00155dd68500', X'3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D225554462D38223F3E0A3C646566696E6974696F6E7320786D6C6E733D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C2220786D6C6E733A7873693D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612D696E7374616E63652220786D6C6E733A7873643D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612220786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220786D6C6E733A62706D6E64693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F44492220786D6C6E733A6F6D6764633D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44432220786D6C6E733A6F6D6764693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44492220747970654C616E67756167653D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D61222065787072657373696F6E4C616E67756167653D22687474703A2F2F7777772E77332E6F72672F313939392F585061746822207461726765744E616D6573706163653D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C223E0A20203C70726F636573732069643D2272657365617263685F746F7069635F617070726F766522206E616D653D22E8AFBEE9A298E7AB8BE9A1B9E5AEA1E689B92220697345786563757461626C653D2274727565223E0A202020203C73746172744576656E742069643D2253746172744576656E7422206E616D653D22E5BC80E5A78B223E3C2F73746172744576656E743E0A202020203C757365725461736B2069643D225374617274557365724E6F646522206E616D653D22E58F91E8B5B7E4BABA223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B33365D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68F90E4BAA42220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2266616C7365222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2266616C7365222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2266616C7365222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2266616C7365222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2266616C7365222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B325D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F73656372657461726961745F72657669657722206E616D653D22E7A798E4B9A6E5A484E5BDA2E5BC8FE5AEA1E69FA5223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F6578706572745F72657669657722206E616D653D22E4B893E5AEB6E8AF84E5AEA1223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F636F756E63696C5F617070726F766522206E616D653D22E5ADA6E4BC9AE7AB8BE9A1B9E5AEA1E689B9223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C656E644576656E742069643D22456E644576656E7422206E616D653D22E7BB93E69D9F223E3C2F656E644576656E743E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D312220736F757263655265663D2253746172744576656E7422207461726765745265663D225374617274557365724E6F6465223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D322220736F757263655265663D225374617274557365724E6F646522207461726765745265663D2241637469766974795F73656372657461726961745F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D332220736F757263655265663D2241637469766974795F73656372657461726961745F72657669657722207461726765745265663D2241637469766974795F6578706572745F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D342220736F757263655265663D2241637469766974795F6578706572745F72657669657722207461726765745265663D2241637469766974795F636F756E63696C5F617070726F7665223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D352220736F757263655265663D2241637469766974795F636F756E63696C5F617070726F766522207461726765745265663D22456E644576656E74223E3C2F73657175656E6365466C6F773E0A20203C2F70726F636573733E0A20203C62706D6E64693A42504D4E4469616772616D2069643D2242504D4E4469616772616D5F72657365617263685F746F7069635F617070726F7665223E0A202020203C62706D6E64693A42504D4E506C616E652062706D6E456C656D656E743D2272657365617263685F746F7069635F617070726F7665222069643D2242504D4E506C616E655F72657365617263685F746F7069635F617070726F7665223E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2253746172744576656E74222069643D2242504D4E53686170655F53746172744576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D22302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D225374617274557365724E6F6465222069643D2242504D4E53686170655F5374617274557365724E6F6465223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D2238302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F73656372657461726961745F726576696577222069643D2242504D4E53686170655F41637469766974795F73656372657461726961745F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223233302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F6578706572745F726576696577222069643D2242504D4E53686170655F41637469766974795F6578706572745F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223338302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F636F756E63696C5F617070726F7665222069643D2242504D4E53686170655F41637469766974795F636F756E63696C5F617070726F7665223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223533302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D22456E644576656E74222069643D2242504D4E53686170655F456E644576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D223638302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D31222069643D2242504D4E456467655F73657175656E6365466C6F772D31223E0A20202020202020203C6F6D6764693A776179706F696E7420783D2233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D2238302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D32222069643D2242504D4E456467655F73657175656E6365466C6F772D32223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223138302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D33222069643D2242504D4E456467655F73657175656E6365466C6F772D33223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223333302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223338302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D34222069643D2242504D4E456467655F73657175656E6365466C6F772D34223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223438302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223533302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D35222069643D2242504D4E456467655F73657175656E6365466C6F772D35223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223633302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223638302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A202020203C2F62706D6E64693A42504D4E506C616E653E0A20203C2F62706D6E64693A42504D4E4469616772616D3E0A3C2F646566696E6974696F6E733E0A', '0');
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('5790a94a-bb1d-11f1-81f2-00155dd68500', '1', 'research_topic_approve.research_topic_approve.png', '575d6448-bb1d-11f1-81f2-00155dd68500', X'89504E470D0A1A0A0000000D49484452000002D0000000460806000000E5F7A3EB00000F0B49444154785EED9DDF6F1CD515C78720553CF01F543C41FF8F228178F0036AA5F53A1B0558EF5AB1218E9008204544B56B5E6A5521522BAAF605252A95554B019BE0242436C1049B0684D462A70921C64013B3D826218E7062273BBDC7F24897B3637BF6C799BDF7F8FB913E32B977E6CE64EFD7277787D9D92000751186E12F666666FE393939B9323A3A1A9E3E7D1AA6AC79DDCBE3E3E3D7C6C6C69EE4F3E32BC855F345AEA084C815945063AE80724CD11834A10D4BA552B8BCBC1CDEB97307A62CBDEEF4FA9F3D7BF64753485AF81CF90872D57C912B2821720525D4982BA01C7AC74DA1E56186E93B3737B7600AC7793E473E825CB92372052544AEA0849A72059443FFBB0AEFB8DD90E6C1148EDB7C8E7C04B97247E40A4A885C410935E50A2887EE3DE20186CD93E683CF918F20576E895C4109912B28A1965C01E5242D1CB76ECC85339FBC114E9FF9FD9AF4DFD4C6B783F5A9A57020576E895C2157122257C895845A72059493A4702C5DBF1A4E9D3A18FEFBDD177F26B5511FDF1ED6AE96C2815CB92572855C49885C2157126AC915504E92C2F1EDF47045D188FCDFF43B15DBC3DAD55238902BB744AE902B09912BE44A422DB902CA495238FEFBFE1F2A0A4624F5F1ED61ED6A291CC8955B2257C89584C8157225A1965C01E524291C53A77B2A0A4624F5F1ED61ED6A291CC8955B2257C89584C8157225A1965C01E5A070B8A596C2815CB92572855C49885C2157126AC915504E92C2419F36E6052392FAF8F6B076B5140EE4CA2D912BE44A42E40AB992504BAE807292148ECB1FFDB9A26044521FDF1ED6AE96C2815CB92572855C49885C2157126AC915504E92C2B178F53FE1D47BBFAB281AD4467D7C7B58BB5A0A0772E596C81572252172855C49A825574039490A07F9D5A7472B0A07B5F1ED607D6A291CC8955B2257C89584C8157225A1965C252593C93C92CD665F340EB7B5B59D377E6B0CD77F9EA776EAA7EDF8BEE28461B8637272F2B7478F1E7DAFB7B777E1E5975F5EEEEAEA2AD309767676AEEEDFBFFFA669FBB2AFAFEF8FF97CFE21BEBF6612158EDBB7C3CB137FA9281CD4467D15DBC39AD55238902BB744AE902B09912BE44A422DB9DA0CB318BEDFAC410B3B77EE9C5E5F2C27727DFB02EDCFC76C2866E1FCC0C99327FF76F0E0C19562B118BEFEFAEBE1C71F7F1C5EB972255C5C5C0C09FA497FA676EA2F140AF7BABBBB673A3A3A9EE0E36964ABC241DFB0F4C5477FAA281A91D4876F616A9C5A0A0772E596C81572252172855C49A825571B6116C02D66217C812F8EAB717D21DDC2C76E086641FC5C5F5FDFF2F3CF3F1F4E4E4E86F7EEDD5B5B306F056D47DBEFDBB76FF5D9679FFD2C9BCD3ECCC7D6C48685C3BCA39EBB3C167E7EF24045B1E0D236B42DDE85D7AF96C2815CB92572855C49885C2157126AC915A7A7A7678759F8F6F3C5F033CF3C131E3E7C381C1F1F8FBDC04BEDD44FDBF17D693C1A971FAB26CC31EF3B75EAD49B7BF6EC09474646C2BB77EFFE7C859C10DA8FF637277CCB9CE0E3FC385A882B1C5BBDDBDE48BC0BAF5F2D8503B9724BE4AA52E4AA7E91AB4A91ABFAD5922B9B4C26F360369B7DD75EFC160A85706868285C5959E14BD058683BDA9EF6B3C7A171697C7ECCAA30E3DF77ECD8B17F75757585172F5EE4C7AE091AA7582CFE94CBE50AFC781A882B1C49DE6D6F24EDCBC783C9D55238902BB744AEE245AEEA13B98A17B9AA4F2DB98AA02BC466A17BDC5EF41E3A74285C5A5AE24BCE44D07EB43FBB1A7DBCAE2BD174E5B9B3B3332C954AFC787541E3E5F3F9A5D6D6D6C7F8317D27AE70F06250AD7C3C985C2D8503B9724BE46A63F97830B9C8D5C6F2F16072B5E42AC2BE6DC3FC773838381896CB65BED4AC0ADA9FC6A1F1ACB1FBF9B113313131F11C2D9E1B75E59963C62D3FF5D453D79BF2181141E20A076C9E5A0A0772E596C8159410B982126AC9156116B62DF695625AF436121A8F5D89AEEE8385668C075E7DF5D565BA675992A1A1A1EBB95CEE243FBECFA070B8A596C2815CB92572052544AEA0845A72458F9AB39FB641B75DD47BE59943E3D9B773D0D339AA7AC4DD891327FE4A4FDBA8F5038349A1F18BC5E27C6B6BEBA3FC1C7C0585C32DB5140EE4CA2D912B2821720525D4922BB3A02D440B5BFAE05FADF73C6F058DCB3E5898EC337B66DF1DAFBCF2CA0A3D7A2E0DCE9D3B77C39CDC87FC3C7C0585C32DB5140EE4CA2D912B2821720525D4922BFB4B52868787F972B2A1D0F8F655687E2EB14C4C4CFCA6A3A323F1739EEB858EB36BD7AE1B994CE697FC5C5CC2BC88EF1B7FCDDB39281C6EE97AE140AEFC14B98212225750420DB9CAE572BF8A16B4F4FC66FA7B4942E3DBCF89A6E3F373AAE0C89123A7E91B04D3A4B7B7772A9BCD76F1737189E845DC6AA25138DCD283C2815C792872052544AEA0841A7265DAF747DBBDF6DA6B7C1929021DC73AB7FDFC9C2AE8EBEB9B4FEBF68D883367CED065F963FC5C5CC27A11379D68140EB7F4A87020571E895C4109912B28A1865C993F0F45FDF44D826940C7B1CE69C83E9F585E7AE9A5E59999193E8E28972E5D9A3527F7093F17978899E0D88946E1704B0F0B0772E581C8159410B982126AC895F9F949D49ED61A958E639DCBD66BD4CECECEF20F3FFCC0C71165717171C99CDC557E2E2E1133B1DCB58946E1704B8F0B0772E5B0C8159410B982122AC9D542F4E7B4D6A8741CEB1CB65EA3EEDEBD3B5C5D5DE5E388628E7733E605F352140EB7E4F3E3ABC8955BF2F9F155E4CA2DF9FCF82A72E5967C7E7C37AD352A1DC73AEE6DBE5EAE60CF9E3DAB69ADEE23E6E7E7BF6C4BB2BA6F227C022DDF6FC3FFBA72568FDF7923570E8B5C4109912B28A1865C999F57A3F6B4D6A8555F817EE185176EA6757F49C4850B173E6D4B727F4913D96C626D5C2F1CE614636D54BF6B7A58389A92AB20664E492DFD8D16B9923188994332ADFE668B5CD5661033A7A42FFDD26AC8559B0FF7401F3870E08BB49FC2F1D65B6F0DB7F9F3148E8A89B549BB70546B10F34B6BB7D5DBEF9A1E158EA6E62A889943BBCDF7FE468B5CC918C4CC99DD26DDDF6C91ABDA0C62E6D06E73BD5F5A0DB96AF3E1291CBDBDBD7F4CFB39D07BF7EE1DF1E039D01B4EAC4DDA85A35A83985F5ABBADDE7ED7F4A0703891AB20660EED36DFFB1B2D722563103367769B747FB345AE6A33889943BBCDF57E6935E4AACD87E740E7F3F9878AC5E2BD14BF89F03B7362F3AE7F136152D22E1CD51AC4FCD2DA6DF5F6BBA6EB852329D2B90A62E6D06EF3BDBFD122573206317366B749F7375BE4AA36839839B4DB5CEF975643AE9AF14D844F3FFD74393A66A26F2224BABBBBBF4AEB368E818181BF9B93FB909F83AFA45D38AA3588F9A5B5DBEAED774D0D858390CE55103387769BEFFD8D16B99231889933BB4DBABFD92257B519C4CCA1DDE67ABFB45A72B573E74EFAD2BDB505EDF0F0305F4E36141A3F3A161D979FCB867474743CB16FDFBED5BB77EFF2311B8A19FF5B7372975B5B5B1FE5E7E02B69178E6A0D627E69EDB67AFB5D534BE190CE55103387769BEFFD8D16B99231889933BB4DBABFD92257B519C4CCA1DDE67ABFB45A7265D68B8568515B2814C2A5A525BEAC6C08346E7B7BFBBDE858745C7E2E9BB277EFDECF464646CA7CE00652EEEFEF7FC39CD8083FB6CFA45D38AA3588F9A5B5DBEAED774D2D85433A5741CC1CDA6DBEF7375AE44AC62066CEEC36E9FE668B5CD566103387769BEBFDD26AC9552693B9DFBE0A7DE8D0A1B05C6EEC3295C6A371EDABCF745C7E2E9B627678249FCF2F5DBC78918FDF103EF8E08323E6E4E6E838FCD83E9376E1A856738AB136AADF35B5140EE95C0531734A6AE96FB4C8958C41CC1C9269F5375BE4AA36839839257DE997564BAE08B36E6CB1AE0C878383837C795917349E3D3E1D8F9F4322CCCAFBF1F6F6F69F4AA5123F465DCCCECEBE634EEA5A6B6BEB63FC98BE9376E1809BABA57020576E895C4109912B28A1965C4598B569BF7585786DD15BEF9568DA9FC6A1F1ACB1FBF9B1AB2297CB15F2F9FCCD465D895EBFF24CDF28D3CE8FA501140EB7D45238902BB744AEA084C81594504BAE227A7A7A769835E471FB4A31DD7651EB3DD1B49F7DDBC6BAC7E938FCD85543578A77EFDE7D7D6868E87AAD1F2CA40F0CAEDFF3FC9DC62BCF11281C6EA9A57020576E895C4109912B28A1965CD964329907B3D9ECBBF6A2973E5868D6A9E1CACA0A5F82C642DBD1F6EC0383218D4BE3F363D68C19F0E15C2E77AA582CCE9F3B77EE46D2E7449BED4A0303036F9A93BA6C3C41E3F0B13581C2E1965A0A0772E596C8159410B982126AC91587AE10DBB77344D273A20F1F3EBCF64D8257AE5C09171717D7D6A3F493FE4CEDD46F3FE73992C66BC895E738E89173E620E3BB76EDFAD11CE4F3D1D1D1A94B972E7DB3B0B070CB9CDFD2F7DF7F3F333D3DFDE9DB6FBF3DD4DDDD7DC26CBB40DB6B7A54DD66A070B8A596C2E153AE6EDDBA154E4D4D552DEDC7C77255E42A7D912B7F40AEDC524BAE36C2AC315BECA773D4E2FAFEB57D60B05AE89B03CDC13AB3D9ECB0F979DE48CF74A613A19FE7D7DB3BB57CC360527C2A1CDB412D85C3A75CD13F2E41CC27CEB792F6E363B92A7295BEC8953F20576EA925579B418F9A336BCE42B50BE9F5ED0B553FAA0E341E9F0AC776504BE1F02957B8A2E30FC8955B2257E98B5CE9831E8F9CCD665FDCEC022FF56B7B8CB2F7F85438B6835A0A0772E596C8159410B982126AC915500E0A875B6A291CC8955B22575042E40A4AA825574039281C6EA9A57020576E895C4109912B28A1965C01E5A070B8A596C2E153AE704FA13F20576E895CA52F72058023F85438B6835A0A874FB9A27F5C82984FAD6F25EDC7C77255E42A7D912B7F40AEDC524BAE80727C2A1CDB412D85C3A75CE18A8E3F20576E895CA52F72058023F85438B6835A0A0772E596C8159410B982126AC915500E0A875B6A291CC8955B22575042E40A4AA825574039281C6EA9A57020576E895C4109912B28A1965C01E5A070B8A596C2E153AE704FA13F20576E895CA52F720580238C8E8E969797972B020CD3D7CCC33553386EF339F2119F7245FFB804319F5ADF4ADA8F8FE5A2C8557344AEFC01B972474DB902CA191F1FBF562A952A420CD3F7EBAFBF1E3085E33C9F231FF12957DAAFE82057CD11B9F207E4CA1D35E50A28676C6CECC9B367CFFE383737B7E0CB3B706D9AD77D6E7676F61FA6687C636CE173E423C855F345AEA084C815945063AEC03680C24AEFF88C778C6BF71FC154A5D79D5E7F554583FE3EEB7F2FE4AA3922575042E40A4AA8325700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036E1FF32BDF83D641CD66B0000000049454E44AE426082', '1');
INSERT INTO `act_ge_bytearray` (`ID_`, `REV_`, `NAME_`, `DEPLOYMENT_ID_`, `BYTES_`, `GENERATED_`) VALUES ('558289c7-bb1d-11f1-81f2-00155dd68500', '1', 'source', NULL, X'3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D225554462D38223F3E0A3C646566696E6974696F6E7320786D6C6E733D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C2220786D6C6E733A7873693D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612D696E7374616E63652220786D6C6E733A7873643D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D612220786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220786D6C6E733A62706D6E64693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F44492220786D6C6E733A6F6D6764633D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44432220786D6C6E733A6F6D6764693D22687474703A2F2F7777772E6F6D672E6F72672F737065632F44442F32303130303532342F44492220747970654C616E67756167653D22687474703A2F2F7777772E77332E6F72672F323030312F584D4C536368656D61222065787072657373696F6E4C616E67756167653D22687474703A2F2F7777772E77332E6F72672F313939392F585061746822207461726765744E616D6573706163653D22687474703A2F2F7777772E6F6D672E6F72672F737065632F42504D4E2F32303130303532342F4D4F44454C223E0A20203C70726F636573732069643D2272657365617263685F746F7069635F617070726F766522206E616D653D22E8AFBEE9A298E7AB8BE9A1B9E5AEA1E689B92220697345786563757461626C653D2274727565223E0A202020203C73746172744576656E742069643D2253746172744576656E7422206E616D653D22E5BC80E5A78B223E3C2F73746172744576656E743E0A202020203C757365725461736B2069643D225374617274557365724E6F646522206E616D653D22E58F91E8B5B7E4BABA223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B33365D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68F90E4BAA42220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2266616C7365222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2266616C7365222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2266616C7365222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2266616C7365222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2266616C7365222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B325D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F73656372657461726961745F72657669657722206E616D653D22E7A798E4B9A6E5A484E5BDA2E5BC8FE5AEA1E69FA5223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F6578706572745F72657669657722206E616D653D22E4B893E5AEB6E8AF84E5AEA1223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C757365725461736B2069643D2241637469766974795F636F756E63696C5F617070726F766522206E616D653D22E5ADA6E4BC9AE7AB8BE9A1B9E5AEA1E689B9223E0A2020202020203C657874656E73696F6E456C656D656E74733E0A20202020202020203C666C6F7761626C653A617070726F76655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A617070726F7665547970653E0A20202020202020203C666C6F7761626C653A63616E646964617465537472617465677920786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31305D5D3E3C2F666C6F7761626C653A63616E64696461746553747261746567793E0A20202020202020203C666C6F7761626C653A63616E646964617465506172616D20786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A63616E646964617465506172616D3E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E9809AE8BF872220656E61626C653D2274727565222069643D2231223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E68B92E7BB9D2220656E61626C653D2274727565222069643D2232223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E8BDACE58A9E2220656E61626C653D2274727565222069643D2233223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E5A794E6B4BE2220656E61626C653D2274727565222069643D2234223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E58AA0E7ADBE2220656E61626C653D2274727565222069643D2235223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A627574746F6E7353657474696E6720786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E2220646973706C61794E616D653D22E98080E59B9E2220656E61626C653D2274727565222069643D2236223E3C2F666C6F7761626C653A627574746F6E7353657474696E673E0A20202020202020203C666C6F7761626C653A617070726F76654D6574686F6420786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B345D5D3E3C2F666C6F7761626C653A617070726F76654D6574686F643E0A20202020202020203C666C6F7761626C653A72656A65637448616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A72656A65637448616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E53746172745573657248616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E53746172745573657248616E646C6572547970653E0A20202020202020203C666C6F7761626C653A61737369676E456D70747948616E646C65725479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B315D5D3E3C2F666C6F7761626C653A61737369676E456D70747948616E646C6572547970653E0A20202020202020203C666C6F7761626C653A7369676E456E61626C6520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A7369676E456E61626C653E0A20202020202020203C666C6F7761626C653A726561736F6E5265717569726520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B66616C73655D5D3E3C2F666C6F7761626C653A726561736F6E526571756972653E0A20202020202020203C666C6F7761626C653A6E6F64655479706520786D6C6E733A666C6F7761626C653D22687474703A2F2F666C6F7761626C652E6F72672F62706D6E223E3C215B43444154415B31315D5D3E3C2F666C6F7761626C653A6E6F6465547970653E0A2020202020203C2F657874656E73696F6E456C656D656E74733E0A2020202020203C6D756C7469496E7374616E63654C6F6F7043686172616374657269737469637320697353657175656E7469616C3D22747275652220666C6F7761626C653A636F6C6C656374696F6E3D22247B636F6C6C5F757365724C6973747D223E0A20202020202020203C6C6F6F7043617264696E616C6974793E313C2F6C6F6F7043617264696E616C6974793E0A20202020202020203C636F6D706C6574696F6E436F6E646974696F6E3E247B206E724F66436F6D706C65746564496E7374616E636573202667743B3D206E724F66496E7374616E636573207D3C2F636F6D706C6574696F6E436F6E646974696F6E3E0A2020202020203C2F6D756C7469496E7374616E63654C6F6F704368617261637465726973746963733E0A202020203C2F757365725461736B3E0A202020203C656E644576656E742069643D22456E644576656E7422206E616D653D22E7BB93E69D9F223E3C2F656E644576656E743E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D312220736F757263655265663D2253746172744576656E7422207461726765745265663D225374617274557365724E6F6465223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D322220736F757263655265663D225374617274557365724E6F646522207461726765745265663D2241637469766974795F73656372657461726961745F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D332220736F757263655265663D2241637469766974795F73656372657461726961745F72657669657722207461726765745265663D2241637469766974795F6578706572745F726576696577223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D342220736F757263655265663D2241637469766974795F6578706572745F72657669657722207461726765745265663D2241637469766974795F636F756E63696C5F617070726F7665223E3C2F73657175656E6365466C6F773E0A202020203C73657175656E6365466C6F772069643D2273657175656E6365466C6F772D352220736F757263655265663D2241637469766974795F636F756E63696C5F617070726F766522207461726765745265663D22456E644576656E74223E3C2F73657175656E6365466C6F773E0A20203C2F70726F636573733E0A20203C62706D6E64693A42504D4E4469616772616D2069643D2242504D4E4469616772616D5F72657365617263685F746F7069635F617070726F7665223E0A202020203C62706D6E64693A42504D4E506C616E652062706D6E456C656D656E743D2272657365617263685F746F7069635F617070726F7665222069643D2242504D4E506C616E655F72657365617263685F746F7069635F617070726F7665223E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2253746172744576656E74222069643D2242504D4E53686170655F53746172744576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D22302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D225374617274557365724E6F6465222069643D2242504D4E53686170655F5374617274557365724E6F6465223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D2238302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F73656372657461726961745F726576696577222069643D2242504D4E53686170655F41637469766974795F73656372657461726961745F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223233302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F6578706572745F726576696577222069643D2242504D4E53686170655F41637469766974795F6578706572745F726576696577223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223338302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D2241637469766974795F636F756E63696C5F617070726F7665222069643D2242504D4E53686170655F41637469766974795F636F756E63696C5F617070726F7665223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2236302E30222077696474683D223130302E302220783D223533302E302220793D22302E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E53686170652062706D6E456C656D656E743D22456E644576656E74222069643D2242504D4E53686170655F456E644576656E74223E0A20202020202020203C6F6D6764633A426F756E6473206865696768743D2233302E30222077696474683D2233302E302220783D223638302E302220793D2231352E30223E3C2F6F6D6764633A426F756E64733E0A2020202020203C2F62706D6E64693A42504D4E53686170653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D31222069643D2242504D4E456467655F73657175656E6365466C6F772D31223E0A20202020202020203C6F6D6764693A776179706F696E7420783D2233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D2238302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D32222069643D2242504D4E456467655F73657175656E6365466C6F772D32223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223138302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223233302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D33222069643D2242504D4E456467655F73657175656E6365466C6F772D33223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223333302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223338302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D34222069643D2242504D4E456467655F73657175656E6365466C6F772D34223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223438302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223533302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A2020202020203C62706D6E64693A42504D4E456467652062706D6E456C656D656E743D2273657175656E6365466C6F772D35222069643D2242504D4E456467655F73657175656E6365466C6F772D35223E0A20202020202020203C6F6D6764693A776179706F696E7420783D223633302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A20202020202020203C6F6D6764693A776179706F696E7420783D223638302E302220793D2233302E30223E3C2F6F6D6764693A776179706F696E743E0A2020202020203C2F62706D6E64693A42504D4E456467653E0A202020203C2F62706D6E64693A42504D4E506C616E653E0A20203C2F62706D6E64693A42504D4E4469616772616D3E0A3C2F646566696E6974696F6E733E0A', NULL);
INSERT INTO `act_re_model` (`ID_`, `REV_`, `NAME_`, `KEY_`, `CATEGORY_`, `CREATE_TIME_`, `LAST_UPDATE_TIME_`, `VERSION_`, `META_INFO_`, `DEPLOYMENT_ID_`, `EDITOR_SOURCE_VALUE_ID_`, `EDITOR_SOURCE_EXTRA_VALUE_ID_`, `TENANT_ID_`) VALUES ('557c2126-bb1d-11f1-81f2-00155dd68500','3','课题立项审批','research_topic_approve','research_topic_approve','2026-09-28 17:16:48.781','2026-09-28 17:16:52.482','1','{"allowCancelRunningProcess":null,"allowWithdrawTask":null,"autoApprovalType":null,"description":"课题申报三级审批(秘书处形式审查→专家评审→学会立项审批)","formCustomCreatePath":"/research/application/index","formCustomViewPath":"/research/application/detail/approve/index.vue","formId":null,"formType":20,"icon":null,"managerUserIds":[1],"printTemplateSetting":null,"processAfterTriggerSetting":null,"processBeforeTriggerSetting":null,"processIdRule":null,"sort":1790587008761,"startDeptIds":null,"startUserIds":null,"summarySetting":null,"taskAfterTriggerSetting":null,"taskBeforeTriggerSetting":null,"titleSetting":null,"type":10,"visible":true}','575d6448-bb1d-11f1-81f2-00155dd68500','558289c7-bb1d-11f1-81f2-00155dd68500',NULL,'');
INSERT INTO `bpm_process_definition_info` (`id`, `process_definition_id`, `model_id`, `model_type`, `category`, `icon`, `description`, `form_type`, `form_id`, `form_conf`, `form_fields`, `form_custom_create_path`, `form_custom_view_path`, `simple_model`, `visible`, `sort`, `start_user_ids`, `start_dept_ids`, `manager_user_ids`, `allow_cancel_running_process`, `allow_withdraw_task`, `process_id_rule`, `auto_approval_type`, `title_setting`, `summary_setting`, `process_before_trigger_setting`, `process_after_trigger_setting`, `task_before_trigger_setting`, `task_after_trigger_setting`, `print_template_setting`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('41', 'research_topic_approve:1:57953d2b-bb1d-11f1-81f2-00155dd68500', '557c2126-bb1d-11f1-81f2-00155dd68500', '10', 'research_topic_approve', NULL, '课题申报三级审批(秘书处形式审查→专家评审→学会立项审批)', '20', NULL, NULL, NULL, '/research/application/index', '/research/application/detail/approve/index.vue', NULL, X'01', '1790587008761', '', NULL, '1', X'01', X'01', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '1', '2026-09-28 17:16:52', '1', '2026-09-28 17:16:52', X'00');
docs/sql/_ums_membership_approve.bpmn
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="ums_membership_approve" name="入会审批" isExecutable="true">
    <startEvent id="StartEvent" name="开始"></startEvent>
    <userTask id="StartUserNode" name="发起人">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[36]]></flowable:candidateStrategy>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="提交" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="false" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="false" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="false" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="false" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="false" id="6"></flowable:buttonsSetting>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[2]]></flowable:assignStartUserHandlerType>
      </extensionElements>
    </userTask>
    <userTask id="Activity_secretariat_audit" name="秘书处初审">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <userTask id="Activity_council_review" name="理事会审议">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <endEvent id="EndEvent" name="结束"></endEvent>
    <sequenceFlow id="sequenceFlow-1" sourceRef="StartEvent" targetRef="StartUserNode"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-2" sourceRef="StartUserNode" targetRef="Activity_secretariat_audit"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-3" sourceRef="Activity_secretariat_audit" targetRef="Activity_council_review"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-4" sourceRef="Activity_council_review" targetRef="EndEvent"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_ums_membership_approve">
    <bpmndi:BPMNPlane bpmnElement="ums_membership_approve" id="BPMNPlane_ums_membership_approve">
      <bpmndi:BPMNShape bpmnElement="StartEvent" id="BPMNShape_StartEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="StartUserNode" id="BPMNShape_StartUserNode">
        <omgdc:Bounds height="60.0" width="100.0" x="80.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_secretariat_audit" id="BPMNShape_Activity_secretariat_audit">
        <omgdc:Bounds height="60.0" width="100.0" x="230.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_council_review" id="BPMNShape_Activity_council_review">
        <omgdc:Bounds height="60.0" width="100.0" x="380.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="EndEvent" id="BPMNShape_EndEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="530.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-1" id="BPMNEdge_sequenceFlow-1">
        <omgdi:waypoint x="30.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="80.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-2" id="BPMNEdge_sequenceFlow-2">
        <omgdi:waypoint x="180.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-3" id="BPMNEdge_sequenceFlow-3">
        <omgdi:waypoint x="330.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-4" id="BPMNEdge_sequenceFlow-4">
        <omgdi:waypoint x="480.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="530.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
docs/sql/archive_borrow_approve.bpmn
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="archive_borrow_approve" name="档案借阅审批" isExecutable="true">
    <startEvent id="StartEvent" name="开始"></startEvent>
    <userTask id="StartUserNode" name="发起人">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[36]]></flowable:candidateStrategy>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="提交" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="false" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="false" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="false" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="false" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="false" id="6"></flowable:buttonsSetting>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[2]]></flowable:assignStartUserHandlerType>
      </extensionElements>
    </userTask>
    <userTask id="Activity_archive_admin_review" name="档案管理员审核">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <userTask id="Activity_leader_approve" name="分管领导审批">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <endEvent id="EndEvent" name="结束"></endEvent>
    <sequenceFlow id="sequenceFlow-1" sourceRef="StartEvent" targetRef="StartUserNode"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-2" sourceRef="StartUserNode" targetRef="Activity_archive_admin_review"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-3" sourceRef="Activity_archive_admin_review" targetRef="Activity_leader_approve"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-4" sourceRef="Activity_leader_approve" targetRef="EndEvent"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_archive_borrow_approve">
    <bpmndi:BPMNPlane bpmnElement="archive_borrow_approve" id="BPMNPlane_archive_borrow_approve">
      <bpmndi:BPMNShape bpmnElement="StartEvent" id="BPMNShape_StartEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="StartUserNode" id="BPMNShape_StartUserNode">
        <omgdc:Bounds height="60.0" width="100.0" x="80.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_archive_admin_review" id="BPMNShape_Activity_archive_admin_review">
        <omgdc:Bounds height="60.0" width="100.0" x="230.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_leader_approve" id="BPMNShape_Activity_leader_approve">
        <omgdc:Bounds height="60.0" width="100.0" x="380.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="EndEvent" id="BPMNShape_EndEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="530.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-1" id="BPMNEdge_sequenceFlow-1">
        <omgdi:waypoint x="30.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="80.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-2" id="BPMNEdge_sequenceFlow-2">
        <omgdi:waypoint x="180.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-3" id="BPMNEdge_sequenceFlow-3">
        <omgdi:waypoint x="330.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-4" id="BPMNEdge_sequenceFlow-4">
        <omgdi:waypoint x="480.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="530.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
docs/sql/archive_module.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,273 @@
-- =========================================
-- æ¡£æ¡ˆç®¡ç†æ¨¡å—(yudao-module-archive)一期
-- å»ºè¡¨ + å­—典数据 + BPM åˆ†ç±» + èœå• + ä¸‰æ–¹å½’档按钮权限
-- è¦†ç›–需求:1.书架(分类管理) 2.文档管理 3.借阅登记 4.归还登记 5.档案统计 + è¯¾é¢˜/会员/财务归档联动
-- äºŒæœŸï¼ˆæ¡£æ¡ˆé”€æ¯ã€é€¾æœŸç½šæ¬¾ï¼‰ä¸åœ¨æ­¤è„šæœ¬å†…
-- =========================================
-- ----- 1. æ¡£æ¡ˆåˆ†ç±»è¡¨ï¼ˆä¹¦æž¶/分类树,分层) -----
DROP TABLE IF EXISTS `archive_category`;
CREATE TABLE `archive_category` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父分类 ID,0=顶级',
  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
  `code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类编码,唯一(member/research/meeting/finance/document/party)',
  `sort` int NOT NULL DEFAULT '0' COMMENT '显示排序',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '分类说明',
  `default_secret_level` tinyint NOT NULL DEFAULT '1' COMMENT '该分类下档案默认密级:1-公开 2-内部 3-保密',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-启用 1-停用',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_code` (`code`) USING BTREE,
  KEY `idx_parent_id` (`parent_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='档案-分类';
-- ----- 2. æ¡£æ¡ˆä¸»è¡¨ -----
DROP TABLE IF EXISTS `archive_document`;
CREATE TABLE `archive_document` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '档案编号 DA+yyyyMMdd+6位流水',
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '档案标题',
  `category_id` bigint NOT NULL COMMENT '档案分类 ID',
  `secret_level` tinyint NOT NULL DEFAULT '1' COMMENT '密级:1-公开 2-内部 3-保密',
  `carrier_type` tinyint NOT NULL DEFAULT '3' COMMENT '载体:1-电子 2-实体 3-电子+实体',
  `shelf_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '实体-书架号',
  `layer_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '实体-层号',
  `column_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '实体-列号',
  `box_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '实体-册号/档案盒号',
  `store_place` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '实体存放位置描述(如 3号库房A架2层)',
  `source_type` tinyint NOT NULL DEFAULT '1' COMMENT '来源:1-手工 2-课题 3-会员 4-财务',
  `source_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '来源单号(立项编号/会员编号/报销单号)',
  `source_id` bigint DEFAULT NULL COMMENT '来源单据 ID',
  `source_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '来源单据标题(冗余快照,便于检索)',
  `archive_date` date DEFAULT NULL COMMENT '归档日期',
  `archivist_user_id` bigint DEFAULT NULL COMMENT '归档人用户 ID',
  `status` tinyint NOT NULL DEFAULT '10' COMMENT '档案状态:10-在库 20-借出中',
  `summary` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '摘要',
  `tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '关键词,英文逗号分隔(检索用)',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_category_id` (`category_id`) USING BTREE,
  KEY `idx_secret_level` (`secret_level`) USING BTREE,
  KEY `idx_status` (`status`) USING BTREE,
  KEY `idx_source` (`source_type`,`source_id`) USING BTREE,
  KEY `idx_archive_date` (`archive_date`) USING BTREE,
  KEY `idx_title` (`title`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='档案-档案主表';
-- ----- 3. å€Ÿé˜…单表(借阅 + å½’还合一) -----
DROP TABLE IF EXISTS `archive_borrow`;
CREATE TABLE `archive_borrow` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '借阅单号 JY+yyyyMMdd+6位流水',
  `document_id` bigint NOT NULL COMMENT '档案 ID',
  `document_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '档案编号(快照)',
  `document_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '档案标题(快照)',
  `borrower_user_id` bigint DEFAULT NULL COMMENT '借阅人用户 ID',
  `borrower_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '借阅人姓名',
  `borrower_dept` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '借阅人部门',
  `borrower_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '借阅人联系电话',
  `borrow_date` date NOT NULL COMMENT '借阅日期',
  `borrow_purpose` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '借阅目的',
  `return_deadline` date NOT NULL COMMENT '归还期限(逾期实时计算基准)',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-草稿 15-审批中 20-借出中 30-已归还 35-已驳回',
  `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT 'BPM æµç¨‹å®žä¾‹ ID',
  `approve_time` datetime DEFAULT NULL COMMENT '审批通过(实际借出)时间',
  `return_time` datetime DEFAULT NULL COMMENT '实际归还时间',
  `return_confirm_user_id` bigint DEFAULT NULL COMMENT '归还确认人用户 ID',
  `return_remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '归还备注',
  `reject_reason` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '驳回原因(BPM å›žå†™ï¼‰',
  `reject_time` datetime DEFAULT NULL COMMENT '驳回时间',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_document_id` (`document_id`) USING BTREE,
  KEY `idx_status` (`status`) USING BTREE,
  KEY `idx_return_deadline` (`return_deadline`) USING BTREE,
  KEY `idx_borrower_user_id` (`borrower_user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='档案-借阅单';
-- ----- 4. åˆå§‹åˆ†ç±»ï¼ˆ6 æ¡ï¼Œè´¢åŠ¡åˆ†ç±»é»˜è®¤å¯†çº§=保密) -----
DELETE FROM `archive_category` WHERE `code` IN ('member','research','meeting','finance','document','party');
INSERT INTO `archive_category` (`id`,`parent_id`,`name`,`code`,`sort`,`description`,`default_secret_level`,`status`,`remark`,`creator`,`create_time`,`updater`,`update_time`,`deleted`)
VALUES (1, 0, '会员档案', 'member', 1, '会员入会材料、资质证明等', 2, 0, '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO `archive_category` (`id`,`parent_id`,`name`,`code`,`sort`,`description`,`default_secret_level`,`status`,`remark`,`creator`,`create_time`,`updater`,`update_time`,`deleted`)
VALUES (2, 0, '课题档案', 'research', 2, '课题申报书、立项批复、结项材料等', 2, 0, '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO `archive_category` (`id`,`parent_id`,`name`,`code`,`sort`,`description`,`default_secret_level`,`status`,`remark`,`creator`,`create_time`,`updater`,`update_time`,`deleted`)
VALUES (3, 0, '会议档案', 'meeting', 3, '学会文件、会议纪要等', 2, 0, '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO `archive_category` (`id`,`parent_id`,`name`,`code`,`sort`,`description`,`default_secret_level`,`status`,`remark`,`creator`,`create_time`,`updater`,`update_time`,`deleted`)
VALUES (4, 0, '财务档案', 'finance', 4, '报销凭证、发票等财务单据', 3, 0, '财务档案默认密级为「保密」,需 archive:document:sensitive æƒé™æŸ¥çœ‹', '1', NOW(), '1', NOW(), b'0');
INSERT INTO `archive_category` (`id`,`parent_id`,`name`,`code`,`sort`,`description`,`default_secret_level`,`status`,`remark`,`creator`,`create_time`,`updater`,`update_time`,`deleted`)
VALUES (5, 0, '文书档案', 'document', 5, '调研报告、学术论文、合同协议等', 2, 0, '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO `archive_category` (`id`,`parent_id`,`name`,`code`,`sort`,`description`,`default_secret_level`,`status`,`remark`,`creator`,`create_time`,`updater`,`update_time`,`deleted`)
VALUES (6, 0, '党建档案', 'party', 6, '党建活动、组织建设等材料', 2, 0, '', '1', NOW(), '1', NOW(), b'0');
-- ----- 5. å­—典数据 -----
-- archive_category_status(档案分类状态)
DELETE FROM system_dict_type WHERE `type` = 'archive_category_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (82000, '档案分类状态', 'archive_category_status', 0, '档案分类启用/停用', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'archive_category_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200001, 1, '启用', '0', 'archive_category_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200002, 2, '停用', '1', 'archive_category_status', 0, 'info', '', '1', NOW(), '1', NOW(), b'0');
-- archive_document_status(档案状态)
DELETE FROM system_dict_type WHERE `type` = 'archive_document_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (82001, '档案状态', 'archive_document_status', 0, '档案在库/借出中', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'archive_document_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200101, 1, '在库', '10', 'archive_document_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200102, 2, '借出中', '20', 'archive_document_status', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
-- archive_secret_level(档案密级)
DELETE FROM system_dict_type WHERE `type` = 'archive_secret_level';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (82002, '档案密级', 'archive_secret_level', 0, '公开/内部/保密,保密级需 archive:document:sensitive æƒé™', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'archive_secret_level';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200201, 1, '公开', '1', 'archive_secret_level', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200202, 2, '内部', '2', 'archive_secret_level', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200203, 3, '保密', '3', 'archive_secret_level', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- archive_carrier_type(档案载体类型)
DELETE FROM system_dict_type WHERE `type` = 'archive_carrier_type';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (82003, '档案载体', 'archive_carrier_type', 0, '电子/实体/电子+实体', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'archive_carrier_type';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200301, 1, '电子', '1', 'archive_carrier_type', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200302, 2, '实体', '2', 'archive_carrier_type', 0, 'info', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200303, 3, '电子+实体', '3', 'archive_carrier_type', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
-- archive_source_type(档案来源类型)
DELETE FROM system_dict_type WHERE `type` = 'archive_source_type';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (82004, '档案来源', 'archive_source_type', 0, '手工录入/课题归档/会员归档/财务归档', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'archive_source_type';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200401, 1, '手工录入', '1', 'archive_source_type', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200402, 2, '课题归档', '2', 'archive_source_type', 0, 'primary', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200403, 3, '会员归档', '3', 'archive_source_type', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200404, 4, '财务归档', '4', 'archive_source_type', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- archive_borrow_status(借阅单状态)
DELETE FROM system_dict_type WHERE `type` = 'archive_borrow_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (82005, '档案借阅状态', 'archive_borrow_status', 0, '借阅单审批与归还状态', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'archive_borrow_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200501, 1, '草稿', '0', 'archive_borrow_status', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200502, 2, '审批中', '15', 'archive_borrow_status', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200503, 3, '借出中', '20', 'archive_borrow_status', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200504, 4, '已归还', '30', 'archive_borrow_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8200505, 5, '已驳回', '35', 'archive_borrow_status', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- ----- 6. BPM åˆ†ç±»ï¼ˆæ¡£æ¡ˆå€Ÿé˜…审批,两级审批) -----
-- æ³¨æ„ï¼šéœ€å…ˆéƒ¨ç½² docs/sql/archive_borrow_approve.bpmn(见该文件头部的部署顺序说明)
DELETE FROM bpm_category WHERE `code` = 'archive_borrow_approve';
INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
VALUES (8003, '档案借阅审批', 'archive_borrow_approve', '档案借阅两级审批(档案管理员审核→分管领导审批)', 0, 3, '1', NOW(), '1', NOW(), b'0');
-- ----- 7. èœå• -----
-- æ³¨æ„ï¼šèœå• ID ä¸è¦ä¸ŽçŽ°æœ‰èœå•å†²çªï¼ˆæ¡£æ¡ˆç®¡ç†å ç”¨ 1075600~1075699,现有 MAX=1075553)
DELETE FROM system_menu WHERE id BETWEEN 1075600 AND 1075699;
-- é¡¶çº§ç›®å½•(type=1),parent_id=0,图标 ep:files
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075600, '档案管理', '', 1, 82, 0, '/archive', 'ep:files', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- æ¡£æ¡ˆåˆ†ç±»ï¼ˆèœå•,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075601, '档案分类', '', 2, 1, 1075600, 'category', 'ep:collection', 'archive/category/index', 'ArchiveCategory', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- æ¡£æ¡ˆç®¡ç†ï¼ˆèœå•,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075602, '档案管理', '', 2, 2, 1075600, 'document', 'ep:folder-opened', 'archive/document/index', 'ArchiveDocument', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- å€Ÿé˜…登记(菜单,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075603, '借阅登记', '', 2, 3, 1075600, 'borrow', 'ep:document-copy', 'archive/borrow/index', 'ArchiveBorrow', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- æ¡£æ¡ˆç»Ÿè®¡ï¼ˆèœå•,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075604, '档案统计', '', 2, 4, 1075600, 'stat', 'ep:data-analysis', 'archive/stat/index', 'ArchiveStat', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ï¼ˆtype=3)—— æ¡£æ¡ˆåˆ†ç±»
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075610, '档案分类查询', 'archive:category:query', 3, 1, 1075601, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075611, '档案分类创建', 'archive:category:create', 3, 2, 1075601, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075612, '档案分类更新', 'archive:category:update', 3, 3, 1075601, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075613, '档案分类删除', 'archive:category:delete', 3, 4, 1075601, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— æ¡£æ¡ˆç®¡ç†
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075620, '档案查询', 'archive:document:query', 3, 1, 1075602, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075621, '档案创建', 'archive:document:create', 3, 2, 1075602, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075622, '档案更新', 'archive:document:update', 3, 3, 1075602, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075623, '档案删除', 'archive:document:delete', 3, 4, 1075602, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075624, '档案导出', 'archive:document:export', 3, 5, 1075602, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075625, '保密档案查看', 'archive:document:sensitive', 3, 6, 1075602, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— å€Ÿé˜…登记
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075630, '借阅查询', 'archive:borrow:query', 3, 1, 1075603, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075631, '借阅登记', 'archive:borrow:create', 3, 2, 1075603, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075632, '借阅修改', 'archive:borrow:update', 3, 3, 1075603, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075633, '借阅删除', 'archive:borrow:delete', 3, 4, 1075603, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075634, '借阅提交审批', 'archive:borrow:submit', 3, 5, 1075603, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075635, '归还登记', 'archive:borrow:return', 3, 6, 1075603, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— æ¡£æ¡ˆç»Ÿè®¡
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075640, '档案统计查询', 'archive:stat:query', 3, 1, 1075604, '1', NOW(), '1', NOW(), b'0');
-- ----- 8. ä¸‰æ–¹æ¨¡å—归档按钮权限(配合归档联动) -----
-- è¯¾é¢˜ç”³æŠ¥ï¼ˆçˆ¶èœå• 1075512)、会员列表(父菜单 1075492);财务归档复用既有 finance:reimbursement:archive(1075424)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075536, '课题申报归档', 'research:application:archive', 3, 7, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075504, '会员归档', 'ums:member:archive', 3, 4, 1075492, '1', NOW(), '1', NOW(), b'0');
docs/sql/archive_module_migration.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
-- =====================================================
-- æ¡£æ¡ˆç®¡ç†æ¨¡å— - æ¥æºæ¨¡å—增量脚本(跨模块改表)
--
-- èƒŒæ™¯ï¼šæ¡£æ¡ˆæ¨¡å—支持「课题申报书 / ä¼šå‘˜ / è´¢åŠ¡æŠ¥é”€å•ã€æŒ‰æ¥æºå•æ®æ‰‹åŠ¨å½’æ¡£ã€‚
--       ä¸‰å¼ æ¥æºè¡¨å„加一列 `archive_document_id`(指向 archive_document.id):
--       â‘  ä½œä¸ºã€Œå·²å½’档」的硬标记,列表页据此显示已归档并跳转档案;
--       â‘¡ å½’档重复校验的兜底(archive_document ä¾§å¦æœ‰ source_type+source_id å”¯ä¸€æ€§æ ¡éªŒï¼‰ã€‚
--       åˆ—可空,不影响既有业务数据。
--
-- æ‰§è¡Œåº“:脚本用 DATABASE() åˆ¤å®šï¼Œä¸Žåº“名无关,故可对任意目标库执行。
--         2026-09-29 å·²åœ¨ application-local.yaml æŒ‡å‘çš„ mom-jssm æ‰§è¡Œå¹¶æ ¸å¯¹é€šè¿‡ï¼›
--         å…¶ä½™çŽ¯å¢ƒï¼ˆdev/test æŒ‡å‘ ruoyi-vue-pro,yzfx/wtxc/zsjc/chdl/kyzn æŒ‡å‘各自 mom-* åº“)按需另跑。
--         æœ¬è„šæœ¬å¹‚ç­‰ï¼Œå¯è¿žç»­é‡å¤æ‰§è¡Œã€‚
-- å‰ç½®ï¼šéœ€å…ˆæ‰§è¡Œ archive_module.sql å»º `archive_document` è¡¨ã€‚
-- æ³¨æ„ï¼šæœ¬è„šæœ¬ä¸å« tenant_id(项目不使用多租户模式);ALTER TABLE ADD COLUMN ä¸æ”¯æŒ
--       IF NOT EXISTS,统一用 information_schema å­˜åœ¨æ€§å®ˆå«ã€‚
-- =====================================================
-- ----- 1. research_application æ–°å¢ž archive_document_id -----
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'research_application' AND column_name = 'archive_document_id');
SET @sql = IF(@c = 0,
    'ALTER TABLE research_application ADD COLUMN archive_document_id bigint DEFAULT NULL COMMENT ''归档生成的档案 ID(archive_document.id,归档联动回写)'' AFTER notice_id',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ----- 2. ums_member æ–°å¢ž archive_document_id -----
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'ums_member' AND column_name = 'archive_document_id');
SET @sql = IF(@c = 0,
    'ALTER TABLE ums_member ADD COLUMN archive_document_id bigint DEFAULT NULL COMMENT ''归档生成的档案 ID(archive_document.id,归档联动回写)'' AFTER status',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ----- 3. finance_reimbursement æ–°å¢ž archive_document_id -----
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'finance_reimbursement' AND column_name = 'archive_document_id');
SET @sql = IF(@c = 0,
    'ALTER TABLE finance_reimbursement ADD COLUMN archive_document_id bigint DEFAULT NULL COMMENT ''归档生成的档案 ID(archive_document.id,归档联动回写)'' AFTER archive_time',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
docs/sql/config_export_all_20260928.sql
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
docs/sql/config_export_all_20260928.sql.bak
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
docs/sql/finance_reimbursement_approve.bpmn
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="finance_reimbursement_approve" name="报销审批" isExecutable="true">
    <startEvent id="StartEvent" name="开始"></startEvent>
    <userTask id="StartUserNode" name="发起人">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[36]]></flowable:candidateStrategy>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="提交" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="false" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="false" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="false" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="false" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="false" id="6"></flowable:buttonsSetting>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[2]]></flowable:assignStartUserHandlerType>
      </extensionElements>
    </userTask>
    <userTask id="Activity_finance_review" name="财务审核">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <userTask id="Activity_finance_approve" name="财务主管审批">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <endEvent id="EndEvent" name="结束"></endEvent>
    <sequenceFlow id="sequenceFlow-1" sourceRef="StartEvent" targetRef="StartUserNode"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-2" sourceRef="StartUserNode" targetRef="Activity_finance_review"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-3" sourceRef="Activity_finance_review" targetRef="Activity_finance_approve"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-4" sourceRef="Activity_finance_approve" targetRef="EndEvent"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_finance_reimbursement_approve">
    <bpmndi:BPMNPlane bpmnElement="finance_reimbursement_approve" id="BPMNPlane_finance_reimbursement_approve">
      <bpmndi:BPMNShape bpmnElement="StartEvent" id="BPMNShape_StartEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="StartUserNode" id="BPMNShape_StartUserNode">
        <omgdc:Bounds height="60.0" width="100.0" x="80.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_finance_review" id="BPMNShape_Activity_finance_review">
        <omgdc:Bounds height="60.0" width="100.0" x="230.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_finance_approve" id="BPMNShape_Activity_finance_approve">
        <omgdc:Bounds height="60.0" width="100.0" x="380.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="EndEvent" id="BPMNShape_EndEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="530.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-1" id="BPMNEdge_sequenceFlow-1">
        <omgdi:waypoint x="30.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="80.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-2" id="BPMNEdge_sequenceFlow-2">
        <omgdi:waypoint x="180.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-3" id="BPMNEdge_sequenceFlow-3">
        <omgdi:waypoint x="330.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-4" id="BPMNEdge_sequenceFlow-4">
        <omgdi:waypoint x="480.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="530.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
docs/sql/research_fund_finance_migration.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
-- =====================================================
-- è¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯¹æŽ¥è´¢åŠ¡æŠ¥é”€ - å¼€å‘库增量脚本
--
-- èƒŒæ™¯ï¼šæ¯æ¡ `research_fund_record` æ‹¨æ¬¾è®°å½•改为自动生成并提交一张财务报销单
--       ï¼ˆfinance_reimbursement,1:1、金额一致),财务审批结果由
--       ResearchFundStatusListener å›žå†™æœ¬å°è´¦çŠ¶æ€ã€‚
--
-- æ‰§è¡Œåº“:脚本用 DATABASE() åˆ¤å®šï¼Œä¸Žåº“名无关,故可对任意目标库执行。
--         2026-09-29 å·²åœ¨ application-local.yaml æŒ‡å‘çš„ mom-jssm æ‰§è¡Œå¹¶æ ¸å¯¹é€šè¿‡ï¼›
--         å…¶ä½™çŽ¯å¢ƒï¼ˆdev/test æŒ‡å‘ ruoyi-vue-pro,yzfx/wtxc/zsjc/chdl/kyzn æŒ‡å‘各自 mom-* åº“)按需另跑。
--         æœ¬è„šæœ¬å¹‚ç­‰ï¼Œå¯é‡å¤æ‰§è¡Œã€‚
-- å†…容:① research_fund_record æ–°å¢ž 4 åˆ— + status æ³¨é‡Šæ›´æ–° + ç´¢å¼•
--       â‘¡ system_dict_data çš„ research_fund_status è¡¥ã€ŒæŠ¥é”€å®¡æ‰¹ä¸­/已驳回」
-- æ³¨æ„ï¼šæœ¬è„šæœ¬ä¸å« tenant_id(项目不使用多租户模式)。
-- =====================================================
-- ----- 1. research_fund_record æ–°å¢ž 4 åˆ—(ALTER TABLE ADD COLUMN ä¸æ”¯æŒ IF NOT EXISTS,用 information_schema å®ˆå«ï¼‰ -----
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'research_fund_record' AND column_name = 'reimbursement_id');
SET @sql = IF(@c = 0,
    'ALTER TABLE research_fund_record ADD COLUMN reimbursement_id bigint DEFAULT NULL COMMENT ''关联财务报销单 ID(finance_reimbursement.id)'' AFTER status',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'research_fund_record' AND column_name = 'reimbursement_no');
SET @sql = IF(@c = 0,
    'ALTER TABLE research_fund_record ADD COLUMN reimbursement_no varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '''' COMMENT ''关联财务报销单号'' AFTER reimbursement_id',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'research_fund_record' AND column_name = 'reject_reason');
SET @sql = IF(@c = 0,
    'ALTER TABLE research_fund_record ADD COLUMN reject_reason varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '''' COMMENT ''报销驳回原因(BPM å›žå†™ï¼‰'' AFTER reimbursement_no',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @c = (SELECT COUNT(*) FROM information_schema.columns
          WHERE table_schema = DATABASE() AND table_name = 'research_fund_record' AND column_name = 'reject_time');
SET @sql = IF(@c = 0,
    'ALTER TABLE research_fund_record ADD COLUMN reject_time datetime DEFAULT NULL COMMENT ''报销驳回时间'' AFTER reject_reason',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- status åˆ—注释更新为「0-待拨付 15-报销审批中 20-已拨付 30-已驳回」
ALTER TABLE research_fund_record
  MODIFY COLUMN status tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待拨付 15-报销审批中 20-已拨付 30-已驳回';
-- æŒ‰æŠ¥é”€å•号回查台账的索引
SET @c = (SELECT COUNT(*) FROM information_schema.statistics
          WHERE table_schema = DATABASE() AND table_name = 'research_fund_record' AND index_name = 'idx_reimbursement_id');
SET @sql = IF(@c = 0,
    'ALTER TABLE research_fund_record ADD INDEX idx_reimbursement_id (reimbursement_id) USING BTREE',
    'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ----- 2. å­—å…¸ research_fund_status è¡¥ 2 æ¡ + æŽ’序调整 + ç±»åž‹å¤‡æ³¨ -----
UPDATE system_dict_type SET remark = '课题经费待拨付/报销审批中/已拨付/已驳回', update_time = NOW()
WHERE `type` = 'research_fund_status';
-- å…ˆåˆ åŽæ’,保证幂等(同 research_module.sql çš„写法)
DELETE FROM system_dict_data WHERE dict_type = 'research_fund_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100401, 1, '待拨付', '0', 'research_fund_status', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100403, 2, '报销审批中', '15', 'research_fund_status', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100402, 3, '已拨付', '20', 'research_fund_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100404, 4, '已驳回', '30', 'research_fund_status', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
docs/sql/research_module.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,249 @@
-- =========================================
-- è¯¾é¢˜ç®¡ç†æ¨¡å—(yudao-module-research)一期
-- å»ºè¡¨ + å­—典数据 + BPM åˆ†ç±» + èœå•
-- è¦†ç›–需求:1.课题指南发布 2.课题在线申报 3.课题评审流程 4.立项审批 5.经费拨付
-- äºŒæœŸï¼ˆç»“项验收/结项证书、成果归档知识库)不在此脚本内
-- =========================================
-- ----- 1. è¯¾é¢˜æŒ‡å—表 -----
DROP TABLE IF EXISTS `research_guideline`;
CREATE TABLE `research_guideline` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '指南编号',
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '指南名称',
  `category` tinyint NOT NULL COMMENT '课题类别:1-重点课题 2-一般课题',
  `apply_start_time` datetime DEFAULT NULL COMMENT '申报开始时间',
  `apply_end_time` datetime DEFAULT NULL COMMENT '申报截止时间',
  `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '指南正文',
  `publish_status` tinyint NOT NULL DEFAULT '0' COMMENT '发布状态:0-草稿 20-已发布 30-已截止',
  `publish_time` datetime DEFAULT NULL COMMENT '发布时间',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_category` (`category`) USING BTREE,
  KEY `idx_publish_status` (`publish_status`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题-指南';
-- ----- 2. è¯¾é¢˜ç”³æŠ¥ä¹¦è¡¨ -----
DROP TABLE IF EXISTS `research_application`;
CREATE TABLE `research_application` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '申报编号',
  `guideline_id` bigint NOT NULL COMMENT '所属课题指南 ID',
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '课题名称',
  `category` tinyint NOT NULL COMMENT '课题类别:1-重点课题 2-一般课题(冗余自指南)',
  `member_id` bigint DEFAULT NULL COMMENT '申报会员 ID(关联 ums_member)',
  `applicant_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '申报人姓名/单位名称',
  `applicant_user_id` bigint DEFAULT NULL COMMENT '申报登录用户 ID',
  `work_unit` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '工作单位',
  `job_title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '职称',
  `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '联系电话',
  `contact_email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '联系邮箱',
  `team_members` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '课题组成员(每行一条:姓名|单位|职称)',
  `budget_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '申请经费(元)',
  `plan_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '申报书正文(研究背景/目标/方案/预期成果)',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-草稿 15-审批中 20-已立项 35-已驳回',
  `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT 'BPM æµç¨‹å®žä¾‹ ID',
  `review_avg_score` decimal(6,2) DEFAULT NULL COMMENT '专家评审加权均分(后端汇总)',
  `review_pass` tinyint DEFAULT NULL COMMENT '评审结论:0-未通过 1-推荐立项(后端汇总)',
  `research_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '立项编号(审批通过后生成)',
  `approve_time` datetime DEFAULT NULL COMMENT '立项时间',
  `notice_id` bigint DEFAULT NULL COMMENT '已发布的立项通知公告 ID',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_guideline_id` (`guideline_id`) USING BTREE,
  KEY `idx_member_id` (`member_id`) USING BTREE,
  KEY `idx_status` (`status`) USING BTREE,
  KEY `idx_applicant_user_id` (`applicant_user_id`) USING BTREE,
  KEY `idx_research_no` (`research_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题-申报书';
-- ----- 3. ä¸“家评审明细表(一主单挂多行,每行一位专家) -----
DROP TABLE IF EXISTS `research_topic_review`;
CREATE TABLE `research_topic_review` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `application_id` bigint NOT NULL COMMENT '课题申报书 ID',
  `evaluator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '专家姓名',
  `evaluator_unit` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '专家所在单位',
  `evaluator_title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '专家职称',
  `topic_score` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '选题价值与意义得分(权重0.3)',
  `plan_score` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '研究方案可行性得分(权重0.3)',
  `team_score` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '团队与研究基础得分(权重0.2)',
  `result_score` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '预期成果与效益得分(权重0.2)',
  `composite_score` decimal(6,2) DEFAULT NULL COMMENT '加权合计得分(后端计算)',
  `ranking` int DEFAULT NULL COMMENT '本申报单内评分排名(后端计算)',
  `conclusion` tinyint DEFAULT NULL COMMENT '评审结论:1-推荐立项 2-不推荐立项',
  `opinion` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '评审意见',
  `review_time` datetime DEFAULT NULL COMMENT '评审时间',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_application_id` (`application_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题-专家评审明细';
-- ----- 4. ç»è´¹æ‹¨ä»˜è®°å½•表 -----
DROP TABLE IF EXISTS `research_fund_record`;
CREATE TABLE `research_fund_record` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `application_id` bigint NOT NULL COMMENT '课题申报书 ID',
  `batch_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拨付批次(如:第一期)',
  `amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '拨付金额(元)',
  `pay_date` date DEFAULT NULL COMMENT '拨付日期',
  `payee` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '收款单位/账户名',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待拨付 15-报销审批中 20-已拨付 30-已驳回',
  `reimbursement_id` bigint DEFAULT NULL COMMENT '关联财务报销单 ID(finance_reimbursement.id)',
  `reimbursement_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '关联财务报销单号',
  `reject_reason` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '报销驳回原因(BPM å›žå†™ï¼‰',
  `reject_time` datetime DEFAULT NULL COMMENT '报销驳回时间',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_application_id` (`application_id`) USING BTREE,
  KEY `idx_reimbursement_id` (`reimbursement_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题-经费拨付记录';
-- ----- 5. å­—典数据 -----
-- research_guideline_category(课题指南类别)
DELETE FROM system_dict_type WHERE `type` = 'research_guideline_category';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (81000, '课题指南类别', 'research_guideline_category', 0, '重点课题/一般课题分类标注', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'research_guideline_category';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100001, 1, '重点课题', '1', 'research_guideline_category', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100002, 2, '一般课题', '2', 'research_guideline_category', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
-- research_guideline_status(课题指南发布状态)
DELETE FROM system_dict_type WHERE `type` = 'research_guideline_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (81001, '课题指南状态', 'research_guideline_status', 0, '课题指南发布状态', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'research_guideline_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100101, 1, '草稿', '0', 'research_guideline_status', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100102, 2, '已发布', '20', 'research_guideline_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100103, 3, '已截止', '30', 'research_guideline_status', 0, 'info', '', '1', NOW(), '1', NOW(), b'0');
-- research_application_status(课题申报书状态)
DELETE FROM system_dict_type WHERE `type` = 'research_application_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (81002, '课题申报状态', 'research_application_status', 0, '课题申报书审批状态', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'research_application_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100201, 1, '草稿', '0', 'research_application_status', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100202, 2, '审批中', '15', 'research_application_status', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100203, 3, '已立项', '20', 'research_application_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100204, 4, '已驳回', '35', 'research_application_status', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- research_review_conclusion(专家评审结论)
DELETE FROM system_dict_type WHERE `type` = 'research_review_conclusion';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (81003, '专家评审结论', 'research_review_conclusion', 0, '专家评审推荐立项/不推荐立项', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'research_review_conclusion';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100301, 1, '推荐立项', '1', 'research_review_conclusion', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100302, 2, '不推荐立项', '2', 'research_review_conclusion', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- research_fund_status(经费拨付状态)
DELETE FROM system_dict_type WHERE `type` = 'research_fund_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (81004, '经费拨付状态', 'research_fund_status', 0, '课题经费待拨付/报销审批中/已拨付/已驳回', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'research_fund_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100401, 1, '待拨付', '0', 'research_fund_status', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100403, 2, '报销审批中', '15', 'research_fund_status', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100402, 3, '已拨付', '20', 'research_fund_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8100404, 4, '已驳回', '30', 'research_fund_status', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- ----- 6. BPM åˆ†ç±» -----
DELETE FROM bpm_category WHERE `code` = 'research_topic_approve';
INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
VALUES (8002, '课题立项审批', 'research_topic_approve', '课题申报三级审批(秘书处形式审查→专家评审→学会立项审批)', 0, 2, '1', NOW(), '1', NOW(), b'0');
-- ----- 7. èœå• -----
-- æ³¨æ„ï¼šèœå• ID ä¸è¦ä¸ŽçŽ°æœ‰èœå•å†²çªï¼ˆè¯¾é¢˜ç®¡ç†å ç”¨ 1075510~1075553)
-- é¡¶çº§ç›®å½•(type=1),parent_id=0,图标 ep:notebook
DELETE FROM system_menu WHERE id BETWEEN 1075510 AND 1075559;
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075510, '课题管理', '', 1, 81, 0, '/research', 'ep:notebook', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- è¯¾é¢˜æŒ‡å—(菜单,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075511, '课题指南', '', 2, 1, 1075510, 'guideline', 'ep:document', 'research/guideline/index', 'ResearchGuideline', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- è¯¾é¢˜ç”³æŠ¥ï¼ˆèœå•,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075512, '课题申报', '', 2, 2, 1075510, 'application', 'ep:edit-pen', 'research/application/index', 'ResearchApplication', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ï¼ˆtype=3)—— è¯¾é¢˜æŒ‡å—
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075520, '课题指南查询', 'research:guideline:query', 3, 1, 1075511, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075521, '课题指南创建', 'research:guideline:create', 3, 2, 1075511, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075522, '课题指南更新', 'research:guideline:update', 3, 3, 1075511, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075523, '课题指南删除', 'research:guideline:delete', 3, 4, 1075511, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075524, '课题指南发布/截止', 'research:guideline:publish', 3, 5, 1075511, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— è¯¾é¢˜ç”³æŠ¥
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075530, '课题申报查询', 'research:application:query', 3, 1, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075531, '课题申报创建', 'research:application:create', 3, 2, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075532, '课题申报更新', 'research:application:update', 3, 3, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075533, '课题申报删除', 'research:application:delete', 3, 4, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075534, '课题申报提交审批', 'research:application:submit', 3, 5, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075535, '发布立项通知', 'research:application:notice', 3, 6, 1075512, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— ä¸“家评审明细
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075540, '专家评审查询', 'research:review:query', 3, 7, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075541, '专家评审录入', 'research:review:create', 3, 8, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075542, '专家评审修改', 'research:review:update', 3, 9, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075543, '专家评审删除', 'research:review:delete', 3, 10, 1075512, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— ç»è´¹æ‹¨ä»˜
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075550, '经费拨付查询', 'research:fund:query', 3, 11, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075551, '经费拨付登记', 'research:fund:create', 3, 12, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075552, '经费拨付修改', 'research:fund:update', 3, 13, 1075512, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075553, '经费拨付删除', 'research:fund:delete', 3, 14, 1075512, '1', NOW(), '1', NOW(), b'0');
docs/sql/research_topic_approve.bpmn
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <process id="research_topic_approve" name="课题立项审批" isExecutable="true">
    <startEvent id="StartEvent" name="开始"></startEvent>
    <userTask id="StartUserNode" name="发起人">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[36]]></flowable:candidateStrategy>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="提交" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="false" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="false" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="false" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="false" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="false" id="6"></flowable:buttonsSetting>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[2]]></flowable:assignStartUserHandlerType>
      </extensionElements>
    </userTask>
    <userTask id="Activity_secretariat_review" name="秘书处形式审查">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <userTask id="Activity_expert_review" name="专家评审">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <userTask id="Activity_council_approve" name="学会立项审批">
      <extensionElements>
        <flowable:approveType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:approveType>
        <flowable:candidateStrategy xmlns:flowable="http://flowable.org/bpmn"><![CDATA[10]]></flowable:candidateStrategy>
        <flowable:candidateParam xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:candidateParam>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="通过" enable="true" id="1"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="拒绝" enable="true" id="2"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="转办" enable="true" id="3"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="委派" enable="true" id="4"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="加签" enable="true" id="5"></flowable:buttonsSetting>
        <flowable:buttonsSetting xmlns:flowable="http://flowable.org/bpmn" displayName="退回" enable="true" id="6"></flowable:buttonsSetting>
        <flowable:approveMethod xmlns:flowable="http://flowable.org/bpmn"><![CDATA[4]]></flowable:approveMethod>
        <flowable:rejectHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:rejectHandlerType>
        <flowable:assignStartUserHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignStartUserHandlerType>
        <flowable:assignEmptyHandlerType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[1]]></flowable:assignEmptyHandlerType>
        <flowable:signEnable xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:signEnable>
        <flowable:reasonRequire xmlns:flowable="http://flowable.org/bpmn"><![CDATA[false]]></flowable:reasonRequire>
        <flowable:nodeType xmlns:flowable="http://flowable.org/bpmn"><![CDATA[11]]></flowable:nodeType>
      </extensionElements>
      <multiInstanceLoopCharacteristics isSequential="true" flowable:collection="${coll_userList}">
        <loopCardinality>1</loopCardinality>
        <completionCondition>${ nrOfCompletedInstances &gt;= nrOfInstances }</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <endEvent id="EndEvent" name="结束"></endEvent>
    <sequenceFlow id="sequenceFlow-1" sourceRef="StartEvent" targetRef="StartUserNode"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-2" sourceRef="StartUserNode" targetRef="Activity_secretariat_review"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-3" sourceRef="Activity_secretariat_review" targetRef="Activity_expert_review"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-4" sourceRef="Activity_expert_review" targetRef="Activity_council_approve"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-5" sourceRef="Activity_council_approve" targetRef="EndEvent"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_research_topic_approve">
    <bpmndi:BPMNPlane bpmnElement="research_topic_approve" id="BPMNPlane_research_topic_approve">
      <bpmndi:BPMNShape bpmnElement="StartEvent" id="BPMNShape_StartEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="StartUserNode" id="BPMNShape_StartUserNode">
        <omgdc:Bounds height="60.0" width="100.0" x="80.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_secretariat_review" id="BPMNShape_Activity_secretariat_review">
        <omgdc:Bounds height="60.0" width="100.0" x="230.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_expert_review" id="BPMNShape_Activity_expert_review">
        <omgdc:Bounds height="60.0" width="100.0" x="380.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="Activity_council_approve" id="BPMNShape_Activity_council_approve">
        <omgdc:Bounds height="60.0" width="100.0" x="530.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="EndEvent" id="BPMNShape_EndEvent">
        <omgdc:Bounds height="30.0" width="30.0" x="680.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-1" id="BPMNEdge_sequenceFlow-1">
        <omgdi:waypoint x="30.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="80.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-2" id="BPMNEdge_sequenceFlow-2">
        <omgdi:waypoint x="180.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-3" id="BPMNEdge_sequenceFlow-3">
        <omgdi:waypoint x="330.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-4" id="BPMNEdge_sequenceFlow-4">
        <omgdi:waypoint x="480.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="530.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-5" id="BPMNEdge_sequenceFlow-5">
        <omgdi:waypoint x="630.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="680.0" y="30.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
docs/sql/ums_module.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,215 @@
-- =========================================
-- UMS ä¼šå‘˜ç®¡ç†æ¨¡å—(yudao-module-ums)
-- å»ºè¡¨ + å­—典数据 + BPM åˆ†ç±» + èœå•
-- =========================================
-- ----- 1. å…¥ä¼šç”³è¯·å•表 -----
DROP TABLE IF EXISTS `ums_application`;
CREATE TABLE `ums_application` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '申请编号',
  `member_type` tinyint NOT NULL COMMENT '会员类型:1-个人 2-单位',
  `applicant_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '申请人姓名(个人)或单位名称(单位)',
  `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '联系电话',
  `contact_email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '邮箱',
  `legal_person` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '法定代表人(单位专有)',
  `organization_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '统一社会信用代码(单位专有)',
  `research_direction` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '研究方向',
  `work_unit` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '工作单位(个人专有)',
  `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '职称(个人专有)',
  `member_grade` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '申请等级',
  `region_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '地区编码',
  `industry` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '行业',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-草稿 15-审批中 20-已通过 35-已驳回',
  `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT 'BPM æµç¨‹å®žä¾‹ ID',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_status` (`status`) USING BTREE,
  KEY `idx_member_type` (`member_type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员-入会申请单';
-- ----- 2. ä¼šå‘˜è¡¨ -----
DROP TABLE IF EXISTS `ums_member`;
CREATE TABLE `ums_member` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '会员编号',
  `user_id` bigint DEFAULT NULL COMMENT '关联系统用户 ID',
  `application_id` bigint DEFAULT NULL COMMENT '来源申请单 ID',
  `member_type` tinyint NOT NULL COMMENT '会员类型:1-个人 2-单位',
  `member_grade` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '会员等级',
  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名(个人)或单位名称(单位)',
  `contact_user` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '联系人(单位用)',
  `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '联系电话',
  `contact_email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '邮箱',
  `legal_person` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '法定代表人(单位专有)',
  `organization_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '统一社会信用代码',
  `research_direction` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '研究方向',
  `work_unit` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '工作单位(个人专有)',
  `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '职称(个人专有)',
  `unit_intro` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '单位简介(单位专有)',
  `region_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '地区编码',
  `industry` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '行业',
  `join_date` date DEFAULT NULL COMMENT '入会日期',
  `expire_date` date DEFAULT NULL COMMENT '有效期至',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-正常 0-暂停',
  `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_user_id` (`user_id`) USING BTREE,
  KEY `idx_region_code` (`region_code`) USING BTREE,
  KEY `idx_industry` (`industry`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员';
-- ----- 3. å­—典数据 -----
-- ums_application_status(入会申请状态)
DELETE FROM system_dict_type WHERE `type` = 'ums_application_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (80001, '入会申请状态', 'ums_application_status', 0, '入会申请审批状态', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'ums_application_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000101, 1, '草稿', '0', 'ums_application_status', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000102, 2, '审批中', '15', 'ums_application_status', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000103, 3, '已通过', '20', 'ums_application_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000104, 4, '已驳回', '35', 'ums_application_status', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- ums_member_type(会员类型)
DELETE FROM system_dict_type WHERE `type` = 'ums_member_type';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (80002, '会员类型', 'ums_member_type', 0, '个人会员/单位会员', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'ums_member_type';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000201, 1, '个人会员', '1', 'ums_member_type', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000202, 2, '单位会员', '2', 'ums_member_type', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
-- ums_member_status(会员状态)
DELETE FROM system_dict_type WHERE `type` = 'ums_member_status';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (80003, '会员状态', 'ums_member_status', 0, '会员正常/暂停', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'ums_member_status';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000301, 1, '正常', '1', 'ums_member_status', 0, 'success', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000302, 2, '暂停', '0', 'ums_member_status', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
-- ums_personal_grade(个人会员等级)
DELETE FROM system_dict_type WHERE `type` = 'ums_personal_grade';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (80004, '个人会员等级', 'ums_personal_grade', 0, '个人会员级别', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'ums_personal_grade';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000401, 1, '普通会员', '1', 'ums_personal_grade', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000402, 2, '理事', '2', 'ums_personal_grade', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000403, 3, '常务理事', '3', 'ums_personal_grade', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000404, 4, '副理事长', '4', 'ums_personal_grade', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000405, 5, '理事长', '5', 'ums_personal_grade', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- ums_unit_grade(单位会员等级)
DELETE FROM system_dict_type WHERE `type` = 'ums_unit_grade';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (80005, '单位会员等级', 'ums_unit_grade', 0, '单位会员级别', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'ums_unit_grade';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000501, 1, '普通单位', '1', 'ums_unit_grade', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000502, 2, '理事单位', '2', 'ums_unit_grade', 0, 'processing', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000503, 3, '副会长单位', '3', 'ums_unit_grade', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000504, 4, '会长单位', '4', 'ums_unit_grade', 0, 'danger', '', '1', NOW(), '1', NOW(), b'0');
-- ums_industry(行业)
DELETE FROM system_dict_type WHERE `type` = 'ums_industry';
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES (80006, '会员行业', 'ums_industry', 0, '学会会员所属行业', '1', NOW(), '1', NOW(), b'0');
DELETE FROM system_dict_data WHERE dict_type = 'ums_industry';
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000601, 1, '智能制造', '1', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000602, 2, '新能源', '2', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000603, 3, '电子信息', '3', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000604, 4, '生物医药', '4', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000605, 5, '新材料', '5', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000606, 6, '节能环保', '6', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000607, 7, '咨询服务', '7', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, remark, creator, create_time, updater, update_time, deleted)
VALUES (8000608, 8, '其他', '8', 'ums_industry', 0, 'default', '', '1', NOW(), '1', NOW(), b'0');
-- ----- 4. BPM åˆ†ç±» -----
DELETE FROM bpm_category WHERE `code` = 'ums_membership_approve';
INSERT INTO bpm_category (id, name, code, description, status, sort, creator, create_time, updater, update_time, deleted)
VALUES (8001, '入会审批', 'ums_membership_approve', '会员入会申请两级审批(秘书处初审→理事会审议)', 0, 1, '1', NOW(), '1', NOW(), b'0');
-- ----- 5. èœå• -----
-- æ³¨æ„ï¼šèœå• ID ä¸è¦ä¸ŽçŽ°æœ‰èœå•å†²çª
-- é¡¶çº§ç›®å½•(type=1),parent_id=0,图标 ep:user
DELETE FROM system_menu WHERE id BETWEEN 1075490 AND 1075503;
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075490, '会员管理', '', 1, 80, 0, '/ums', 'ep:user', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- å…¥ä¼šç”³è¯·ï¼ˆèœå•,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075491, '入会申请', '', 2, 1, 1075490, 'application', 'ep:edit', 'ums/application/index', 'UmsApplication', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- ä¼šå‘˜ç®¡ç†ï¼ˆèœå•,type=2)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075492, '会员列表', '', 2, 2, 1075490, 'member', 'ep:user-filled', 'ums/member/index', 'UmsMember', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- ä¼šå‘˜é€šè®¯å½•(菜单,type=2,只读)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (1075493, '会员通讯录', '', 2, 3, 1075490, 'directory', 'ep:notebook', 'ums/member/directory', 'UmsMemberDirectory', 0, b'1', b'1', b'0', '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ï¼ˆtype=3)—— å…¥ä¼šç”³è¯·
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075494, '入会申请查询', 'ums:application:query', 3, 1, 1075491, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075495, '入会申请创建', 'ums:application:create', 3, 2, 1075491, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075496, '入会申请提交审批', 'ums:application:submit', 3, 3, 1075491, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075501, '入会申请更新', 'ums:application:update', 3, 4, 1075491, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075502, '入会申请删除', 'ums:application:delete', 3, 5, 1075491, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— ä¼šå‘˜ç®¡ç†
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075497, '会员查询', 'ums:member:query', 3, 1, 1075492, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075498, '会员创建', 'ums:member:create', 3, 2, 1075492, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075499, '会员更新', 'ums:member:update', 3, 3, 1075492, '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075503, '会员删除', 'ums:member:delete', 3, 4, 1075492, '1', NOW(), '1', NOW(), b'0');
-- æŒ‰é’®æƒé™ â€”— é€šè®¯å½•(只读)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, creator, create_time, updater, update_time, deleted)
VALUES (1075500, '通讯录查询', 'ums:member:directory', 3, 1, 1075493, '1', NOW(), '1', NOW(), b'0');
-- ----- 6. é»˜è®¤éƒ¨é—¨ï¼ˆä¼šå‘˜ç³»ç»Ÿè´¦å·æ‰€å±žéƒ¨é—¨ï¼‰ -----
DELETE FROM system_dept WHERE id = 129;
INSERT INTO system_dept (id, name, parent_id, sort, leader_user_id, phone, email, status, creator, create_time, updater, update_time, deleted)
VALUES (129, '学会会员', 0, 99, NULL, '', '', 0, '1', NOW(), '1', NOW(), b'0');
docs/Õб꼼ÊõÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1071 @@
# å­¦ä¼šä¸šåŠ¡ç®¡ç†å¹³å° æŠ•标技术方案
> è¦†ç›–系统:协同办公、财务报销、会员管理、企业微信、课题管理、档案管理、系统管理
| é¡¹ç›® | å†…容 |
|---|---|
| æ–‡æ¡£åç§° | å­¦ä¼šä¸šåŠ¡ç®¡ç†å¹³å°æŠ•æ ‡æŠ€æœ¯æ–¹æ¡ˆ |
| æ–‡æ¡£ç±»åž‹ | æŠ•标技术文件(技术方案分册) |
| ç¼–制日期 | 2026-09-29 |
| ç‰ˆæœ¬ | V1.0 |
| æŠ•标人 | ã€æŠ•标单位全称】 |
| æ‹›æ ‡äºº | ã€æ‹›æ ‡å•位全称】 |
> è¯´æ˜Žï¼šæ–‡ä¸­ `【】` ä¸ºå¾…填占位内容(单位名称、品牌、价格、工期等),由投标人定稿时替换。本文所述全部功能、技术组件、数据模型、流程定义、权限点、字典编码,均取自投标人现有系统的实际代码与运行库,无推测性描述。
---
## ä¸€ã€é¡¹ç›®æ¦‚è¿°
### 1.1 å»ºè®¾èƒŒæ™¯
学会(协会)日常业务长期依赖线下台账与分散的 Excel è¡¨æ ¼è¿è½¬ï¼Œå­˜åœ¨å››ç±»çªå‡ºé—®é¢˜ï¼š
| åºå· | çŽ°çŠ¶é—®é¢˜ | å…·ä½“表现 |
|---|---|---|
| 1 | ä¼šå‘˜ä¿¡æ¯åˆ†æ•£ | å…¥ä¼šç”³è¯·èµ°çº¸è´¨è¡¨å•或邮件,批准后靠人工建表维护会员名录,等级(普通会员/理事/常务理事/理事长)与单位会员信息难以统一归档 |
| 2 | è¯¾é¢˜ç®¡ç†æ–­é“¾ | è¯¾é¢˜æŒ‡å—发布、申报书收集、专家评审打分、立项通知、经费拨付各环节分离,评审分数与立项结论靠人工汇总,过程不可追溯 |
| 3 | è´¢åŠ¡æŠ¥é”€æ— ç•™ç—• | æŠ¥é”€å•靠纸质粘贴发票流转,审批层级与金额规则靠人工判断,报销凭证无法与档案关联 |
| 4 | æ¡£æ¡ˆä¸Žå€Ÿé˜…失控 | çº¸è´¨æ¡£æ¡ˆæ— ç”µå­å°è´¦ï¼Œå€Ÿå‡ºé æ‰‹å·¥ç™»è®°ï¼Œé€¾æœŸæ— äººæé†’;敏感档案(如财务凭证)无权限隔离,存在越权查阅风险 |
同时,移动端触达能力缺失,通知发布依赖逐一转发;组织人员与流程的权限边界不清,无统一审计。
### 1.2 å»ºè®¾ç›®æ ‡
本方案的建设目标为:**以统一审批引擎与统一档案底座为核心,构建一体化、可追溯、有权限边界的学会业务管理平台**,具体分解为五个可验证目标:
1. **业务在线化**:会员入会、课题立项、财务报销、档案借阅四类核心业务全部线上流转,替代纸质台账。
2. **审批统一化**:四类业务共用一套工作流引擎,审批规则(审批人、层级、会签方式、驳回策略)可配置、可追溯,无需为每类业务单独开发审批逻辑。
3. **档案集中化**:课题、会员、财务三类业务的成果材料一键归档至统一档案库,形成"业务产生档案、档案反哺检索"的闭环;档案借阅走两级审批并实时计算逾期。
4. **权限精细化**:以 RBAC è§’色权限为基础,叠加密级管控与独立敏感权限点,实现"公开/内部/保密"三级档案的可见性隔离,并可审计。
5. **触达移动化**:借助企业微信将活动、通知直接推送至成员,成员可在线报名、签到,形成移动端业务入口。
### 1.3 å»ºè®¾èŒƒå›´ä¸Žæ¨¡å—总览
本期建设范围即本方案的七大子系统,均以现有菜单树为严格依据:
| åºå· | å­ç³»ç»Ÿ | èœå•路径 | èœå• ID | äºŒçº§åŠŸèƒ½é¡µ | å®šä½äºŽä¸šåŠ¡é“¾ä¸­çš„è§’è‰² |
|---|---|---|---|---|---|
| 1 | ååŒåŠžå…¬ | `/bpm` | 1185 | æµç¨‹ç®¡ç†ã€å®¡æ‰¹ä¸­å¿ƒã€é€šçŸ¥å…¬å‘Šã€OA åº”用 | å…¨å¹³å°**审批与流程底座** |
| 2 | è´¢åŠ¡æŠ¥é”€ | `/finance` | 1075425 | æŠ¥é”€å• | ç»è´¹æ”¯å‡ºçš„**申请与审批** |
| 3 | ä¼šå‘˜ç®¡ç† | `/ums` | 1075490 | å…¥ä¼šç”³è¯·ã€ä¼šå‘˜åˆ—表、会员通讯录 | å­¦ä¼š**主体(人/单位)**管理 |
| 4 | ä¼ä¸šå¾®ä¿¡ | `/wxcp` | 6100000 | æ´»åŠ¨ç®¡ç†ã€é€šçŸ¥ç®¡ç† | å¯¹ä¼šå‘˜ï¼æˆå‘˜çš„**移动触达** |
| 5 | è¯¾é¢˜ç®¡ç† | `/research` | 1075510 | è¯¾é¢˜æŒ‡å—、课题申报(含专家评审、经费拨付) | å­¦æœ¯ä¸šåŠ¡çš„**主线流程** |
| 6 | æ¡£æ¡ˆç®¡ç† | `/archive` | 1075600 | æ¡£æ¡ˆåˆ†ç±»ã€æ¡£æ¡ˆç®¡ç†ã€å€Ÿé˜…登记、档案统计 | å…¨å¹³å°çš„**档案归集与借阅底座** |
| 7 | ç³»ç»Ÿç®¡ç† | `/system` | 1 | ç”¨æˆ·ã€è§’色、菜单、部门、岗位、字典、编码规则、审计日志、消息中心、OAuth2、三方登录、文件存储、地区、公司信息配置 | å¹³å°**基础支撑与治理** |
**七大子系统之间的关系(一句话概括)**:会员管理提供"人"、课题管理提供"事"、财务报销提供"钱",三者均在协同办公的审批引擎上流转,并在收口时向档案管理归集形成"档案";企业微信负责把"事"与"通知"推送到人;系统管理为以上全部提供身份、权限、字典、编码与审计能力。
### 1.4 æœ¯è¯­ä¸Žç¼©ç•¥è¯­
| æœ¯è¯­ | è¯´æ˜Ž |
|---|---|
| BPM | Business Process Management,业务流程管理,本方案指基于 Flowable çš„审批引擎 |
| BPMN 2.0 | ä¸šåŠ¡æµç¨‹å»ºæ¨¡ä¸Žæ ‡æ³¨æ ‡å‡†ï¼Œæœ¬æ–¹æ¡ˆçš„æµç¨‹å›¾äº¤æ¢æ ¼å¼ |
| RBAC | åŸºäºŽè§’色的访问控制,用户 â€” è§’色 â€” èœå•/权限点三层模型 |
| å¯†çº§ | æ¡£æ¡ˆçš„æ•æ„Ÿçº§åˆ«ï¼Œåˆ†å…¬å¼€ï¼ˆ1)/内部(2)/保密(3)三级 |
| å•据快照 | ä¸šåŠ¡å•æ®åœ¨æµè½¬æ—¶å¯¹å…³è”å¯¹è±¡å…³é”®å­—æ®µçš„å†—ä½™å­˜å‚¨ï¼Œé¿å…å› ä¸»æ•°æ®å˜æ›´å¯¼è‡´åŽ†å²å¯è¯»æ€§ä¸‹é™ |
| å½’档联动 | ä¸šåŠ¡å•æ®å®¡æ‰¹å®ŒæˆåŽï¼Œä¸€é”®ç”Ÿæˆå¯¹åº”æ¡£æ¡ˆå¹¶å‘æ¥æºè¡¨å›žå†™æ¡£æ¡ˆ ID çš„æœºåˆ¶ |
| é€¾æœŸ | å€Ÿé˜…单处于"借出中"且归还期限早于当前日期的状态(实时计算,不落库) |
---
## äºŒã€æ€»ä½“技术架构
### 2.1 è®¾è®¡åŽŸåˆ™
| åŽŸåˆ™ | è½åœ°æ–¹å¼ |
|---|---|
| **平台化、可复用** | å®¡æ‰¹ã€é™„件、编码、字典、消息五类能力抽为平台级公共服务,业务模块只做组合调用,避免重复建设 |
| **模块化、低耦合** | Maven å¤šæ¨¡å—拆分,模块间通过 `-api` å­æ¨¡å—的接口通信,禁止跨模块直接访问对方的 Mapper/DO |
| **配置优先** | å®¡æ‰¹è§„则、单据状态、业务分类均以字典与流程配置驱动,前台按配置渲染,减少硬编码 |
| **强一致与幂等** | å…³é”®çŠ¶æ€å˜æ›´é‡‡ç”¨æ¡ä»¶æ›´æ–°ï¼ˆ`UPDATE ... WHERE status = æœŸæœ›å€¼`)保证并发安全;归档、提交等写操作均做幂等短路 |
| **权限前置** | æƒé™æ ¡éªŒä¸‹æ²‰åˆ°æŽ¥å£å±‚(注解式),敏感数据过滤下沉到查询层,双重保险 |
| **可审计** | å…³é”®æŽ¥å£è®°å½•操作日志,登录行为记录登录日志,单据流转保留完整审批记录 |
### 2.2 æ€»ä½“æž¶æž„
采用"单体多模块 + å‰åŽç«¯åˆ†ç¦»"架构,部署为一个后端服务实例(可按需横向扩展为多实例),前端为独立静态资源。
```
┌─────────────────────────────────────────────────────────────────────┐
│  æŽ¥å…¥å±‚   æµè§ˆå™¨ï¼ˆPC)       ä¼ä¸šå¾®ä¿¡ï¼ˆç§»åŠ¨ç«¯ï¼‰      ç¬¬ä¸‰æ–¹ç³»ç»Ÿï¼ˆOpenAPI)│
└──────────────────────────┬──────────────────────────────────────────┘
                           â”‚  HTTPS
┌──────────────────────────┴──────────────────────────────────────────┐
│  ç½‘关/接入  åå‘代理(Nginx) Â· ç»Ÿä¸€é‰´æƒè¿‡æ»¤å™¨ Â· è¯·æ±‚日志 Â· è·¨åŸŸæŽ§åˆ¶   â”‚
└──────────────────────────┬──────────────────────────────────────────┘
┌──────────────────────────┴──────────────────────────────────────────┐
│  åº”用层(业务模块)                                                    â”‚
│  ååŒåŠžå…¬(bpm) â”‚ è´¢åŠ¡æŠ¥é”€(finance) â”‚ ä¼šå‘˜ç®¡ç†(ums) â”‚ è¯¾é¢˜ç®¡ç†(research)│
│  æ¡£æ¡ˆç®¡ç†(archive) â”‚ ä¼ä¸šå¾®ä¿¡(wxcp) â”‚ ç³»ç»Ÿç®¡ç†(system)                 â”‚
└──────────────────────────┬──────────────────────────────────────────┘
┌──────────────────────────┴──────────────────────────────────────────┐
│  å¹³å°æœåŠ¡å±‚ï¼ˆframework)                                               â”‚
│  å·¥ä½œæµå¼•擎(Flowable) â”‚ é™„件中心 â”‚ ç¼–码规则 â”‚ å­—å…¸ â”‚ æ¶ˆæ¯é€šçŸ¥          â”‚
│  ç»Ÿä¸€é‰´æƒ â”‚ ç»Ÿä¸€å¼‚常与返回体 â”‚ Excel å¯¼å…¥å¯¼å‡º â”‚ æ“ä½œæ—¥å¿—/访问日志      â”‚
└──────────────────────────┬──────────────────────────────────────────┘
┌──────────────────────────┴──────────────────────────────────────────┐
│  æ•°æ®å±‚   MySQL 8.4.8(业务与流程数据) Â· Redis(缓存/令牌/会话)      â”‚
│          æœ¬åœ°ï¼å¯¹è±¡å­˜å‚¨ï¼ˆé™„件二进制)                                   â”‚
└─────────────────────────────────────────────────────────────────────┘
```
### 2.3 æŠ€æœ¯æ ˆé€‰åž‹
**后端**
| åˆ†ç±» | ç»„ä»¶ | ç‰ˆæœ¬ | é€‰åž‹è¯´æ˜Ž |
|---|---|---|---|
| è¯­è¨€ï¼è¿è¡Œæ—¶ | Java | 25 | é•¿æœŸæ”¯æŒç‰ˆæœ¬ï¼Œå…·å¤‡è™šæ‹Ÿçº¿ç¨‹èƒ½åŠ›ï¼Œåˆ©äºŽé«˜å¹¶å‘ IO åœºæ™¯ |
| åº”用框架 | Spring Boot | 4.1.0 | ç”Ÿæ€æˆç†Ÿã€ç»„件齐全,社区与企业支持周期长 |
| æž„建 | Maven(多模块) | 3.9+ | æ¨¡å—化拆分与统一依赖版本管理(`yudao-dependencies` BOM) |
| æŒä¹…层 | MyBatis-Plus | 3.5.16 | å…¼é¡¾ SQL å¯æŽ§æ€§ä¸Žå•表 CRUD æ•ˆçŽ‡ï¼Œæ”¯æŒé€»è¾‘åˆ é™¤ã€åˆ†é¡µã€ä¹è§‚é” |
| å·¥ä½œæµå¼•擎 | Flowable | 8.0.0 | å®Œæ•´å®žçް BPMN 2.0,支持会签、或签、依次审批、加签转派、流程图轨迹 |
| æ•°æ®åº“ | MySQL | 8.4.8 | ä¸»å¤‡éƒ¨ç½²ï¼Œä¸šåŠ¡ä¸Žæµç¨‹æ•°æ®åŒåº“ï¼Œä¾¿äºŽäº‹åŠ¡ä¸€è‡´æ€§ |
| ç¼“å­˜ | Redis | 6.x+ | ä»¤ç‰Œã€éªŒè¯ç ã€å­—典与热点数据缓存 |
| å¯¹è±¡å­˜å‚¨ | æœ¬åœ°ç£ç›˜ï¼S3 å…¼å®¹ | â€” | é€šè¿‡ç»Ÿä¸€é™„件中心抽象,存储配置可切换 |
| æŽ¥å£æ–‡æ¡£ | SpringDoc OpenAPI | éšæ¡†æž¶ | æŒ‰æ¨¡å—分组(GroupedOpenApi)自动生成在线接口文档 |
**前端**
| åˆ†ç±» | ç»„ä»¶ | ç‰ˆæœ¬ | é€‰åž‹è¯´æ˜Ž |
|---|---|---|---|
| åŸºç¡€æ¡†æž¶ | Vue | 3.5.35 | Composition API,组合式逻辑复用 |
| è„šæ‰‹æž¶ | Vben Admin | 5.7.0 | æˆç†Ÿçš„中后台方案,含权限、字典、布局、主题能力 |
| æž„建工具 | Vite | 8.0.10 | å†·å¯åŠ¨ä¸Žçƒ­æ›´æ–°å¿«ï¼Œæž„å»ºäº§ç‰©æŒ‰éœ€åˆ†åŒ… |
| è¯­è¨€ | TypeScript | 6.0.3 | å…¨é‡ç±»åž‹çº¦æŸï¼Œ`any` ç¦ç”¨ï¼Œé™ä½Žè¿è¡ŒæœŸé”™è¯¯ |
| UI ç»„件库 | Ant Design Vue | 4.2.6 | è¡¨æ ¼ã€è¡¨å•、弹窗组件完备,符合后台操作习惯 |
| çŠ¶æ€ç®¡ç† | Pinia | 3.0.4 | è½»é‡ã€ç±»åž‹å‹å¥½ï¼Œé…åˆæŒä¹…化插件保存登录态 |
| è·¯ç”± | Vue Router | 5.1.0 | åŠ¨æ€è·¯ç”±ç”±åŽç«¯èœå•é©±åŠ¨ï¼Œæ— éœ€å‰ç«¯æ‰‹å·¥ç»´æŠ¤è·¯ç”±è¡¨ |
| æ ·å¼ | Tailwind CSS | 4.3.0 | åŽŸå­åŒ–æ ·å¼ï¼Œç»Ÿä¸€é—´è·ä¸Žè‰²æ¿ |
| å›¾è¡¨ | ECharts | 6.1.0 | ç»Ÿè®¡çœ‹æ¿çš„饼图/柱图/趋势图 |
| æµç¨‹è®¾è®¡å™¨ | bpmn-js | 18.16.1 | åœ¨çº¿ç»˜åˆ¶ BPMN 2.0 æµç¨‹å›¾ |
| å¯Œæ–‡æœ¬ | TinyMCE / Tiptap | 7.9 / 3.26 | é€šçŸ¥ã€å…¬å‘Šã€æŒ‡å—正文编辑 |
| åŒ…管理 | pnpm | 11.0.9 | ä¾èµ–扁平化、安装快、磁盘占用低 |
### 2.4 åº”用模块划分
后端以 Maven å¤šæ¨¡å—组织,共 **30 ä¸ªæ¨¡å—**(含框架与依赖管理模块),业务模块与平台模块解耦:
| ç±»åˆ« | æ¨¡å— | èŒè´£ |
|---|---|---|
| ä¾èµ–管理 | `yudao-dependencies` | ç»Ÿä¸€ä¾èµ–版本(BOM),锁定上下游组件版本 |
| æ¡†æž¶ | `yudao-framework` | ç»Ÿä¸€è¿”回体、异常、鉴权、安全、Excel、日志、MyBatis æ‰©å±•、Redis å°è£…ç­‰ |
| æœåŠ¡ä¸»ç¨‹åº | `yudao-server` | å”¯ä¸€å¯åŠ¨æ¨¡å—ï¼Œèšåˆå…¨éƒ¨ä¸šåŠ¡æ¨¡å—ä¸Žé…ç½® |
| **本期七大子系统** | `yudao-module-bpm` | ååŒåŠžå…¬ï¼šå·¥ä½œæµå¼•æ“Žä¸Žæµç¨‹è®¾è®¡ï¼å®¡æ‰¹ä¸­å¿ƒ |
| | `yudao-module-finance` (+`-api`) | è´¢åŠ¡æŠ¥é”€ |
| | `yudao-module-ums` | ä¼šå‘˜ç®¡ç† |
| | `yudao-module-research` | è¯¾é¢˜ç®¡ç† |
| | `yudao-module-archive` (+`-api`) | æ¡£æ¡ˆç®¡ç†ä¸Žå½’档联动对外接口 |
| | `yudao-module-wxcp` | ä¼ä¸šå¾®ä¿¡ |
| | `yudao-module-system` | ç³»ç»Ÿç®¡ç†ï¼šèº«ä»½ã€æƒé™ã€å­—典、附件、消息、审计 |
| å¹³å°åŸºç¡€ | `yudao-module-infra` | åŸºç¡€è®¾æ–½ï¼šæ–‡ä»¶ã€ä»£ç ç”Ÿæˆã€å®šæ—¶ä»»åŠ¡ã€é…ç½®ã€API æ—¥å¿— |
| å…¶å®ƒä¸šåŠ¡ï¼ˆæœ¬æœŸä¸åœ¨èŒƒå›´ï¼‰ | `crm` / `erp` / `mes` / `mdm` / `hrm` / `srm` / `aftersales` / `bi` / `ai` / `im` / `member` / `qcreport` | å·¥åŽ‚ä¾§ä¸šåŠ¡ä¸Žå¹³å°å¢žå¼ºèƒ½åŠ›ï¼Œä¸Žæœ¬æ–¹æ¡ˆä¸ƒå¤§å­ç³»ç»Ÿå…±ç”¨åŒä¸€æŠ€æœ¯åº•åº§ï¼Œå¯æŒ‰éœ€å¯ç”¨ |
> æ¨¡å—划分的价值:本期七大子系统可独立编译、独立测试、独立升级;后续如需扩展(如新增采购、生产类业务),复用同一套审批、附件、档案能力即可,无需重构。
### 2.5 æ•°æ®æž¶æž„
当前运行库共 **430 å¼ è¡¨**,按业务域划分如下(节选本期相关部分):
| ä¸šåŠ¡åŸŸ | è¡¨å‰ç¼€ï¼æ•°é‡ | æœ¬æœŸç›¸å…³è¡¨ | è¯´æ˜Ž |
|---|---|---|---|
| ç³»ç»Ÿæ²»ç† | `system_*` | `system_users`、`system_role`、`system_menu`、`system_dict_type`、`system_dict_data`、`system_dept`、`system_post`、`system_storage_blob`、`system_storage_attachment`、`system_oauth2_client` ç­‰ | èº«ä»½ã€æƒé™ã€å­—典、附件元数据 |
| å·¥ä½œæµ | `bpm_*` + `act_*` | `bpm_category`、`bpm_process_definition_info`、`act_re_procdef`、`act_ru_task`、`act_hi_taskinst` ç­‰ | æµç¨‹é…ç½®ä¸Ž Flowable è¿è¡Œï¼åŽ†å²æ•°æ® |
| ååŒåŠžå…¬ | `bpm_oa_*` | `bpm_oa_notice`、`bpm_oa_leave` | é€šçŸ¥å…¬å‘Šã€è¯·å‡ |
| è´¢åŠ¡æŠ¥é”€ | `finance_*` | `finance_reimbursement`(财务-报销单)、`finance_reimbursement_item`(财务-报销明细) | ä¸»è¡¨ + æ˜Žç»†è¡¨ï¼Œä¸€å•多行费用 |
| ä¼šå‘˜ç®¡ç† | `ums_*` | `ums_application`(会员-入会申请单)、`ums_member`(会员) | ç”³è¯·å•与会员档案分离,申请通过后才生成会员 |
| è¯¾é¢˜ç®¡ç† | `research_*` | `research_guideline`(课题-指南)、`research_application`(课题-申报书)、`research_topic_review`(课题-专家评审明细)、`research_fund_record`(课题-经费拨付记录) | æŒ‡å— â†’ ç”³æŠ¥ â†’ è¯„审 â†’ ç«‹é¡¹ â†’ ç»è´¹ å…¨é“¾è·¯ç•™ç—• |
| ä¼ä¸šå¾®ä¿¡ | `wxcp_*` | `wxcp_activity`(企业微信活动)、`wxcp_activity_signup`(企业微信活动报名记录)、`wxcp_notice`(企业微信通知) | æ´»åŠ¨ä¸Žé€šçŸ¥åŠå…¶æŠ¥åæ˜Žç»† |
| æ¡£æ¡ˆç®¡ç† | `archive_*` | `archive_category`(档案-分类)、`archive_document`(档案-档案主表)、`archive_borrow`(档案-借阅单) | åˆ†ç±»æ ‘ + æ¡£æ¡ˆä¸»è¡¨ + å€Ÿé˜…单,借阅与归还合一 |
**数据建模要点**:
1. **主表 + æ˜Žç»†è¡¨**:报销单(`finance_reimbursement`)与报销明细(`finance_reimbursement_item`)分离,支持一单多行费用项与自动合计金额。
2. **申请与结果分离**:入会申请(`ums_application`)与会员(`ums_member`)分离,审批通过后才落会员,避免"未批准的申请人"污染会员库。
3. **快照冗余**:借阅单中冗余存储 `document_no`、`document_title`(档案编号与标题),即使档案改名,历史借阅记录仍保持当时的业务语义。
4. **附件不落列**:任何业务表均不设置 `file_url`/`file_urls` ç­‰æ–‡ä»¶åœ°å€åˆ—,文件与业务的关联统一通过中间表 `system_storage_attachment`(`record_type` + `record_id` + `storage_blob_id`)承载,支持一个单据多附件、多用途分类,且存储介质切换(本地/对象存储)不影响业务表结构。
5. **逻辑删除**:全表统一 `deleted` æ ‡è®°ï¼ŒåŽ†å²æ•°æ®å¯è¿½æº¯ã€å¯æ¢å¤ã€‚
6. **单租户部署**:框架具备多租户隔离能力,本期业务表按单租户模式部署,保留未来租户隔离的扩展空间。
### 2.6 éƒ¨ç½²æž¶æž„
| å±‚次 | éƒ¨ç½²å½¢æ€ | è¯´æ˜Ž |
|---|---|---|
| å‰ç«¯ | Nginx æ‰˜ç®¡é™æ€èµ„源 | ä¸ŽåŽç«¯åˆ†ç¦»éƒ¨ç½²ï¼Œå¯ç‹¬ç«‹å‘布;同一 Nginx åå‘代理 `/admin-api` è‡³åŽç«¯ |
| åŽç«¯ | å•个 Spring Boot æœåŠ¡å®žä¾‹ï¼ˆå¯å¤šå®žä¾‹æ¨ªå‘æ‰©å±•ï¼‰ | æ— çŠ¶æ€è®¾è®¡ï¼Œä¼šè¯ä¸Žä»¤ç‰Œç½®äºŽ Redis,多实例可共享 |
| æ•°æ®åº“ | MySQL 8.4.8 ä¸»å¤‡ | ä¸šåŠ¡è¡¨ä¸Ž Flowable æµç¨‹è¡¨åŒåº“,保证"业务状态 + æµç¨‹çŠ¶æ€"在同一事务内一致 |
| ç¼“å­˜ | Redis | ä»¤ç‰Œã€éªŒè¯ç ã€å­—典缓存 |
| å­˜å‚¨ | æœ¬åœ°ç£ç›˜æˆ– S3 å…¼å®¹å¯¹è±¡å­˜å‚¨ | ç»é™„件中心抽象,配置切换,业务代码无感 |
| æ“ä½œç³»ç»Ÿ | å…¼å®¹ Windows Server ä¸Žä¸»æµ Linux å‘行版 | åŽç«¯ JVM è·¨å¹³å°ï¼Œå‰ç«¯ä¸ºé™æ€èµ„源 |
---
## ä¸‰ã€æŠ€æœ¯æž¶æž„与集成
本章说明七大子系统并非七套独立系统,而是**共享平台能力、彼此数据贯通**的一个整体。以下逐项给出共享机制与集成方式。
### 3.1 ç»Ÿä¸€èº«ä»½è®¤è¯ä¸Žæƒé™ä½“ç³»
**认证**:基于 OAuth2 çš„令牌机制,支持账号密码登录、三方登录(`system_social_client` / `system_social_user`)。令牌与刷新令牌由 Redis æ‰¿è½½ï¼Œæ”¯æŒä»¤ç‰Œç®¡ç†ä¸Žå¼ºåˆ¶ä¸‹çº¿ï¼ˆ`/system/oauth2/token`)。
**授权**:采用"用户 â€” è§’色 â€” èœå•/权限点"三层 RBAC:
- ç”¨æˆ·å¯ç»‘定多个角色,角色可绑定多个菜单与权限点;
- æƒé™ç‚¹ä»¥å­—符串标识(形如 `archive:borrow:submit`),后端以注解式校验(`@PreAuthorize("@ss.hasPermission('...')")`)前置拦截,**无权限即拒绝调用,不依赖前端隐藏按钮**;
- å‰ç«¯æŒ‰æƒé™ç‚¹æŽ§åˆ¶æŒ‰é’®å¯è§æ€§ï¼Œä½œä¸ºä½“验优化而非安全边界。
**权限点规模**:本期六大业务子系统共定义 **68 ä¸ªæƒé™ç‚¹**,覆盖增、删、改、查、提交、导出、归档、推送、归还等全部动作(完整清单见附录 A)。其中 `archive:document:sensitive` ä¸ºç‹¬ç«‹çš„**敏感档案查阅权限**,用于密级管控(详见 3.1.3)。
**数据权限**:支持按部门、按用户维度的数据范围控制;"我的数据"类查询通过人员交接接口扩展可见范围,保证离岗人员的历史业务数据可被交接人接管,业务不中断。
**3.1.3 å¯†çº§ç®¡æŽ§ï¼ˆæ¡£æ¡ˆæ•æ„Ÿæ•°æ®éš”离)**
档案密级分三级:**公开(1)/内部(2)/保密(3)**。管控采取"两处落点、统一封装"策略,避免过滤逻辑散落导致漏网:
| è½ç‚¹ | ç®¡æŽ§è§„则 |
|---|---|
| è¯¦æƒ…读取 | è¯·æ±‚密级为"保密"的档案时,若当前用户不具备 `archive:document:sensitive` æƒé™ï¼Œç›´æŽ¥æ‹’绝并返回明确提示(指引联系管理员开通权限) |
| åˆ—表/导出 | ç”¨æˆ·ä¸å…·å¤‡è¯¥æƒé™æ—¶ï¼ŒæŸ¥è¯¢æ¡ä»¶è¢«å¼ºåˆ¶è¿½åŠ "密级不等于保密",保密档案在列表与导出结果中不可见 |
判定逻辑由服务层统一封装(密级过滤与密级可见性校验两个方法),**后续任何新增的档案读取入口都必须走同一封装**,从机制上杜绝越权。
**财务档案默认保密**:档案分类中"财务档案"分类的默认密级被预置为"保密(3)",因此由财务报销归档生成的档案自动继承保密级别,无需人工设置,符合财务凭证的合规要求。
### 3.2 ç»Ÿä¸€å·¥ä½œæµå¼•擎
**引擎**:Flowable 8.0.0,完整支持 BPMN 2.0。
**流程设计**:通过"流程模型"页在线绘制流程图(bpmn-js),支持用户任务、条件分支、并行网关、会签(多人同时审批)、或签(任一人审批即可)、依次审批(按顺序逐一审批)等节点模式;审批人可配置为指定角色、指定部门、指定用户、用户组、发起人自选、上级主管、连续多级主管等策略。
**配套配置**(协同办公—流程管理下的六个配置页):
| é…ç½®é¡¹ | ä½œç”¨ |
|---|---|
| æµç¨‹æ¨¡åž‹ | åœ¨çº¿ç»˜åˆ¶ä¸Žå‘布 BPMN æµç¨‹å›¾ |
| æµç¨‹è¡¨å• | é…ç½®æµç¨‹ç»‘定的表单;支持"自定义表单"(在线拖拽设计)与"业务表单"(直接绑定业务系统页面)两种模式 |
| æµç¨‹åˆ†ç±» | æŒ‰ä¸šåŠ¡åŸŸå½’ç±»æµç¨‹ï¼ˆå¦‚"课题立项审批""档案借阅审批"),支持停用 |
| ç”¨æˆ·åˆ†ç»„ | å°†å®¡æ‰¹äººç»´æŠ¤ä¸ºé€»è¾‘分组(如"档案管理员组""分管领导组"),流程中按分组派发任务 |
| æµç¨‹ç›‘听器 | é€šè¿‡ Java ç±»ç›‘听器实现"流程事件 â†’ ä¸šåŠ¡å›žå†™",是本方案业务集成的核心技术点(详见下节) |
| æµç¨‹è¡¨è¾¾å¼ | å®šä¹‰å¯å¤ç”¨çš„审批人表达式与条件表达式 |
**业务附件配置**:可针对具体流程与节点配置附件上传要求,实现"哪些环节必须上传材料"的流程级约束。
**审批中心**:提供"我的待办""我的已办""我发起的""抄送我的"四类视图。每个任务支持:同意、驳回、退回、加签、转派、评论、查看流程图与流转轨迹。
**流程实例与任务管理**:面向管理员,可查询运行中/已完成实例、定位当前节点与审批人、查看完整审批记录与流程轨迹、执行终止等干预操作。
**流程规模**:运行库已部署 **39 æ¡æµç¨‹å®šä¹‰**,其中本期业务直接相关的四条为:
| æµç¨‹åç§° | æµç¨‹å®šä¹‰ Key | ç‰ˆæœ¬ | å®¡æ‰¹å±‚级 |
|---|---|---|---|
| å…¥ä¼šå®¡æ‰¹ | `ums_membership_approve` | 1 | æŒ‰å…¥ä¼šå®¡æ‰¹æµç¨‹é…ç½® |
| è¯¾é¢˜ç«‹é¡¹å®¡æ‰¹ | `research_topic_approve` | 1 | å¤šçº§å®¡æ‰¹ï¼ˆç”³æŠ¥ä¹¦ä¸‰çº§æµè½¬ï¼‰ |
| æŠ¥é”€å®¡æ‰¹ | `finance_reimbursement_approve` | 1 | æŒ‰æŠ¥é”€å®¡æ‰¹æµç¨‹é…ç½® |
| æ¡£æ¡ˆå€Ÿé˜…审批 | `archive_borrow_approve` | 1 | **两级**:档案管理员审核 â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ |
**业务集成机制(流程监听器回写)**:这是本方案的关键技术设计。业务单据提交审批时,通过流程实例接口创建流程实例并以业务单据 ID ä½œä¸º"业务 Key"(`businessKey`);流程状态变化时,由该业务模块注册的**状态事件监听器**接收事件,按 `businessKey` åæŸ¥ä¸šåŠ¡å•æ®å¹¶å›žå†™çŠ¶æ€ã€‚æœºåˆ¶ç‰¹ç‚¹ï¼š
1. **多监听器并行**:不同流程 Key æ³¨å†Œå„自的监听器,互不干扰(如报销监听器与档案借阅监听器订阅不同流程);
2. **分支容错**:监听器内部对"审批通过""审批驳回""撤回/取消"三类结果分别处理,每类分支独立捕获异常,单次回写失败不影响其他业务;
3. **先解流程后建单**:提交前先解析可用的流程定义,若未配置则直接拒绝并给出可操作的提示(指引到"流程定义"页面发布),避免产生无流程的孤儿草稿;
4. **防重复借出的并发控制**(档案借阅场景):审批通过时以**条件更新**方式将档案状态由"在库"改为"借出中"(`UPDATE ... WHERE id = ? AND status = 10`)。若档案已被其他借阅单占用,更新影响行数为 0,此时抛出明确业务异常并整体回滚借阅单的状态回写,**保证不会出现"两份借阅单同时借出同一档案"的脏数据,且失败信息对用户可见**(提示档案已被占用及占用的借阅单号)。
### 3.3 ç»Ÿä¸€é™„件中心
所有业务附件统一走系统管理的附件中心,**业务表零文件字段**:
| æ­¥éª¤ | æŽ¥å£ | è¯´æ˜Ž |
|---|---|---|
| 1. ä¸Šä¼  | `POST /system/storage-blob/upload` | ä¸Šä¼ æ–‡ä»¶å¾—到文件 ID(`blobId`) |
| 2. ç»‘定 | `POST /system/storage-attachment/bind` | ä»¥ `recordType`(业务标识)+ `recordId`(单据 ID)+ `blobId` å»ºç«‹å…³è” |
| 3. æŸ¥è¯¢ | `GET /system/storage-attachment/list` | æŒ‰ `recordType` + `recordId` å–回附件列表 |
| 4. åˆ é™¤ | `DELETE /system/storage-attachment/delete` | è§£é™¤å…³è” |
**recordType å‘½åè§„范**:`{模块前缀}_{业务表名}`,本期使用如下:
| ä¸šåŠ¡å¯¹è±¡ | recordType |
|---|---|
| æ¡£æ¡ˆ | `archive_document` |
| è´¢åŠ¡æŠ¥é”€å•ï¼ˆå‘ç¥¨ï¼‰ | `finance_reimbursement` |
| å…¥ä¼šç”³è¯·ï¼ˆå…¥ä¼šææ–™ï¼‰ | `ums_application` |
| ä¼šå‘˜ï¼ˆä¼šå‘˜ææ–™ï¼‰ | `ums_member` |
| è¯¾é¢˜æŒ‡å— | `research_guideline` |
| è¯¾é¢˜ç”³æŠ¥ä¹¦ | `research_application` |
| è¯¾é¢˜ä¸“家评审明细 | `research_topic_review` |
| è¯¾é¢˜ç»è´¹æ‹¨ä»˜å‡­è¯ | `research_fund_record` |
| ä¼ä¸šå¾®ä¿¡æ´»åŠ¨æŠ¥åææ–™ | `wxcp_activity_signup` |
该机制的价值:一次投入全局复用——多附件、多用途、存储介质可切换、权限可独立控制,且业务表结构不受文件存储方式影响。
### 3.4 ç»Ÿä¸€ç¼–码规则
单据编号由系统管理的"编码规则"统一生成(`/system/auto-code`),采用"前缀 + æ—¥æœŸ + æµæ°´å·"格式,保证唯一性与业务可读性。本期各单据编号前缀如下:
| å•据 | å‰ç¼€ | ç¤ºä¾‹ |
|---|---|---|
| å…¥ä¼šç”³è¯·å• | `SQ` | `SQ20260928000001` |
| ä¼šå‘˜ | `HY` | `HY20260928000001` |
| è¯¾é¢˜æŒ‡å— | `GL` | `GL20260928000001` |
| è¯¾é¢˜ç”³æŠ¥ä¹¦ | `SB` | `SB20260928000001` |
| è¯¾é¢˜ç«‹é¡¹ç¼–号 | `LX` | `LX20260928000001` |
| è´¢åŠ¡æŠ¥é”€å• | `BX` | `BX20260928000001` |
| æ¡£æ¡ˆç¼–号 | `DA` | `DA20260929000001` |
| æ¡£æ¡ˆå€Ÿé˜…单号 | `JY` | `JY20260929000001` |
### 3.5 ç»Ÿä¸€æ¶ˆæ¯ä¸Žé€šçŸ¥
平台提供三条触达通道,按场景选择:
| é€šé“ | æ‰¿è½½ä½ç½® | å…¸åž‹åœºæ™¯ |
|---|---|---|
| ç«™å†…ä¿¡ | ç³»ç»Ÿç®¡ç† â†’ æ¶ˆæ¯ä¸­å¿ƒ â†’ ç«™å†…信管理 | å®¡æ‰¹å¾…办提醒、系统通知 |
| çŸ­ä¿¡ | ç³»ç»Ÿç®¡ç† â†’ æ¶ˆæ¯ä¸­å¿ƒ â†’ çŸ­ä¿¡ç®¡ç† | é‡è¦èŠ‚ç‚¹æé†’ï¼ˆå¯å¯¹æŽ¥è¿è¥å•†é€šé“ï¼‰ |
| é‚®ä»¶ | ç³»ç»Ÿç®¡ç† â†’ æ¶ˆæ¯ä¸­å¿ƒ â†’ é‚®ç®±ç®¡ç† | æ­£å¼é€šçŸ¥ã€ææ–™å‘送 |
| **企业微信推送** | ä¼ä¸šå¾®ä¿¡ â†’ é€šçŸ¥ç®¡ç†ï¼æ´»åŠ¨ç®¡ç† | é¢å‘成员的活动与通知推送,支持"全员/指定部门/指定成员"三种范围 |
**企业微信通道能力**:以官方企业微信 SDK å¯¹æŽ¥ï¼Œå®žçŽ°ï¼š
- **通讯录同步**:实时拉取企业微信部门树与成员列表(`/wxcp/member/department/tree`、`/wxcp/member/list`),供推送时选人;
- **消息推送**:按范围推送通知或活动,返回推送结果(成功/失败人数);
- **回调接收**:接收企业微信的 URL éªŒè¯ä¸Žæ¶ˆæ¯ï¼äº‹ä»¶æŽ¨é€ï¼ˆè¿›å…¥åº”用、关注、通讯录变更、文本消息),支持加密签名校验与消息解密回执;
- **报名二维码**:为活动生成报名二维码(PNG,可直接展示或打印),二维码指向在线报名页,成员扫码即可报名。
### 3.6 è·¨æ¨¡å—归档联动(本方案核心亮点)
**问题**:课题申报材料、会员材料、财务凭证天然产生于各自业务模块,若无归集机制,必然形成"业务系统里有一份、档案柜里另一份、检索时两边都找不到"的分裂局面。
**解决思路**:由档案模块对外暴露一组标准接口,业务模块在合适的时点调用,一次性完成"生成档案 + ç»‘定附件 + å›žå†™æ¥æºæ ‡è®°"。
**接口设计**(档案模块对外 RPC æŽ¥å£ï¼ŒåŒ…路径 `archive/api/document`):
| æŽ¥å£ | è¯­ä¹‰ |
|---|---|
| æŒ‰æ¥æºå½’æ¡£ | å…¥å‚含来源类型、来源单据 ID、来源单号、来源标题、目标档案分类编码、密级、附件 ID åˆ—表等;返回生成的档案 ID |
| æ˜¯å¦å·²å½’æ¡£ | æŒ‰æ¥æºç±»åž‹ + æ¥æºå•据 ID æŸ¥è¯¢æ˜¯å¦å·²å½’æ¡£ |
| æŒ‰æ¥æºæŸ¥è¯¢æ¡£æ¡ˆ | è¿”回已归档档案的编号、标题、密级、载体、状态、归档日期等 |
**归档幂等保障(三级防护)**:
1. **业务侧短路**:先按"来源类型 + æ¥æºå•据 ID"查询是否已存在档案,命中则直接返回既有档案 ID,不重复生成;
2. **来源表回写标记**:归档成功后,向来源业务表回写档案 ID(`archive_document_id`)。前端据此显示"已归档"并隐藏归档按钮;
3. **重复归档拦截**:若绕过前两级防护重复归档,仍会抛出明确异常,提示来源单号与已生成的档案编号(便于用户直接定位并跳转)。
**三方归档联动明细**:
| æ¥æºæ¨¡å— | è§¦å‘条件 | ç›®æ ‡æ¡£æ¡ˆåˆ†ç±» | å¯†çº§ | å¸¦å…¥å†…容 | æ¥æºè¡¨å›žå†™ |
|---|---|---|---|---|---|
| è¯¾é¢˜ç®¡ç† | ç”³æŠ¥ä¹¦çŠ¶æ€ä¸º"已立项" | è¯¾é¢˜æ¡£æ¡ˆ | å†…部(2) | ç«‹é¡¹ç¼–号作为来源单号;申报书附件随档带入 | `research_application.archive_document_id` |
| ä¼šå‘˜ç®¡ç† | ä¼šå‘˜çŠ¶æ€ä¸º"正常" | ä¼šå‘˜æ¡£æ¡ˆ | å†…部(2) | ä¼šå‘˜ç¼–号作为来源单号;会员材料随档带入 | `ums_member.archive_document_id` |
| è´¢åŠ¡æŠ¥é”€ | æŠ¥é”€å•状态为"已通过" | è´¢åŠ¡æ¡£æ¡ˆ | **保密(3)** | æŠ¥é”€å•号作为来源单号;发票附件随档带入 | `finance_reimbursement.archive_document_id` |
**财务场景的特殊处理(叠加而非替换)**:财务报销原有的"归档"动作负责将报销单自身状态由"已通过"改为"已归档"并记录归档时间;本方案在此基础上**追加**生成财务档案。两者写入不同数据表、语义正交,且在同一事务内完成——即:**归档成功 = æŠ¥é”€å•状态已归档 + è´¢åŠ¡æ¡£æ¡ˆå·²ç”Ÿæˆï¼ŒäºŒè€…è¦ä¹ˆåŒæ—¶æˆåŠŸï¼Œè¦ä¹ˆåŒæ—¶å›žæ»š**,不会出现"报销单归档了但档案没生成"的半成品。
**归档带来的业务价值**:
- å½’档后的材料进入统一的档案检索(按标题、关键词、来源单号、分类、密级、状态多维检索);
- è´¢åŠ¡ç±»æ¡£æ¡ˆè‡ªåŠ¨è½å…¥"保密"密级,天然受权限隔离保护;
- æ¡£æ¡ˆå¯è¢«å€Ÿé˜…并走两级审批,形成"归档 â€” å€Ÿé˜… â€” å½’还"的完整闭环;
- æ¡£æ¡ˆç»Ÿè®¡å¯å®žæ—¶åæ˜ å„类档案的存量与流转。
### 3.7 æ¨¡å—间数据流总览
```
                       â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
   å…¥ä¼šç”³è¯· â”€â”€å®¡æ‰¹â”€â”€â–¶  â”‚      ä¼šå‘˜ç®¡ç†         â”‚
   (ums_application)   â”‚      (ums_member)    â”‚
                       â””──────────┬───────────┘
                                  â”‚ å½’档(会员材料)
                       â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
   è¯¾é¢˜æŒ‡å— â”€â–¶ è¯¾é¢˜ç”³æŠ¥ â”‚                      â”‚
   â”€â–¶ ä¸“家评审 â”€â–¶ ç«‹é¡¹  â”‚      æ¡£æ¡ˆç®¡ç†         â”‚
   (research_*)        â”‚   (archive_document) â”‚
        â”‚              â”‚                      â”‚
        â”‚ å½’档(课题材料)│  åˆ†ç±»æ ‘ / æ¡£æ¡ˆä¸»è¡¨    â”‚
        â””─────────────▶│  å€Ÿé˜…单 / ç»Ÿè®¡        â”‚
                       â””──────────▲───────────┘
                                  â”‚ å½’档(财务凭证 â†’ ä¿å¯†ï¼‰
                       â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”´â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
   æŠ¥é”€å• â”€â”€å®¡æ‰¹â”€â”€â–¶     â”‚      è´¢åŠ¡æŠ¥é”€         â”‚
   (finance_*)         â””──────────────────────┘
        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
        â”‚  ååŒåŠžå…¬ï¼ˆBPM å®¡æ‰¹å¼•擎)              â”‚
        â”‚  ä¸ºä»¥ä¸Šå››ç±»ä¸šåŠ¡æä¾›ç»Ÿä¸€çš„å®¡æ‰¹æµè½¬èƒ½åŠ›   â”‚
        â”‚  å…¥ä¼šå®¡æ‰¹ / è¯¾é¢˜ç«‹é¡¹å®¡æ‰¹ / æŠ¥é”€å®¡æ‰¹ /   â”‚
        â”‚  æ¡£æ¡ˆå€Ÿé˜…审批                          â”‚
        â””──────────────────────────────────────┘
        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
        â”‚  ä¼ä¸šå¾®ä¿¡ï¼šå°†é€šçŸ¥ / æ´»åŠ¨æŽ¨é€è‡³æˆå‘˜      â”‚
        â”‚  ç³»ç»Ÿç®¡ç†ï¼šèº«ä»½ / æƒé™ / å­—å…¸ / é™„ä»¶ /   â”‚
        â”‚  ç¼–码 / æ¶ˆæ¯ / å®¡è®¡ï¼Œæ”¯æ’‘以上全部       â”‚
        â””──────────────────────────────────────┘
```
**四条贯通链**,可直接作为演示与验收主线:
| é“¾è·¯ | æµè½¬è¿‡ç¨‹ |
|---|---|
| ä¼šå‘˜é“¾ | æäº¤å…¥ä¼šç”³è¯· â†’ BPM å…¥ä¼šå®¡æ‰¹ â†’ å®¡æ‰¹é€šè¿‡ç”Ÿæˆä¼šå‘˜ â†’ ä¸€é”®å½’档生成会员档案 â†’ å€Ÿé˜…审批 â†’ å½’还 |
| è¯¾é¢˜é“¾ | å‘布课题指南 â†’ æäº¤è¯¾é¢˜ç”³æŠ¥ä¹¦ â†’ ç«‹é¡¹å®¡æ‰¹ï¼ˆå¤šçº§ï¼‰â†’ ä¸“家评审打分 â†’ ç«‹é¡¹ â†’ ç»è´¹æ‹¨ä»˜ï¼ˆå¯å…³è”报销)→ å‘布立项通知 â†’ ä¸€é”®å½’档生成课题档案 |
| è´¢åС链 | åˆ›å»ºæŠ¥é”€å•(含多条明细与发票附件)→ æäº¤æŠ¥é”€å®¡æ‰¹ â†’ å®¡æ‰¹é€šè¿‡ â†’ å½’档(报销单状态置"已归档" + ç”Ÿæˆä¿å¯†çº§è´¢åŠ¡æ¡£æ¡ˆï¼‰ |
| è§¦è¾¾é“¾ | åˆ›å»ºä¼ä¸šå¾®ä¿¡æ´»åŠ¨ï¼é€šçŸ¥ â†’ é€‰æ‹©æŽ¨é€èŒƒå›´ï¼ˆå…¨å‘˜ï¼éƒ¨é—¨ï¼æˆå‘˜ï¼‰â†’ æŽ¨é€ â†’ ä¼šå‘˜æ‰«ç æŠ¥å â†’ ç­¾åˆ° â†’ æŸ¥çœ‹æŠ¥åç»Ÿè®¡ |
### 3.8 å¯¹å¤–开放接口规范
| è§„范项 | å†…容 |
|---|---|
| é£Žæ ¼ | RESTful,统一前缀 `/admin-api` |
| æ–¹æ³•语义 | `GET` æŸ¥è¯¢ã€`POST` æ–°å¢žã€`PUT` æ›´æ–°ã€`DELETE` åˆ é™¤ |
| ç»Ÿä¸€è¿”回体 | `{ "code": 0, "data": ..., "msg": "..." }`;`code = 0` è¡¨ç¤ºæˆåŠŸï¼Œéž 0 ä¸ºä¸šåŠ¡é”™è¯¯ç å¹¶é™„å¯è¯»æç¤º |
| åˆ†é¡µ | ç»Ÿä¸€ `pageNo` / `pageSize` å…¥å‚,返回 `list` + `total` |
| é‰´æƒ | è¯·æ±‚头携带 `Authorization: Bearer <token>` |
| æŽ¥å£æ–‡æ¡£ | æŒ‰æ¨¡å—分组自动生成 OpenAPI æ–‡æ¡£ï¼Œå¯åœ¨çº¿è°ƒè¯• |
| é”™è¯¯æç¤º | éµå¾ª"具体维度 + åŒæ–¹å®žé™…值 + å¯æ‰§è¡ŒåŠ¨ä½œ"原则,禁止笼统提示,便于运维与用户自助排障 |
### 3.9 é›†æˆæ¸…单汇总
| é›†æˆå¯¹è±¡ | é›†æˆæ–¹å¼ | è¯´æ˜Ž |
|---|---|---|
| ä¼ä¸šå¾®ä¿¡ | å®˜æ–¹ SDK(服务端 API + å›žè°ƒï¼‰ | é€šè®¯å½•同步、消息推送、活动报名二维码、回调接收 |
| çŸ­ä¿¡æœåС商 | å¯é…ç½®çŸ­ä¿¡é€šé“ | çŸ­ä¿¡é€šçŸ¥ï¼ˆæŽ¥å£é¢„留,按需开通) |
| é‚®ä»¶æœåС噍 | SMTP | é‚®ä»¶é€šçŸ¥ |
| å¯¹è±¡å­˜å‚¨ | æœ¬åœ°ï¼S3 å…¼å®¹ | é™„件二进制存储,配置切换 |
| ç»Ÿä¸€èº«ä»½ï¼ˆå¯é€‰ï¼‰ | OAuth2 / ä¸‰æ–¹ç™»å½• | å¯å¯¹æŽ¥æ—¢æœ‰è´¦å·ä½“系实现单点登录 |
| å¼€æ”¾èƒ½åŠ› | OpenAPI + API å¯†é’¥ç®¡ç† | ä¾›ç¬¬ä¸‰æ–¹ç³»ç»ŸæŒ‰æƒé™è°ƒç”¨å¹³å°æŽ¥å£ |
---
## å››ã€åŠŸèƒ½æ–¹æ¡ˆ
以下逐个子系统给出功能清单。每节包含:模块定位、功能清单、关键数据项与状态机、与其他模块的协同关系。
### 4.1 ååŒåŠžå…¬ï¼ˆ`/bpm`)
#### 4.1.1 æ¨¡å—定位
全平台的**审批与流程底座**。它不承载具体业务数据,而是为财务报销、会员管理、课题管理、档案借阅等业务提供"可配置、可追溯、可干预"的流程流转能力,并提供通知公告与轻量 OA åº”用。
#### 4.1.2 åŠŸèƒ½æ¸…å•
**(1)流程管理**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æµç¨‹æ¨¡åž‹ | åœ¨çº¿å¯è§†åŒ–绘制 BPMN 2.0 æµç¨‹å›¾å¹¶å‘布;支持用户任务、条件分支、并行网关;支持会签(多人同时审批)、或签(任一人审批)、依次审批(按顺序逐一审批)等多人审批模式 |
| æµç¨‹è¡¨å• | é…ç½®æµç¨‹ç»‘定的表单,支持两种模式:① è‡ªå®šä¹‰è¡¨å•——在线拖拽设计字段,无需开发;② ä¸šåŠ¡è¡¨å•â€”â€”ç›´æŽ¥ç»‘å®šä¸šåŠ¡ç³»ç»Ÿæ—¢æœ‰é¡µé¢ï¼Œä¸šåŠ¡æ•°æ®ä¸Žæµç¨‹æ•°æ®åŒå±å±•ç¤º |
| æµç¨‹åˆ†ç±» | æŒ‰ä¸šåŠ¡åŸŸå¯¹æµç¨‹å½’ç±»ï¼ˆå¦‚"入会审批""课题立项审批""报销审批""档案借阅审批"),支持启用/停用 |
| ç”¨æˆ·åˆ†ç»„ | å°†å®¡æ‰¹äººç»´æŠ¤ä¸ºé€»è¾‘分组,流程中按分组派发任务,人员变动时只需调整分组,无需逐条修改流程 |
| æµç¨‹ç›‘听器 | é…ç½®æµç¨‹äº‹ä»¶ç›‘听器(Java ç±»ï¼äº‹ä»¶ç›‘听),实现流程状态变化时自动回写业务单据状态,是业务集成的核心机制 |
| æµç¨‹è¡¨è¾¾å¼ | å®šä¹‰å¯å¤ç”¨çš„审批人表达式与条件表达式 |
| ä¸šåŠ¡é™„ä»¶é…ç½® | é’ˆå¯¹æµç¨‹ä¸ŽèŠ‚ç‚¹é…ç½®é™„ä»¶ä¸Šä¼ è¦æ±‚ï¼Œå®žçŽ°"哪些环节必须上传材料"的流程级约束 |
| æµç¨‹å®žä¾‹ | æŸ¥è¯¢è¿è¡Œä¸­ä¸Žå·²å®Œæˆçš„æµç¨‹å®žä¾‹ï¼ŒæŸ¥çœ‹å½“前节点、审批人、流转轨迹与历史记录;支持终止等管理干预 |
| æµç¨‹ä»»åŠ¡ | æŸ¥è¯¢å…¨éƒ¨æµç¨‹ä»»åŠ¡ï¼Œæ”¯æŒå®¡æ‰¹ï¼ˆåŒæ„ï¼é©³å›žï¼é€€å›žï¼‰ã€åŠ ç­¾ã€è½¬æ´¾ã€æŠ„é€ç­‰æ“ä½œ |
**(2)审批中心**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æˆ‘的待办 | åˆ—出当前用户待处理的任务,可直接审批;支持批量操作 |
| æˆ‘的已办 | åˆ—出当前用户已处理的任务,可回溯查看审批意见与耗时 |
| æˆ‘发起的 | åˆ—出当前用户发起的流程,可跟踪进度、撤回未完成流程 |
| æŠ„送我的 | åˆ—出抄送给当前用户的流程,可查阅但不参与审批 |
| æµç¨‹è¯¦æƒ… | å±•示业务表单内容 + æµç¨‹å›¾è½¨è¿¹ + é€èŠ‚ç‚¹å®¡æ‰¹è®°å½•ï¼ˆå®¡æ‰¹äººã€æ—¶é—´ã€æ„è§ã€ç»“æžœï¼‰ |
**(3)通知公告**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| å…¬å‘Šç®¡ç† | å…¬å‘Šçš„创建、编辑、发布、撤回,支持富文本正文与附件 |
| é˜…读回执 | è®°å½•公告的阅读情况,便于确认关键通知已触达 |
**(4)OA åº”用**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| è¯·å‡ç”³è¯· | æä¾›éƒ¨é—¨è¯·å‡ç”³è¯·ä¸Žå®¡æ‰¹çš„æ ‡å‡†ç¤ºä¾‹ï¼ŒéªŒè¯æµç¨‹å¼•擎的通用性;可作为其它轻量 OA å•据的模板 |
**(5)智能能力入口**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| å¤§æ¨¡åž‹ API å¯†é’¥ç®¡ç† | ç»´æŠ¤å¤§æ¨¡åž‹æœåŠ¡å•†çš„æŽ¥å…¥å¯†é’¥ |
| æ¨¡åž‹ç®¡ç† | ç»´æŠ¤å¯ç”¨çš„大模型清单与参数配置 |
| çŸ¥è¯†åº“管理 | æž„建业务知识库,用于智能问答与文本辅助生成 |
> è¯´æ˜Žï¼šä¸Šè¿°æ™ºèƒ½èƒ½åŠ›ä¸ºå¹³å°å¢žå¼ºé¡¹ï¼Œå¯æŒ‰æ‹›æ ‡äººè¦æ±‚å¯ç”¨æˆ–å…³é—­ï¼Œä¸å½±å“ä¸ƒå¤§å­ç³»ç»Ÿçš„æ ¸å¿ƒä¸šåŠ¡åŠŸèƒ½ã€‚
#### 4.1.3 å…³é”®è®¾è®¡
| è®¾è®¡ç‚¹ | è¯´æ˜Ž |
|---|---|
| è¡¨å•双模 | è‡ªå®šä¹‰è¡¨å•适合简单审批(如请假);业务表单适合复杂业务(如报销单、申报书),直接复用业务页面,避免重复开发表单 |
| å®¡æ‰¹äººç­–ç•¥ | æ”¯æŒè§’色、部门、用户、用户组、发起人自选、上级主管、连续多级主管七类策略,覆盖学会常见审批关系 |
| ç»“果回写 | æµç¨‹ç»“果通过监听器回写业务单据,业务模块无需轮询,状态实时一致 |
| æµç¨‹å¯å¹²é¢„ | ç®¡ç†å‘˜å¯æŸ¥çœ‹å…¨éƒ¨å®žä¾‹ä¸Žä»»åŠ¡ï¼Œå¿…è¦æ—¶ç»ˆæ­¢æµç¨‹ï¼Œé¿å…æµç¨‹å¡æ­»å¯¼è‡´ä¸šåŠ¡åœæ»ž |
#### 4.1.4 ä¸Žå…¶ä»–模块的协同
协同办公是"被依赖方":财务报销、会员管理、课题管理、档案借阅均在此登记流程定义并复用其审批能力。已部署的相关流程定义见 3.2 èŠ‚ã€‚
---
### 4.2 è´¢åŠ¡æŠ¥é”€ï¼ˆ`/finance`)
#### 4.2.1 æ¨¡å—定位
学会经费支出的**申请、审批与归档**闭环。解决纸质报销"审批层级靠人工判断、发票无法关联、凭证无法归档"的问题。
#### 4.2.2 åŠŸèƒ½æ¸…å•
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æŠ¥é”€å•新增 | å½•入报销标题、申请日期、报销事由;添加多条报销明细(费用类型、金额、发生日期、说明),系统自动合计报销总金额 |
| æŠ¥é”€æ˜Žç»† | ä¸€å•多行费用项,费用类型由字典驱动:差旅、办公、招待、交通、其他 |
| å‘票附件上传 | é€šè¿‡ç»Ÿä¸€é™„件中心上传发票影像,支持多张;列表与详情显示"是否上传发票"标识 |
| æŠ¥é”€å•编辑 | ä¿®æ”¹å•据与明细;按状态约束可编辑范围(审批中/已通过/已归档不允许编辑) |
| æŠ¥é”€å•删除 | æŒ‰çŠ¶æ€çº¦æŸå¯åˆ é™¤èŒƒå›´ï¼Œé¿å…å·²æµè½¬å•æ®è¢«è¯¯åˆ  |
| åˆ†é¡µæŸ¥è¯¢ä¸Žæ£€ç´¢ | æŒ‰æŠ¥é”€å•号、标题、状态、申请人、申请日期区间检索;分页展示 |
| Excel å¯¼å‡º | æŒ‰å½“前检索条件导出报销单明细,字段含单号、标题、金额、状态、申请人、申请日期、归档时间等;导出动作记录操作日志 |
| æäº¤å®¡æ‰¹ | é€‰æ‹©å¯ç”¨çš„æŠ¥é”€å®¡æ‰¹æµç¨‹å¹¶æäº¤ï¼Œæµç¨‹å®žä¾‹åˆ›å»ºåŽå•据进入"审批中" |
| å®¡æ‰¹ç»“果回写 | å®¡æ‰¹é€šè¿‡ â†’ "已通过";审批驳回 â†’ "已驳回"(记录驳回原因);撤回 â†’ å›žåˆ°"草稿" |
| å½’æ¡£ | å¯¹"已通过"的报销单执行归档:**报销单状态置"已归档"并记录归档时间**,同时**生成保密级财务档案**并把发票附件随档带入;成功后提示"已归档并生成档案【档案编号】" |
#### 4.2.3 å…³é”®æ•°æ®é¡¹ä¸ŽçŠ¶æ€æœº
**关键数据项**:报销单号、报销标题、报销总金额、状态、申请人、申请日期、流程实例 ID、归档时间、归档生成的档案 ID、备注、报销明细列表、发票附件列表。
**状态机**:
```
草稿(0) â”€â”€æäº¤å®¡æ‰¹â”€â”€â–¶ å®¡æ‰¹ä¸­(15)
   â–²                    â”œâ”€â”€ å®¡æ‰¹é€šè¿‡ â”€â”€â–¶ å·²é€šè¿‡(20) â”€â”€å½’档──▶ å·²å½’æ¡£(40)
   â”‚                    â”œâ”€â”€ å®¡æ‰¹é©³å›ž â”€â”€â–¶ å·²é©³å›ž(35)(可修改后重新提交)
   â””──撤回──────────────┘
```
| çŠ¶æ€å€¼ | åç§° | å¯æ‰§è¡ŒåŠ¨ä½œ |
|---|---|---|
| 0 | è‰ç¨¿ | ç¼–辑、删除、提交审批 |
| 15 | å®¡æ‰¹ä¸­ | æ’¤å›žã€æŸ¥çœ‹å®¡æ‰¹è¿›åº¦ |
| 20 | å·²é€šè¿‡ | å½’档、查看、导出 |
| 35 | å·²é©³å›ž | ç¼–辑、删除、重新提交 |
| 40 | å·²å½’æ¡£ | æŸ¥çœ‹ã€å¯¼å‡ºï¼ˆä¸å¯å†ä¿®æ”¹ï¼‰ |
#### 4.2.4 ä¸Žå…¶ä»–模块的协同
| ååŒå¯¹è±¡ | ååŒå†…容 |
|---|---|
| ååŒåŠžå…¬ | å¤ç”¨æŠ¥é”€å®¡æ‰¹æµç¨‹å¼•擎,审批结果经监听器回写单据状态 |
| æ¡£æ¡ˆç®¡ç† | å½’档时生成保密级财务档案,并向报销单回写档案 ID |
| ç³»ç»Ÿç®¡ç† | ç”³è¯·äººå–自身份体系;发票附件走统一附件中心;金额与单据记录操作日志 |
| è¯¾é¢˜ç®¡ç† | è¯¾é¢˜ç»è´¹æ‹¨ä»˜å¯å…³è”报销流程,形成"拨付申请 â†’ æŠ¥é”€å®¡æ‰¹"的支出凭证链 |
---
### 4.3 ä¼šå‘˜ç®¡ç†ï¼ˆ`/ums`)
#### 4.3.1 æ¨¡å—定位
学会主体(个人会员与单位会员)的**全生命周期管理**:从入会申请、审批、建档到名录与通讯录维护。
#### 4.3.2 åŠŸèƒ½æ¸…å•
**(1)入会申请**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| å…¥ä¼šç”³è¯·æ–°å¢ž | æŒ‰ä¼šå‘˜ç±»åž‹ï¼ˆä¸ªäººä¼šå‘˜ï¼å•位会员)动态录入不同字段组合 |
| å·®å¼‚化字段 | ä¸ªäººä¼šå‘˜ï¼šå§“名、工作单位、职称、研究方向;单位会员:单位名称、法定代表人、统一社会信用代码、单位简介、联系人 |
| å…¬å…±å­—段 | è”系电话、邮箱、申请等级、所属地区、所属行业、申请说明 |
| ç”³è¯·ææ–™ä¸Šä¼  | é€šè¿‡ç»Ÿä¸€é™„件中心上传入会材料(如单位营业执照、个人资质证明),支持多附件 |
| ç¼–辑与删除 | æŒ‰çŠ¶æ€çº¦æŸï¼ˆå®¡æ‰¹ä¸­ä¸å¯ç¼–è¾‘ï¼Œå·²é€šè¿‡ä¸å¯åˆ é™¤ï¼‰ |
| åˆ†é¡µæ£€ç´¢ | æŒ‰ç”³è¯·ç¼–号、申请人/单位名称、会员类型、状态、申请时间检索 |
| æäº¤å®¡æ‰¹ | é€‰æ‹©å…¥ä¼šå®¡æ‰¹æµç¨‹å¹¶æäº¤ï¼Œè¿›å…¥"审批中" |
| å®¡æ‰¹ç»“果回写 | å®¡æ‰¹é€šè¿‡ â†’ "已通过"并生成会员;驳回 â†’ "已驳回"(记录原因);撤回 â†’ å›ž"草稿" |
**(2)会员列表**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| ä¼šå‘˜å»ºæ¡£ | å…¥ä¼šç”³è¯·å®¡æ‰¹é€šè¿‡åŽè‡ªåŠ¨ç”Ÿæˆä¼šå‘˜æ¡£æ¡ˆï¼Œè®°å½•ä¼šå‘˜ç¼–å·ã€ä¼šå‘˜ç±»åž‹ã€ä¼šå‘˜ç­‰çº§ã€å…¥ä¼šæ—¥æœŸã€æœ‰æ•ˆæœŸè‡³ |
| ä¼šå‘˜ç­‰çº§ | ä¸ªäººä¼šå‘˜ç­‰çº§ï¼šæ™®é€šä¼šå‘˜ã€ç†äº‹ã€å¸¸åŠ¡ç†äº‹ã€å‰¯ç†äº‹é•¿ã€ç†äº‹é•¿ï¼›å•ä½ä¼šå‘˜ç­‰çº§ï¼šæ™®é€šå•ä½ã€ç†äº‹å•ä½ã€å‰¯ä¼šé•¿å•ä½ã€ä¼šé•¿å•ä½ |
| ä¼šå‘˜çŠ¶æ€ | æ­£å¸¸ã€æš‚停 |
| ä¼šå‘˜ä¿¡æ¯ç»´æŠ¤ | ç»´æŠ¤è”系方式、研究方向、单位简介、备注等;会员材料附件管理 |
| åˆ†é¡µæ£€ç´¢ | æŒ‰ä¼šå‘˜ç¼–号、姓名/单位名称、会员类型、等级、状态、所属地区、所属行业检索 |
| å…³è”系统账号 | ä¼šå‘˜å¯å…³è”平台用户账号,会员登录后可查看自身信息 |
| å½’æ¡£ | å¯¹"正常"状态的会员一键归档,生成会员档案并将会员材料随档带入;已归档后按钮隐藏,避免重复归档 |
**(3)会员通讯录**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| é€šè®¯å½•视图 | ä»¥é€šè®¯å½•形式展示会员名录,便于内部查阅与联络 |
| åˆ†çº§æŸ¥è¯¢ | æŒ‰ä¼šå‘˜ç±»åž‹ã€ç­‰çº§ã€åœ°åŒºã€è¡Œä¸šç­›é€‰ |
| è”系方式展示 | å±•示联系电话、邮箱、工作单位等(受角色权限控制) |
#### 4.3.3 å…³é”®æ•°æ®é¡¹ä¸ŽçŠ¶æ€æœº
**入会申请状态**:草稿(0) â†’ å®¡æ‰¹ä¸­(15) â†’ å·²é€šè¿‡(20) / å·²é©³å›ž(35)。
**会员状态**:正常(1) / æš‚停(0)。
**关键数据项**:申请编号、会员类型、申请人/单位名称、联系电话、邮箱、法定代表人、统一社会信用代码、研究方向、工作单位、职称、申请等级、地区编码、行业、状态、流程实例 ID、创建人、附件列表;会员侧另有会员编号、关联用户 ID、来源申请单 ID、会员等级、入会日期、有效期至、归档生成的档案 ID。
#### 4.3.4 ä¸Žå…¶ä»–模块的协同
| ååŒå¯¹è±¡ | ååŒå†…容 |
|---|---|
| ååŒåŠžå…¬ | å¤ç”¨å…¥ä¼šå®¡æ‰¹æµç¨‹ï¼›å®¡æ‰¹é€šè¿‡è§¦å‘建会员逻辑 |
| æ¡£æ¡ˆç®¡ç† | ä¼šå‘˜ä¸€é”®å½’档生成会员档案;会员材料成为可借阅的档案资产 |
| è¯¾é¢˜ç®¡ç† | ä¼šå‘˜æ˜¯è¯¾é¢˜ç”³æŠ¥çš„主体(申报书关联会员 ID) |
| ä¼ä¸šå¾®ä¿¡ | ä¼šå‘˜ï¼æˆå‘˜å¯é€šè¿‡ä¼ä¸šå¾®ä¿¡æŽ¥æ”¶æ´»åŠ¨ä¸Žé€šçŸ¥ |
| ç³»ç»Ÿç®¡ç† | ä¼šå‘˜å¯å…³è”平台用户账号,复用统一身份与权限体系 |
---
### 4.4 ä¼ä¸šå¾®ä¿¡ï¼ˆ`/wxcp`)
#### 4.4.1 æ¨¡å—定位
面向成员的**移动端触达与活动组织**能力。打通企业微信,实现"通知与活动一键推送至手机、成员扫码报名、现场扫码签到",替代逐一转发。
#### 4.4.2 åŠŸèƒ½æ¸…å•
**(1)活动管理**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æ´»åŠ¨æ–°å¢žä¸Žç¼–è¾‘ | å½•入活动标题、详情(富文本)、封面图、活动地点、活动起止时间、报名起止时间、报名人数上限(0 è¡¨ç¤ºä¸é™ï¼‰ã€å¤‡æ³¨ |
| æ´»åŠ¨å‘å¸ƒ | è‰ç¨¿ â†’ å·²å‘布,进入可报名状态 |
| æ´»åŠ¨ç»“æŸï¼å–æ¶ˆ | æ´»åŠ¨ç»“æŸåŽç½®"已结束";异常情况可取消,置"已取消" |
| æ´»åŠ¨åˆ é™¤ | åˆ é™¤ä¸å†éœ€è¦çš„æ´»åЍ |
| æŽ¨é€è‡³ä¼ä¸šå¾®ä¿¡ | å°†æ´»åŠ¨æŽ¨é€ç»™ä¼ä¸šå¾®ä¿¡æˆå‘˜ï¼Œæ”¯æŒä¸‰ç§æŽ¨é€èŒƒå›´ï¼šå…¨å‘˜ã€æŒ‡å®šéƒ¨é—¨ã€æŒ‡å®šæˆå‘˜ï¼›è¿”å›žæŽ¨é€ç»“æžœï¼ˆæˆåŠŸï¼å¤±è´¥äººæ•°ï¼‰ï¼›æ”¯æŒè¦†ç›–æ´»åŠ¨ä¸Šå·²é…ç½®çš„æŽ¨é€èŒƒå›´ |
| æŠ¥åäºŒç»´ç  | ç”Ÿæˆæ´»åŠ¨æŠ¥åäºŒç»´ç ï¼ˆPNG å›¾ç‰‡ï¼Œå¯ç›´æŽ¥å±•示或打印),成员扫码进入在线报名页 |
| æŠ¥åé¡µåœ°å€ | è¿”回报名页链接,便于前端自行生成二维码或复制分享 |
| æŠ¥åè®°å½•管理 | åˆ†é¡µæŸ¥è¯¢æŠ¥åè®°å½•(含活动标题、报名人、状态);支持取消报名 |
| æŠ¥åç­¾åˆ° | å¯¹æŠ¥åè®°å½•执行签到,状态置"已签到" |
| æŠ¥åææ–™æŸ¥çœ‹ | æŸ¥çœ‹æŸæ¡æŠ¥åè®°å½•上传的图片/附件 |
| æŠ¥åç»Ÿè®¡ | ç»Ÿè®¡æŠ¥åæ€»æ•°ã€çŠ¶æ€åˆ†å¸ƒã€æŒ‰éƒ¨é—¨åˆ†å¸ƒã€æŒ‰æ—¥æœŸè¶‹åŠ¿ã€ç­¾åˆ°çŽ‡ |
**(2)通知管理**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| é€šçŸ¥æ–°å¢žä¸Žç¼–辑 | å½•入通知标题、正文(富文本)、备注 |
| é€šçŸ¥å‘布 | è‰ç¨¿ â†’ å·²å‘布 |
| é€šçŸ¥æ’¤å›ž | å·²å‘布通知可撤回,置"已撤回" |
| é€šçŸ¥åˆ é™¤ | åˆ é™¤é€šçŸ¥ |
| æŽ¨é€è‡³ä¼ä¸šå¾®ä¿¡ | åŒæ´»åŠ¨æŽ¨é€ï¼Œæ”¯æŒå…¨å‘˜ï¼æŒ‡å®šéƒ¨é—¨ï¼æŒ‡å®šæˆå‘˜ä¸‰ç§èŒƒå›´ï¼Œè¿”å›žæŽ¨é€ç»“æžœ |
**(3)通讯录与回调(支撑能力)**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| éƒ¨é—¨æ ‘查询 | å®žæ—¶èŽ·å–ä¼ä¸šå¾®ä¿¡éƒ¨é—¨æ ‘ï¼Œä¾›æŽ¨é€é€‰äººä½¿ç”¨ |
| éƒ¨é—¨å¹³é“ºåˆ—表 | æä¾›å·²æ‹å¹³çš„部门列表,便于前端下拉选择 |
| æˆå‘˜åˆ—表查询 | æŒ‰éƒ¨é—¨èŽ·å–æˆå‘˜åˆ—è¡¨ï¼Œå¯é€‰æ˜¯å¦åŒ…å«å­éƒ¨é—¨æˆå‘˜ |
| å›žè°ƒ URL éªŒè¯ | å“åº”企业微信的 URL æœ‰æ•ˆæ€§éªŒè¯ï¼ˆç­¾åæ ¡éªŒ + åŠ å¯†ä¸²è§£å¯†å›žæ˜¾ï¼‰ |
| æ¶ˆæ¯ï¼äº‹ä»¶æŽ¥æ”¶ | æŽ¥æ”¶å¹¶è§£å¯†ä¼ä¸šå¾®ä¿¡æŽ¨é€çš„æ¶ˆæ¯ä¸Žäº‹ä»¶ï¼ˆè¿›å…¥åº”用、关注、通讯录变更、文本消息),按类型路由处理并回执 |
#### 4.4.3 å…³é”®æ•°æ®é¡¹ä¸ŽçŠ¶æ€æœº
**活动状态**:草稿(0) â†’ å·²å‘布(1) â†’ å·²ç»“束(2);异常可置 å·²å–消(3)。
**通知状态**:草稿(0) â†’ å·²å‘布(1) â†’ å·²æ’¤å›ž(2)。
**报名状态**:已报名(0) / å·²å–消(1) / å·²ç­¾åˆ°(2)。
**推送范围**:全员(0) / æŒ‡å®šéƒ¨é—¨(1) / æŒ‡å®šæˆå‘˜(2)。
**关键数据项**:活动——标题、详情、封面图、地点、活动起止时间、报名起止时间、报名上限、已报名人数、状态、推送范围、推送目标、推送状态、最近推送时间;通知——标题、正文、状态、推送范围、推送目标、推送状态、最近推送时间;报名记录——活动 ID、报名人、状态、提交材料。
#### 4.4.4 ä¸Žå…¶ä»–模块的协同
| ååŒå¯¹è±¡ | ååŒå†…容 |
|---|---|
| ç³»ç»Ÿç®¡ç† | æŽ¨é€è®°å½•并入统一消息体系;附件经统一附件中心存储 |
| ä¼šå‘˜ç®¡ç† | ä¼šå‘˜æ´»åŠ¨æŠ¥åå¯å›žæµä¸ºä¼šå‘˜æ´»è·ƒåº¦æ•°æ® |
| æ¡£æ¡ˆç®¡ç† | æ´»åŠ¨é€šçŸ¥ã€æŠ¥åææ–™å¯ä½œä¸ºæ–‡ä¹¦æ¡£æ¡ˆæ‰‹å·¥å½•å…¥å½’é›†ï¼ˆæœ¬æœŸä¸ºæ‰‹å·¥ï¼Œä¸å«è‡ªåŠ¨å½’æ¡£ï¼‰ |
---
### 4.5 è¯¾é¢˜ç®¡ç†ï¼ˆ`/research`)
#### 4.5.1 æ¨¡å—定位
学会学术业务的**主线流程**:指南发布 â†’ è¯¾é¢˜ç”³æŠ¥ â†’ ä¸“家评审 â†’ ç«‹é¡¹ â†’ ç»è´¹æ‹¨ä»˜ â†’ ç«‹é¡¹é€šçŸ¥ï¼Œå…¨é“¾è·¯ç•™ç—•、可追溯。
#### 4.5.2 åŠŸèƒ½æ¸…å•
**(1)课题指南**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æŒ‡å—新增与编辑 | å½•入指南编号、指南名称、课题类别(重点课题/一般课题)、申报开始与截止时间、指南正文(富文本)、备注 |
| æŒ‡å—附件 | ä¸Šä¼ æŒ‡å—文件(如申报模板、评审标准),支持多附件 |
| æŒ‡å—发布 | è‰ç¨¿ â†’ å·²å‘布,进入可申报状态 |
| æŒ‡å—截止 | å·²å‘布 â†’ å·²æˆªæ­¢ï¼Œåœæ­¢æŽ¥å—申报 |
| å·²å‘布指南列表 | æä¾›å·²å‘布指南清单,供申报时选择所属指南,避免手工输入错漏 |
| æŒ‡å—删除 | åˆ é™¤ä¸å†éœ€è¦çš„æŒ‡å— |
**(2)课题申报**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| ç”³æŠ¥ä¹¦æ–°å¢ž | å½•入所属课题指南、课题名称、课题类别、申报人信息(姓名/单位名称、工作单位、职称、联系电话、联系邮箱)、课题组成员、申请经费、申报书正文;申报人可关联会员 |
| ç”³æŠ¥ä¹¦é™„ä»¶ | ä¸Šä¼ ç”³æŠ¥ä¹¦æ­£æ–‡æ–‡ä»¶ä¸Žæ”¯æ’‘材料,支持多附件 |
| ç”³æŠ¥ä¹¦ç¼–辑/删除 | æŒ‰çŠ¶æ€çº¦æŸï¼ˆå®¡æ‰¹ä¸­ä¸å¯ç¼–è¾‘ï¼Œå·²ç«‹é¡¹ä¸å¯åˆ é™¤ï¼‰ |
| åˆ†é¡µæ£€ç´¢ | æŒ‰ç”³æŠ¥ç¼–号、课题名称、所属指南、类别、状态、申报时间检索 |
| æäº¤å®¡æ‰¹ | é€‰æ‹©è¯¾é¢˜ç«‹é¡¹å®¡æ‰¹æµç¨‹å¹¶æäº¤ï¼Œè¿›å…¥"审批中" |
| ç«‹é¡¹å®¡æ‰¹ | é€šè¿‡ BPM å¤šçº§å®¡æ‰¹æµè½¬ï¼›å®¡æ‰¹é€šè¿‡ â†’ "已立项"并生成立项编号、记录立项时间;驳回 â†’ "已驳回"(记录原因) |
| ä¸“家评审明细 | ä¸ºæŸè¯¾é¢˜ç”³æŠ¥ä¹¦å½•入多名专家的评审明细:评审专家、评分、评审意见、评审结论(推荐立项/不推荐立项);系统计算加权平均分并回写申报书 |
| ç«‹é¡¹é€šçŸ¥å‘布 | ç«‹é¡¹åŽå‘布立项通知(生成通知公告并记录通知 ID),实现结果对外发布 |
| ç»è´¹æ‹¨ä»˜ç™»è®° | ä¸ºå·²ç«‹é¡¹è¯¾é¢˜ç™»è®°ç»è´¹æ‹¨ä»˜è®°å½•:拨付金额、拨付日期、拨付说明、凭证附件;拨付状态跟踪 |
| å½’æ¡£ | å¯¹"已立项"的申报单一键归档,生成课题档案并将申报书附件随档带入 |
#### 4.5.3 å…³é”®æ•°æ®é¡¹ä¸ŽçŠ¶æ€æœº
**指南状态**:草稿(0) â†’ å·²å‘布(20) â†’ å·²æˆªæ­¢(30)。
**申报书状态**:草稿(0) â†’ å®¡æ‰¹ä¸­(15) â†’ å·²ç«‹é¡¹(20) / å·²é©³å›ž(35)。
**经费拨付状态**:待拨付(0) â†’ æŠ¥é”€å®¡æ‰¹ä¸­(15) â†’ å·²æ‹¨ä»˜(20) / å·²é©³å›ž(30)。
**评审结论**:推荐立项(1) / ä¸æŽ¨èç«‹é¡¹(2)。
**课题类别**:重点课题(1) / ä¸€èˆ¬è¯¾é¢˜(2)。
**申报书关键数据项**:申报编号、所属指南、课题名称、课题类别、申报会员、申报人名称、工作单位、职称、联系电话、联系邮箱、课题组成员、申请经费、申报书正文、状态、流程实例 ID、专家评审加权均分、评审结论、立项编号、立项时间、立项通知 ID、归档生成的档案 ID、备注、附件列表。
#### 4.5.4 ä¸Žå…¶ä»–模块的协同
| ååŒå¯¹è±¡ | ååŒå†…容 |
|---|---|
| ååŒåŠžå…¬ | ç«‹é¡¹å®¡æ‰¹å¤ç”¨æµç¨‹å¼•擎;立项通知复用通知公告能力 |
| ä¼šå‘˜ç®¡ç† | ç”³æŠ¥ä¹¦å…³è”会员,实现"会员 â€” è¯¾é¢˜"归属清晰 |
| è´¢åŠ¡æŠ¥é”€ | ç»è´¹æ‹¨ä»˜å¯è¡”接报销流程,形成"拨付 â†’ æŠ¥é”€æ”¯å‡º"的资金链闭环 |
| æ¡£æ¡ˆç®¡ç† | ç”³æŠ¥ä¹¦ä¸€é”®å½’档生成课题档案,评审与拨付凭证可随之归集 |
| ç³»ç»Ÿç®¡ç† | é™„件走统一附件中心;编号由编码规则生成 |
---
### 4.6 æ¡£æ¡ˆç®¡ç†ï¼ˆ`/archive`)
#### 4.6.1 æ¨¡å—定位
全平台的**档案归集与借阅底座**。覆盖"分类建树 â†’ æ¡£æ¡ˆç™»è®°ï¼ˆå«å®žä½“位置)→ å€Ÿé˜…审批 â†’ å½’还登记 â†’ ç»Ÿè®¡"全流程,并对外提供归档联动接口(见 3.6 èŠ‚ï¼‰ã€‚æœ¬æ¨¡å—ç›´æŽ¥å›žåº”æ‹›æ ‡éœ€æ±‚ä¸­çš„"书架(分类管理)、文档管理、借阅登记、归还登记、档案统计"五项能力。
#### 4.6.2 åŠŸèƒ½æ¸…å•
**(1)档案分类(书架/分类管理)**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| åˆ†ç±»æ ‘维护 | æ”¯æŒå¤šçº§åˆ†ç±»ï¼ˆçˆ¶åˆ†ç±» â€” å­åˆ†ç±»ï¼‰ï¼Œå®žçŽ°åˆ†å±‚åˆ†åˆ—çš„ä¹¦æž¶å¼ç»„ç»‡ |
| åˆå§‹åˆ†ç±» | é¢„置六类:会员档案、课题档案、会议档案、财务档案、文书档案、党建档案 |
| åˆ†ç±»ç¼–码 | æ¯ä¸ªåˆ†ç±»æœ‰å”¯ä¸€ç¼–码(member/research/meeting/finance/document/party),归档联动按编码定位目标分类 |
| é»˜è®¤å¯†çº§ | æ¯ä¸ªåˆ†ç±»å¯è®¾ç½®é»˜è®¤å¯†çº§ï¼Œæ–°å»ºæ¡£æ¡ˆæ—¶è‡ªåŠ¨å¸¦å‡ºï¼›**财务档案分类默认密级为"保密"** |
| æŽ’序与说明 | æ”¯æŒæ˜¾ç¤ºæŽ’序与分类说明,便于前台按业务习惯排列 |
| å¯ç”¨ï¼åœç”¨ | åœç”¨åˆ†ç±»åŽä¸å†å…è®¸æ–°å»ºæ¡£æ¡ˆï¼ŒåŽ†å²æ¡£æ¡ˆä¿ç•™ |
| åˆ†ç±»åˆ é™¤çº¦æŸ | å­˜åœ¨å­åˆ†ç±»æˆ–已挂档案的分类不允许删除,防止孤儿数据 |
**(2)档案管理**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æ¡£æ¡ˆç™»è®° | å½•入档案标题、所属分类、密级、载体类型、摘要、关键词、归档日期、归档人、备注 |
| ç”µå­ + å®žä½“双载体 | è½½ä½“类型支持"电子/实体/电子+实体"三选;每条档案既可挂电子附件,也可登记实体存放位置 |
| å®žä½“存放位置 | ç™»è®°ä¹¦æž¶å·ã€å±‚号、列号、册号/档案盒号,以及位置描述(如"3 å·åº“房 A æž¶ 2 å±‚"),实现纸质档案的精确检索 |
| ç”µå­é™„ä»¶ | é€šè¿‡ç»Ÿä¸€é™„件中心上传电子文件,支持多附件 |
| æ¥æºæ ‡è¯† | è®°å½•来源类型(手工录入/课题归档/会员归档/财务归档)、来源单号、来源单据 ID ä¸Žæ¥æºæ ‡é¢˜å¿«ç…§ï¼Œå®žçް"从档案反查业务单据" |
| å¤šç»´æ£€ç´¢ | æŒ‰æ ‡é¢˜ã€å…³é”®è¯ã€æ¥æºå•号、分类、密级、状态、归档日期检索 |
| ç¼–辑约束 | æ¡£æ¡ˆå¤„于"借出中"时禁止修改,防止借出期间信息被改动 |
| åˆ é™¤çº¦æŸ | æ¡£æ¡ˆå¤„于"借出中"或存在未归还借阅单时禁止删除 |
| å¯¼å‡º | æŒ‰æ£€ç´¢æ¡ä»¶å¯¼å‡ºæ¡£æ¡ˆæ¸…单;导出动作记录操作日志 |
| å¯†çº§ç®¡æŽ§ | ä¿å¯†çº§æ¡£æ¡ˆå¯¹æ—  `archive:document:sensitive` æƒé™çš„用户在详情、列表、导出三处均不可见(见 3.1.3) |
**(3)借阅登记**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| å€Ÿé˜…单登记 | è®°å½•借阅人(姓名、部门、联系电话)、借阅日期、借阅目的、归还期限;档案编号与标题以快照方式冗余存储 |
| å€Ÿé˜…单编辑/删除 | ä»…"草稿/已驳回"可编辑;仅"草稿"可删除 |
| æäº¤å®¡æ‰¹ | é€‰æ‹©æ¡£æ¡ˆå€Ÿé˜…审批流程并提交(**两级审批**:档案管理员审核 â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ï¼‰ |
| å”¯ä¸€æ€§æ ¡éªŒ | åŒä¸€æ¡£æ¡ˆè‹¥å·²å­˜åœ¨"审批中"或"借出中"的借阅单,提交时直接拒绝并提示已存在的借阅单号,从源头避免重复借出 |
| å®¡æ‰¹é€šè¿‡ | å€Ÿé˜…单置"借出中"并记录实际借出时间;**同时以条件更新将档案置"借出中"**;若档案已被占用则本次审批回滚并提示(见 3.2 èŠ‚å¹¶å‘æŽ§åˆ¶ï¼‰ |
| å®¡æ‰¹é©³å›ž | å€Ÿé˜…单置"已驳回",记录驳回原因与时间;档案状态保持"在库" |
| æ’¤å›žï¼å–消 | å›žåˆ°"草稿",可修改后重新提交 |
| é€¾æœŸå®žæ—¶è®¡ç®— | å€Ÿé˜…单处于"借出中"且归还期限早于当前日期即为逾期;列表以红色标签显示"逾期 N å¤©";支持只筛逾期单。**逾期在查询时实时计算,不写入数据库、不依赖定时任务、不发送站内信**——避免定时任务失败导致提醒失真,也避免无用消息堆积 |
| é€¾æœŸæ¸…单 | æä¾›ç‹¬ç«‹çš„逾期借阅分页查询,供管理员集中催还 |
**(4)归还登记**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| å½’还确认 | å¯¹"借出中"的借阅单执行归还登记:记录实际归还时间、归还确认人、归还备注 |
| çŠ¶æ€è”åŠ¨ | å€Ÿé˜…单置"已归还";**同时以条件更新将档案置回"在库"**,档案恢复可借 |
| å½’还约束 | ä»…"借出中"的借阅单可登记归还,重复归还被拒绝并给出明确提示 |
| é—­çޝ | å½’还后档案回到"在库",可再次被借阅,形成完整流转闭环 |
**(5)档案统计**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æ€»é‡æŒ‡æ ‡ | æ€»æ¡£æ¡ˆæ•°é‡ã€åœ¨åº“数量、借出数量、借出中数量、逾期数量 |
| åˆ†ç±»åˆ†å¸ƒ | æŒ‰æ¡£æ¡ˆåˆ†ç±»ç»Ÿè®¡æ•°é‡ï¼ˆé¥¼å›¾ï¼‰ |
| å¯†çº§åˆ†å¸ƒ | æŒ‰å¯†çº§ï¼ˆå…¬å¼€ï¼å†…部/保密)统计数量(柱图) |
| è½½ä½“分布 | æŒ‰è½½ä½“类型(电子/实体/电子+实体)统计数量 |
| å®žæ—¶æ€§ | ç»Ÿè®¡æ•°æ®å–自实际档案表,与列表数据一致 |
#### 4.6.3 å…³é”®æ•°æ®é¡¹ä¸ŽçŠ¶æ€æœº
**档案状态**:在库(10) â‡„ å€Ÿå‡ºä¸­(20)。
- â†’ å€Ÿå‡ºä¸­çš„**唯一入口**:借阅单审批通过(经流程监听器回写);
- â†’ åœ¨åº“çš„**唯一入口**:归还登记。
该"单一入口"设计保证档案状态不会被其他路径意外篡改。
**借阅单状态**:
```
草稿(0) â”€â”€æäº¤â”€â”€â–¶ å®¡æ‰¹ä¸­(15)
   â–²                 â”œâ”€â”€ å®¡æ‰¹é€šè¿‡ â”€â”€â–¶ å€Ÿå‡ºä¸­(20) â”€â”€å½’还登记──▶ å·²å½’还(30)
   â”‚                 â””── å®¡æ‰¹é©³å›ž â”€â”€â–¶ å·²é©³å›ž(35)(可修改后重新提交)
   â””──撤回───────────┘
```
**密级**:公开(1) / å†…部(2) / ä¿å¯†(3)。
**载体类型**:电子(1) / å®žä½“(2) / ç”µå­+实体(3)。
**来源类型**:手工录入(1) / è¯¾é¢˜å½’æ¡£(2) / ä¼šå‘˜å½’æ¡£(3) / è´¢åŠ¡å½’æ¡£(4)。
**分类状态**:启用(0) / åœç”¨(1)。
**档案主表关键数据项**:档案编号(`DA` + æ—¥æœŸ + æµæ°´ï¼‰ã€æ ‡é¢˜ã€åˆ†ç±»ã€å¯†çº§ã€è½½ä½“类型、书架号、层号、列号、册号/档案盒号、存放位置描述、来源类型、来源单号、来源单据 ID、来源标题、归档日期、归档人、状态、摘要、关键词、备注、电子附件。
**借阅单关键数据项**:借阅单号(`JY` + æ—¥æœŸ + æµæ°´ï¼‰ã€æ¡£æ¡ˆ ID、档案编号、档案标题、借阅人用户 ID、借阅人姓名、部门、联系电话、借阅日期、借阅目的、归还期限、状态、流程实例 ID、实际借出时间、实际归还时间、归还确认人、归还备注、驳回原因、驳回时间。
#### 4.6.4 ä¸Žå…¶ä»–模块的协同
| ååŒå¯¹è±¡ | ååŒå†…容 |
|---|---|
| **课题管理** | æŽ¥æ”¶è¯¾é¢˜å½’档请求,生成课题档案(密级:内部),带入申报书附件,回写来源表档案 ID |
| **会员管理** | æŽ¥æ”¶ä¼šå‘˜å½’档请求,生成会员档案(密级:内部),带入会员材料,回写来源表档案 ID |
| **财务报销** | æŽ¥æ”¶è´¢åŠ¡å½’æ¡£è¯·æ±‚ï¼Œç”Ÿæˆè´¢åŠ¡æ¡£æ¡ˆï¼ˆå¯†çº§ï¼š**保密**),带入发票附件,与报销单归档动作同事务提交 |
| ååŒåŠžå…¬ | å€Ÿé˜…审批复用流程引擎(两级审批);流程结果经监听器回写借阅单与档案状态 |
| ç³»ç»Ÿç®¡ç† | é™„件走统一附件中心;档案读取受密级权限点约束;操作记录审计日志 |
**明确的能力边界**(本方案不含以下内容,避免验收争议):
| è¾¹ç•Œé¡¹ | è¯´æ˜Ž |
|---|---|
| ä¼šè®®æ¡£æ¡ˆã€å…šå»ºæ¡£æ¡ˆ | ç³»ç»Ÿå†…无对应的业务来源模块,分类已预置,**仅支持手工录入**,不含自动归档 |
| é€¾æœŸæé†’方式 | ä»…在页面查询时实时计算并高亮,**不建立定时任务、不发送站内信** |
| è¯¾é¢˜ç»“项归档 | è¯¾é¢˜ç®¡ç†ä¸å«ç»“项功能,归档针对"已立项"的申报单 |
| åˆåŒæ¡£æ¡ˆ | ä¸åœ¨æœ¬æœŸå½’档联动范围 |
| æ¡£æ¡ˆé”€æ¯ã€é€¾æœŸç½šæ¬¾ | æœ¬æœŸä¸å«ï¼Œå¯ä½œä¸ºäºŒæœŸæ‰©å±• |
---
### 4.7 ç³»ç»Ÿç®¡ç†ï¼ˆ`/system`)
#### 4.7.1 æ¨¡å—定位
平台的**基础支撑与治理中心**,为其余六个子系统提供身份、权限、字典、编码、附件、消息与审计能力。
#### 4.7.2 åŠŸèƒ½æ¸…å•
**(1)组织与身份**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| ç”¨æˆ·ç®¡ç† | ç”¨æˆ·çš„æ–°å¢žã€ç¼–辑、停用、删除;重置密码;分配角色;分配部门与岗位 |
| è§’色管理 | è§’色的新增与权限分配;支持数据范围设置(全部/本部门及以下/本部门/仅本人/自定义) |
| èœå•管理 | èœå•与按钮权限的统一维护,菜单驱动前端路由与侧边栏,新增功能无需前端手工配置路由 |
| éƒ¨é—¨ç®¡ç† | å¤šçº§ç»„织架构维护 |
| å²—位管理 | å²—位维护,供用户归属 |
| ç§Ÿæˆ·ç®¡ç† | å¤šç§Ÿæˆ·èƒ½åŠ›å…¥å£ï¼ˆæœ¬æœŸä¸šåŠ¡æŒ‰å•ç§Ÿæˆ·éƒ¨ç½²ï¼Œä¿ç•™éš”ç¦»æ‰©å±•èƒ½åŠ›ï¼‰ |
**(2)配置与数据**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| å­—典管理 | å­—典类型与字典项的统一维护;单据状态、密级、费用类型、会员等级等全部枚举由字典驱动,前台按字典渲染列与下拉,**改字典即改前台展现,无需改代码** |
| ç¼–码规则 | å•据编号规则维护,支撑各类单据的"前缀 + æ—¥æœŸ + æµæ°´"编号 |
| åœ°åŒºç®¡ç† | è¡Œæ”¿åŒºåˆ’数据维护,支撑会员与业务的地区归属 |
| å…¬å¸ä¿¡æ¯é…ç½® | å•位名称、标识等基础信息配置 |
**(3)文件与消息**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| æ–‡ä»¶å­˜å‚¨ | é™„件元数据与存储配置管理;支持本地磁盘与 S3 å…¼å®¹å¯¹è±¡å­˜å‚¨åˆ‡æ¢ |
| çŸ­ä¿¡ç®¡ç† | çŸ­ä¿¡é€šé“与模板管理 |
| é‚®ç®±ç®¡ç† | é‚®ä»¶è´¦å·ä¸Žæ¨¡æ¿ç®¡ç† |
| ç«™å†…信管理 | ç«™å†…消息发送与查询,支撑审批待办提醒 |
| é€šçŸ¥å…¬å‘Š | é¢å‘内部用户的公告发布与阅读回执 |
**(4)安全与开放**
| åŠŸèƒ½é¡¹ | åŠŸèƒ½è¯´æ˜Ž |
|---|---|
| OAuth 2.0 â€” åº”用管理 | ç»´æŠ¤æŽ¥å…¥çš„客户端应用(客户端 ID/密钥/回调地址),支撑单点登录与第三方接入 |
| OAuth 2.0 â€” ä»¤ç‰Œç®¡ç† | æŸ¥è¯¢åœ¨çº¿ä»¤ç‰Œï¼Œæ”¯æŒå¼ºåˆ¶ä¸‹çº¿ |
| ä¸‰æ–¹ç™»å½• â€” ä¸‰æ–¹åº”用 | é…ç½®ç¬¬ä¸‰æ–¹å¹³å°ç™»å½•(如微信、钉钉等) |
| ä¸‰æ–¹ç™»å½• â€” ä¸‰æ–¹ç”¨æˆ· | ç»´æŠ¤ä¸‰æ–¹è´¦å·ä¸Žå¹³å°è´¦å·çš„绑定关系 |
| å®¡è®¡æ—¥å¿— â€” æ“ä½œæ—¥å¿— | è®°å½•关键写操作(含导出等敏感动作)的操作人、时间、接口、参数摘要与结果 |
| å®¡è®¡æ—¥å¿— â€” ç™»å½•日志 | è®°å½•登录行为(账号、IP、时间、结果),支撑安全审计与异常排查 |
#### 4.7.3 ä¸Žå…¶ä»–模块的协同
系统管理是**全平台的被依赖方**:所有子系统的登录鉴权、菜单渲染、权限校验、字典翻译、单据编号、附件存取、消息发送与操作留痕,均建立在本模块之上。其"菜单 + æƒé™ç‚¹ + å­—å…¸"三张配置表,决定了其余六个子系统的可见范围与展现形式。
---
## äº”、附录
### é™„录 A æƒé™ç‚¹æ¸…单(六大业务子系统,共 68 ä¸ªï¼‰
**财务报销**
| æƒé™ç‚¹ | è¯´æ˜Ž |
|---|---|
| `finance:reimbursement:create` | æ–°å¢žæŠ¥é”€å• |
| `finance:reimbursement:update` | ä¿®æ”¹æŠ¥é”€å• |
| `finance:reimbursement:delete` | åˆ é™¤æŠ¥é”€å• |
| `finance:reimbursement:query` | æŸ¥è¯¢æŠ¥é”€å• |
| `finance:reimbursement:export` | å¯¼å‡ºæŠ¥é”€å• |
| `finance:reimbursement:update-status` | æäº¤æŠ¥é”€å®¡æ‰¹ï¼ˆçŠ¶æ€æµè½¬ï¼‰ |
| `finance:reimbursement:archive` | å½’档报销单(并生成财务档案) |
**会员管理**
| æƒé™ç‚¹ | è¯´æ˜Ž |
|---|---|
| `ums:application:create` | æ–°å¢žå…¥ä¼šç”³è¯· |
| `ums:application:update` | ä¿®æ”¹å…¥ä¼šç”³è¯· |
| `ums:application:delete` | åˆ é™¤å…¥ä¼šç”³è¯· |
| `ums:application:query` | æŸ¥è¯¢å…¥ä¼šç”³è¯· |
| `ums:application:submit` | æäº¤å…¥ä¼šå®¡æ‰¹ |
| `ums:member:create` | æ–°å¢žä¼šå‘˜ |
| `ums:member:update` | ä¿®æ”¹ä¼šå‘˜ |
| `ums:member:delete` | åˆ é™¤ä¼šå‘˜ |
| `ums:member:query` | æŸ¥è¯¢ä¼šå‘˜ |
| `ums:member:directory` | æŸ¥çœ‹ä¼šå‘˜é€šè®¯å½• |
| `ums:member:archive` | ä¼šå‘˜å½’档(生成会员档案) |
**课题管理**
| æƒé™ç‚¹ | è¯´æ˜Ž |
|---|---|
| `research:guideline:create` | æ–°å¢žè¯¾é¢˜æŒ‡å— |
| `research:guideline:update` | ä¿®æ”¹è¯¾é¢˜æŒ‡å— |
| `research:guideline:delete` | åˆ é™¤è¯¾é¢˜æŒ‡å— |
| `research:guideline:query` | æŸ¥è¯¢è¯¾é¢˜æŒ‡å— |
| `research:guideline:publish` | å‘布/截止课题指南 |
| `research:application:create` | æ–°å¢žè¯¾é¢˜ç”³æŠ¥ä¹¦ |
| `research:application:update` | ä¿®æ”¹è¯¾é¢˜ç”³æŠ¥ä¹¦ |
| `research:application:delete` | åˆ é™¤è¯¾é¢˜ç”³æŠ¥ä¹¦ |
| `research:application:query` | æŸ¥è¯¢è¯¾é¢˜ç”³æŠ¥ä¹¦ |
| `research:application:submit` | æäº¤è¯¾é¢˜ç«‹é¡¹å®¡æ‰¹ |
| `research:application:notice` | å‘布立项通知 |
| `research:application:archive` | è¯¾é¢˜å½’档(生成课题档案) |
| `research:review:create` | å½•入专家评审明细 |
| `research:review:update` | ä¿®æ”¹ä¸“家评审明细 |
| `research:review:delete` | åˆ é™¤ä¸“家评审明细 |
| `research:review:query` | æŸ¥è¯¢ä¸“家评审明细 |
| `research:fund:create` | ç™»è®°ç»è´¹æ‹¨ä»˜ |
| `research:fund:update` | ä¿®æ”¹ç»è´¹æ‹¨ä»˜ |
| `research:fund:delete` | åˆ é™¤ç»è´¹æ‹¨ä»˜ |
| `research:fund:query` | æŸ¥è¯¢ç»è´¹æ‹¨ä»˜ |
**档案管理**
| æƒé™ç‚¹ | è¯´æ˜Ž |
|---|---|
| `archive:category:create` | æ–°å¢žæ¡£æ¡ˆåˆ†ç±» |
| `archive:category:update` | ä¿®æ”¹æ¡£æ¡ˆåˆ†ç±» |
| `archive:category:delete` | åˆ é™¤æ¡£æ¡ˆåˆ†ç±» |
| `archive:category:query` | æŸ¥è¯¢æ¡£æ¡ˆåˆ†ç±» |
| `archive:document:create` | æ–°å¢žæ¡£æ¡ˆ |
| `archive:document:update` | ä¿®æ”¹æ¡£æ¡ˆ |
| `archive:document:delete` | åˆ é™¤æ¡£æ¡ˆ |
| `archive:document:query` | æŸ¥è¯¢æ¡£æ¡ˆ |
| `archive:document:export` | å¯¼å‡ºæ¡£æ¡ˆ |
| **`archive:document:sensitive`** | **查看保密级档案(敏感权限点)** |
| `archive:borrow:create` | ç™»è®°å€Ÿé˜…单 |
| `archive:borrow:update` | ä¿®æ”¹å€Ÿé˜…单 |
| `archive:borrow:delete` | åˆ é™¤å€Ÿé˜…单 |
| `archive:borrow:query` | æŸ¥è¯¢å€Ÿé˜…单 |
| `archive:borrow:submit` | æäº¤å€Ÿé˜…审批 |
| `archive:borrow:return` | å½’还登记 |
| `archive:stat:query` | æŸ¥çœ‹æ¡£æ¡ˆç»Ÿè®¡ |
**企业微信**
| æƒé™ç‚¹ | è¯´æ˜Ž |
|---|---|
| `wxcp:activity:create` | æ–°å¢žæ´»åЍ |
| `wxcp:activity:update` | ä¿®æ”¹ï¼å‘布/结束/取消活动 |
| `wxcp:activity:delete` | åˆ é™¤æ´»åЍ |
| `wxcp:activity:query` | æŸ¥è¯¢æ´»åЍ |
| `wxcp:activity:push` | æŽ¨é€æ´»åŠ¨è‡³ä¼ä¸šå¾®ä¿¡ |
| `wxcp:signup:query` | æŸ¥è¯¢æŠ¥åè®°å½• |
| `wxcp:signup:update` | å–消报名/报名签到 |
| `wxcp:notice:create` | æ–°å¢žé€šçŸ¥ |
| `wxcp:notice:update` | ä¿®æ”¹ï¼å‘布/撤回通知 |
| `wxcp:notice:delete` | åˆ é™¤é€šçŸ¥ |
| `wxcp:notice:query` | æŸ¥è¯¢é€šçŸ¥ |
| `wxcp:notice:push` | æŽ¨é€é€šçŸ¥è‡³ä¼ä¸šå¾®ä¿¡ |
| `wxcp:member:query` | æŸ¥è¯¢ä¼ä¸šå¾®ä¿¡éƒ¨é—¨ä¸Žæˆå‘˜ |
### é™„录 B ä¸šåŠ¡å­—å…¸æ¸…å•ï¼ˆå…± 69 æ¡å­—典项)
| å­—典类型 | ç”¨é€” | å­—典项 |
|---|---|---|
| `finance_reimbursement_status` | æŠ¥é”€å•状态 | è‰ç¨¿(0)、审批中(15)、已通过(20)、已驳回(35)、已归档(40) |
| `finance_expense_type` | è´¹ç”¨ç±»åž‹ | å·®æ—…(1)、办公(2)、招待(3)、交通(4)、其他(5) |
| `ums_application_status` | å…¥ä¼šç”³è¯·çŠ¶æ€ | è‰ç¨¿(0)、审批中(15)、已通过(20)、已驳回(35) |
| `ums_member_type` | ä¼šå‘˜ç±»åž‹ | ä¸ªäººä¼šå‘˜(1)、单位会员(2) |
| `ums_member_status` | ä¼šå‘˜çŠ¶æ€ | æ­£å¸¸(1)、暂停(0) |
| `ums_personal_grade` | ä¸ªäººä¼šå‘˜ç­‰çº§ | æ™®é€šä¼šå‘˜(1)、理事(2)、常务理事(3)、副理事长(4)、理事长(5) |
| `ums_unit_grade` | å•位会员等级 | æ™®é€šå•位(1)、理事单位(2)、副会长单位(3)、会长单位(4) |
| `ums_industry` | æ‰€å±žè¡Œä¸š | æ™ºèƒ½åˆ¶é€ ã€æ–°èƒ½æºã€ç”µå­ä¿¡æ¯ã€ç”Ÿç‰©åŒ»è¯ã€æ–°ææ–™ã€èŠ‚èƒ½çŽ¯ä¿ã€å’¨è¯¢æœåŠ¡ã€å…¶ä»–ï¼ˆ1–8) |
| `research_guideline_category` | è¯¾é¢˜ç±»åˆ« | é‡ç‚¹è¯¾é¢˜(1)、一般课题(2) |
| `research_guideline_status` | æŒ‡å—状态 | è‰ç¨¿(0)、已发布(20)、已截止(30) |
| `research_application_status` | ç”³æŠ¥ä¹¦çŠ¶æ€ | è‰ç¨¿(0)、审批中(15)、已立项(20)、已驳回(35) |
| `research_review_conclusion` | è¯„审结论 | æŽ¨èç«‹é¡¹(1)、不推荐立项(2) |
| `research_fund_status` | ç»è´¹æ‹¨ä»˜çŠ¶æ€ | å¾…拨付(0)、报销审批中(15)、已拨付(20)、已驳回(30) |
| `archive_category_status` | æ¡£æ¡ˆåˆ†ç±»çŠ¶æ€ | å¯ç”¨(0)、停用(1) |
| `archive_document_status` | æ¡£æ¡ˆçŠ¶æ€ | åœ¨åº“(10)、借出中(20) |
| `archive_secret_level` | æ¡£æ¡ˆå¯†çº§ | å…¬å¼€(1)、内部(2)、保密(3) |
| `archive_carrier_type` | æ¡£æ¡ˆè½½ä½“ | ç”µå­(1)、实体(2)、电子+实体(3) |
| `archive_source_type` | æ¡£æ¡ˆæ¥æº | æ‰‹å·¥å½•å…¥(1)、课题归档(2)、会员归档(3)、财务归档(4) |
| `archive_borrow_status` | å€Ÿé˜…单状态 | è‰ç¨¿(0)、审批中(15)、借出中(20)、已归还(30)、已驳回(35) |
### é™„录 C æµç¨‹å®šä¹‰æ¸…单(本期业务相关)
| æµç¨‹åç§° | æµç¨‹å®šä¹‰ Key | ç‰ˆæœ¬ | è¯´æ˜Ž |
|---|---|---|---|
| å…¥ä¼šå®¡æ‰¹ | `ums_membership_approve` | 1 | ä¼šå‘˜å…¥ä¼šç”³è¯·çš„多级审批 |
| è¯¾é¢˜ç«‹é¡¹å®¡æ‰¹ | `research_topic_approve` | 1 | è¯¾é¢˜ç”³æŠ¥ä¹¦çš„多级立项审批 |
| æŠ¥é”€å®¡æ‰¹ | `finance_reimbursement_approve` | 1 | æŠ¥é”€å•审批 |
| æ¡£æ¡ˆå€Ÿé˜…审批 | `archive_borrow_approve` | 1 | ä¸¤çº§å®¡æ‰¹ï¼šæ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ |
> è¿è¡Œåº“当前共部署 39 æ¡æµç¨‹å®šä¹‰ï¼Œè¦†ç›–入会、课题、报销、档案借阅以及工厂侧采购、销售、仓储、人事等业务;本期方案涉及的为上述四条。
### é™„录 D èœå•与权限映射(本期七大子系统)
| ä¸€çº§èœå• | ID | è·¯å¾„ | äºŒçº§åŠŸèƒ½é¡µ |
|---|---|---|---|
| ååŒåŠžå…¬ | 1185 | `/bpm` | æµç¨‹ç®¡ç†ï¼ˆå«æµç¨‹æ¨¡åž‹ï¼è¡¨å•/分类/用户分组/监听器/表达式/业务附件配置/流程实例/流程任务)、审批中心、通知公告、OA åº”用 |
| è´¢åŠ¡æŠ¥é”€ | 1075425 | `/finance` | æŠ¥é”€å• |
| ä¼šå‘˜ç®¡ç† | 1075490 | `/ums` | å…¥ä¼šç”³è¯·ã€ä¼šå‘˜åˆ—表、会员通讯录 |
| ä¼ä¸šå¾®ä¿¡ | 6100000 | `/wxcp` | æ´»åŠ¨ç®¡ç†ã€é€šçŸ¥ç®¡ç† |
| è¯¾é¢˜ç®¡ç† | 1075510 | `/research` | è¯¾é¢˜æŒ‡å—、课题申报 |
| æ¡£æ¡ˆç®¡ç† | 1075600 | `/archive` | æ¡£æ¡ˆåˆ†ç±»ã€æ¡£æ¡ˆç®¡ç†ã€å€Ÿé˜…登记、档案统计 |
| ç³»ç»Ÿç®¡ç† | 1 | `/system` | ç”¨æˆ·ã€è§’色、菜单、部门、岗位、字典、编码规则、租户、审计日志(操作日志/登录日志)、消息中心(短信/邮箱/站内信/通知公告)、OAuth 2.0(应用管理/令牌管理)、三方登录(三方应用/三方用户)、文件存储、地区管理、公司信息配置 |
---
*(文档完)*
pom.xml
@@ -58,6 +58,9 @@
        <module>yudao-module-ums</module>
        <!-- è¯¾é¢˜ç®¡ç†æ¨¡å— -->
        <module>yudao-module-research</module>
        <!-- æ¡£æ¡ˆç®¡ç†æ¨¡å— -->
        <module>yudao-module-archive</module>
        <module>yudao-module-archive-api</module>
    </modules>
    <name>${project.artifactId}</name>
yudao-module-archive-api/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-archive-api</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        archive æ¨¡å—下,档案归档的 API æŽ¥å£å®šä¹‰ï¼Œä¾›å…¶ä»–模块引用
    </description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-common</artifactId>
        </dependency>
        <!-- Swagger æ³¨è§£ -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
yudao-module-archive-api/src/main/java/cn/iocoder/yudao/module/archive/api/document/ArchiveDocumentApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.archive.api.document;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
/**
 * æ¡£æ¡ˆå½’æ¡£ API æŽ¥å£
 *
 * ä¾›å…¶ä»–模块(课题/会员/财务)按来源单据归档,生成档案条目并带入来源附件。
 *
 * @author è¶…级管理员
 */
public interface ArchiveDocumentApi {
    String PREFIX = "/rpc-api/archive/document";
    /**
     * æŒ‰æ¥æºå•据归档;幂等:同 sourceType + sourceId å·²å½’档则返回既有档案编号
     *
     * @param reqDTO å½’档信息
     * @return æ¡£æ¡ˆç¼–号
     */
    CommonResult<Long> archiveBySource(ArchiveDocumentCreateReqDTO reqDTO);
    /**
     * åˆ¤æ–­æ¥æºå•据是否已归档
     *
     * @param sourceType æ¥æºç±»åž‹
     * @param sourceId   æ¥æºå•据编号
     * @return æ˜¯å¦å·²å½’æ¡£
     */
    CommonResult<Boolean> isArchived(Integer sourceType, Long sourceId);
    /**
     * èŽ·å–æ¥æºå•æ®çš„æ¡£æ¡ˆ
     *
     * @param sourceType æ¥æºç±»åž‹
     * @param sourceId   æ¥æºå•据编号
     * @return æ¡£æ¡ˆ
     */
    CommonResult<ArchiveDocumentRespDTO> getBySource(Integer sourceType, Long sourceId);
}
yudao-module-archive-api/src/main/java/cn/iocoder/yudao/module/archive/api/document/dto/ArchiveDocumentCreateReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.archive.api.document.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
@Schema(description = "RPC æœåŠ¡ - æ¡£æ¡ˆå½’æ¡£ Request DTO")
@Data
public class ArchiveDocumentCreateReqDTO {
    @Schema(description = "来源类型:2-课题 3-会员 4-财务", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    @NotNull(message = "来源类型不能为空")
    private Integer sourceType;
    @Schema(description = "来源单据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "来源单据编号不能为空")
    private Long sourceId;
    @Schema(description = "来源单号(课题立项编号/会员编号/报销单号)", example = "LX20260929000001")
    private String sourceNo;
    @Schema(description = "来源单据标题", example = "XX技术研究课题")
    private String sourceTitle;
    @Schema(description = "目标档案分类编码(research/member/finance)", requiredMode = Schema.RequiredMode.REQUIRED, example = "research")
    @NotEmpty(message = "目标档案分类编码不能为空")
    private String categoryCode;
    @Schema(description = "档案标题,为空时取来源单据标题")
    private String title;
    @Schema(description = "密级:1-公开 2-内部 3-保密,为空时取分类默认密级")
    private Integer secretLevel;
    @Schema(description = "摘要")
    private String summary;
    @Schema(description = "关键词,英文逗号分隔")
    private String tags;
    @Schema(description = "来源附件 BlobId åˆ—表")
    private List<Long> blobIds;
    @Schema(description = "归档日期,为空时取当天")
    private LocalDate archiveDate;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-archive-api/src/main/java/cn/iocoder/yudao/module/archive/api/document/dto/ArchiveDocumentRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.archive.api.document.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Schema(description = "RPC æœåŠ¡ - æ¡£æ¡ˆ Response DTO")
@Data
public class ArchiveDocumentRespDTO {
    @Schema(description = "档案编号", example = "1024")
    private Long id;
    @Schema(description = "档案单号", example = "DA20260929000001")
    private String no;
    @Schema(description = "档案标题", example = "XX技术研究课题")
    private String title;
    @Schema(description = "档案分类编号", example = "2")
    private Long categoryId;
    @Schema(description = "档案分类名称", example = "课题档案")
    private String categoryName;
    @Schema(description = "密级:1-公开 2-内部 3-保密", example = "2")
    private Integer secretLevel;
    @Schema(description = "载体:1-电子 2-实体 3-电子+实体", example = "3")
    private Integer carrierType;
    @Schema(description = "档案状态:10-在库 20-借出中", example = "10")
    private Integer status;
    @Schema(description = "来源类型:1-手工 2-课题 3-会员 4-财务", example = "2")
    private Integer sourceType;
    @Schema(description = "来源单号", example = "LX20260929000001")
    private String sourceNo;
    @Schema(description = "来源单据编号", example = "1024")
    private Long sourceId;
    @Schema(description = "归档日期", example = "2026-09-29")
    private LocalDate archiveDate;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-archive/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-archive</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        archive åŒ…下,档案管理模块(学会/协会型档案:书架分类、文档管理、借阅审批、归还登记、档案统计,并与课题/会员/财务模块归档联动)。
    </description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-system</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- BPM å·¥ä½œæµï¼ˆæ¡£æ¡ˆå€Ÿé˜… æ¡£æ¡ˆç®¡ç†å‘˜å®¡æ ¸ â†’ åˆ†ç®¡é¢†å¯¼å®¡æ‰¹ ä¸¤çº§å®¡æ‰¹ï¼‰ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-bpm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- æ¡£æ¡ˆ API(供课题/会员/财务模块按来源单据归档) -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-archive-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-security</artifactId>
        </dependency>
        <!-- DB ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
        </dependency>
        <!-- å·¥å…·ç±»ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-excel</artifactId>
        </dependency>
        <!-- Test æµ‹è¯•相关 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/api/document/ArchiveDocumentApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.archive.api.document;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO;
import cn.iocoder.yudao.module.archive.service.category.ArchiveCategoryService;
import cn.iocoder.yudao.module.archive.service.document.ArchiveDocumentService;
import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * æ¡£æ¡ˆå½’æ¡£ API å®žçŽ°ç±»
 *
 * ä¾›å…¶ä»–模块(课题/会员/财务)按来源单据归档,生成档案条目并带入来源附件。
 * è¯¥å®žçŽ°ä¸ºæœ¬åœ° Bean è°ƒç”¨ï¼ˆæ—  {@code @PreAuthorize}),权限由调用方(来源模块)的归档按钮权限点控制。
 *
 * @author è¶…级管理员
 */
@RestController
@RequestMapping(ArchiveDocumentApi.PREFIX)
@Validated
public class ArchiveDocumentApiImpl implements ArchiveDocumentApi {
    @Resource
    private ArchiveDocumentService archiveDocumentService;
    @Resource
    private ArchiveCategoryService archiveCategoryService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Long> archiveBySource(ArchiveDocumentCreateReqDTO reqDTO) {
        return success(archiveDocumentService.archiveBySource(reqDTO));
    }
    @Override
    public CommonResult<Boolean> isArchived(Integer sourceType, Long sourceId) {
        return success(archiveDocumentService.isArchived(sourceType, sourceId));
    }
    @Override
    public CommonResult<ArchiveDocumentRespDTO> getBySource(Integer sourceType, Long sourceId) {
        ArchiveDocumentDO document = archiveDocumentService.getBySource(sourceType, sourceId);
        if (document == null) {
            return success(null);
        }
        // è¡¥å……分类名称,便于来源模块列表页直接展示
        ArchiveDocumentRespDTO respDTO = BeanUtils.toBean(document, ArchiveDocumentRespDTO.class);
        ArchiveCategoryDO category = archiveCategoryService.getCategory(document.getCategoryId());
        if (category != null) {
            respDTO.setCategoryName(category.getName());
        }
        return success(respDTO);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/ArchiveBorrowController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,151 @@
package cn.iocoder.yudao.module.archive.controller.admin.borrow;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowRespVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowReturnReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowSaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.borrow.ArchiveBorrowDO;
import cn.iocoder.yudao.module.archive.enums.ArchiveBorrowStatusEnum;
import cn.iocoder.yudao.module.archive.service.borrow.ArchiveBorrowService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
@Tag(name = "管理后台 - æ¡£æ¡ˆå€Ÿé˜…")
@RestController
@RequestMapping("/archive/borrow")
@Validated
public class ArchiveBorrowController {
    @Resource
    private ArchiveBorrowService borrowService;
    @Resource
    private AdminUserApi adminUserApi;
    @PostMapping("/create")
    @Operation(summary = "创建档案借阅单(草稿)")
    @PreAuthorize("@ss.hasPermission('archive:borrow:create')")
    public CommonResult<Long> createBorrow(@Valid @RequestBody ArchiveBorrowSaveReqVO createReqVO) {
        return success(borrowService.createBorrow(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新档案借阅单(仅草稿或已驳回可修改)")
    @PreAuthorize("@ss.hasPermission('archive:borrow:update')")
    public CommonResult<Boolean> updateBorrow(@Valid @RequestBody ArchiveBorrowSaveReqVO updateReqVO) {
        borrowService.updateBorrow(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除档案借阅单(仅草稿可删除)")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('archive:borrow:delete')")
    public CommonResult<Boolean> deleteBorrow(@RequestParam("id") Long id) {
        borrowService.deleteBorrow(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得档案借阅单")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('archive:borrow:query')")
    public CommonResult<ArchiveBorrowRespVO> getBorrow(@RequestParam("id") Long id) {
        return success(buildBorrowDetail(borrowService.getBorrow(id)));
    }
    @GetMapping("/page")
    @Operation(summary = "获得档案借阅单分页")
    @PreAuthorize("@ss.hasPermission('archive:borrow:query')")
    public CommonResult<PageResult<ArchiveBorrowRespVO>> getBorrowPage(@Valid ArchiveBorrowPageReqVO pageReqVO) {
        PageResult<ArchiveBorrowDO> pageResult = borrowService.getBorrowPage(pageReqVO);
        return success(new PageResult<>(buildBorrowDetailList(pageResult.getList()), pageResult.getTotal()));
    }
    @GetMapping("/overdue-page")
    @Operation(summary = "获得逾期未还借阅单分页(借出中 ä¸” å½’还期限已过)")
    @PreAuthorize("@ss.hasPermission('archive:borrow:query')")
    public CommonResult<PageResult<ArchiveBorrowRespVO>> getOverdueBorrowPage(
            @Valid ArchiveBorrowPageReqVO pageReqVO) {
        PageResult<ArchiveBorrowDO> pageResult = borrowService.getOverdueBorrowPage(pageReqVO);
        return success(new PageResult<>(buildBorrowDetailList(pageResult.getList()), pageResult.getTotal()));
    }
    @PostMapping("/submit")
    @Operation(summary = "提交档案借阅审批")
    @PreAuthorize("@ss.hasPermission('archive:borrow:submit')")
    public CommonResult<Boolean> submitBorrow(@RequestParam("id") Long id,
                                              @RequestParam(value = "processDefinitionKey", required = false)
                                              String processDefinitionKey) {
        borrowService.submitBorrow(id, processDefinitionKey);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获得档案借阅审批的流程定义列表")
    @PreAuthorize("@ss.hasPermission('archive:borrow:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessDefinitionList() {
        return success(borrowService.getApproveProcessDefinitionList());
    }
    @PostMapping("/return")
    @Operation(summary = "归还登记(仅借出中可登记,归还后档案回到在库)")
    @PreAuthorize("@ss.hasPermission('archive:borrow:return')")
    public CommonResult<Boolean> returnBorrow(@Valid @RequestBody ArchiveBorrowReturnReqVO returnReqVO) {
        borrowService.returnBorrow(returnReqVO);
        return success(true);
    }
    // ==================== å†…部方法 ====================
    private ArchiveBorrowRespVO buildBorrowDetail(ArchiveBorrowDO borrow) {
        if (borrow == null) {
            return null;
        }
        return buildBorrowDetailList(Collections.singletonList(borrow)).get(0);
    }
    private List<ArchiveBorrowRespVO> buildBorrowDetailList(List<ArchiveBorrowDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // 1. å½’还确认人姓名
        Map<Long, AdminUserRespDTO> confirmUserMap = adminUserApi.getUserMap(
                convertSet(list, ArchiveBorrowDO::getReturnConfirmUserId));
        // 2. æ‹¼æŽ¥ç»“果(逾期状态实时计算,不落库)
        LocalDate today = LocalDate.now();
        return BeanUtils.toBean(list, ArchiveBorrowRespVO.class, vo -> {
            findAndThen(confirmUserMap, vo.getReturnConfirmUserId(),
                    user -> vo.setReturnConfirmUserName(user.getNickname()));
            boolean overdue = ArchiveBorrowStatusEnum.BORROWED.getStatus().equals(vo.getStatus())
                    && vo.getReturnDeadline() != null && vo.getReturnDeadline().isBefore(today);
            vo.setOverdue(overdue);
            vo.setOverdueDays(overdue ? ChronoUnit.DAYS.between(vo.getReturnDeadline(), today) : 0L);
        });
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.archive.controller.admin.borrow.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - æ¡£æ¡ˆå€Ÿé˜…单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ArchiveBorrowPageReqVO extends PageParam {
    @Schema(description = "借阅单号", example = "JY20260929000001")
    private String no;
    @Schema(description = "档案编号", example = "DA20260929000001")
    private String documentNo;
    @Schema(description = "档案标题", example = "2026年度学会工作总结")
    private String documentTitle;
    @Schema(description = "借阅人姓名", example = "张三")
    private String borrowerName;
    @Schema(description = "状态:0-草稿 15-审批中 20-借出中 30-已归还 35-已驳回", example = "20")
    private Integer status;
    @Schema(description = "借阅日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate[] borrowDate;
    @Schema(description = "归还期限")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate[] returnDeadline;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package cn.iocoder.yudao.module.archive.controller.admin.borrow.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - æ¡£æ¡ˆå€Ÿé˜…单 Response VO")
@Data
public class ArchiveBorrowRespVO {
    @Schema(description = "编号", example = "1")
    private Long id;
    @Schema(description = "借阅单号", example = "JY20260929000001")
    private String no;
    @Schema(description = "档案 ID", example = "1")
    private Long documentId;
    @Schema(description = "档案编号(快照)", example = "DA20260929000001")
    private String documentNo;
    @Schema(description = "档案标题(快照)", example = "2026年度学会工作总结")
    private String documentTitle;
    @Schema(description = "借阅人用户 ID", example = "1")
    private Long borrowerUserId;
    @Schema(description = "借阅人姓名", example = "张三")
    private String borrowerName;
    @Schema(description = "借阅人部门", example = "秘书处")
    private String borrowerDept;
    @Schema(description = "借阅人联系电话", example = "13800138000")
    private String borrowerPhone;
    @Schema(description = "借阅日期", example = "2026-09-29")
    private LocalDate borrowDate;
    @Schema(description = "借阅目的", example = "撰写年度总结引用历史数据")
    private String borrowPurpose;
    @Schema(description = "归还期限", example = "2026-10-15")
    private LocalDate returnDeadline;
    @Schema(description = "状态:0-草稿 15-审批中 20-借出中 30-已归还 35-已驳回", example = "20")
    private Integer status;
    @Schema(description = "流程实例编号", example = "a1b2c3d4")
    private String processInstanceId;
    @Schema(description = "审批通过(实际借出)时间")
    private LocalDateTime approveTime;
    @Schema(description = "实际归还时间")
    private LocalDateTime returnTime;
    @Schema(description = "归还确认人用户 ID", example = "1")
    private Long returnConfirmUserId;
    @Schema(description = "归还确认人姓名", example = "管理员")
    private String returnConfirmUserName;
    @Schema(description = "归还备注")
    private String returnRemark;
    @Schema(description = "驳回原因")
    private String rejectReason;
    @Schema(description = "驳回时间")
    private LocalDateTime rejectTime;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "是否逾期(借出中 ä¸” å½’还期限已过,实时计算)", example = "false")
    private Boolean overdue;
    @Schema(description = "逾期天数(未逾期为 0)", example = "0")
    private Long overdueDays;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowReturnReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.archive.controller.admin.borrow.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - æ¡£æ¡ˆå½’还登记 Request VO")
@Data
public class ArchiveBorrowReturnReqVO {
    @Schema(description = "借阅单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "借阅单编号不能为空")
    private Long id;
    @Schema(description = "归还备注", example = "档案完好,无缺损")
    private String returnRemark;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/borrow/vo/ArchiveBorrowSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.archive.controller.admin.borrow.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - æ¡£æ¡ˆå€Ÿé˜…单新增/修改 Request VO")
@Data
public class ArchiveBorrowSaveReqVO {
    @Schema(description = "编号", example = "1")
    private Long id;
    @Schema(description = "档案 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "借阅档案不能为空")
    private Long documentId;
    @Schema(description = "借阅人用户 ID", example = "1")
    private Long borrowerUserId;
    @Schema(description = "借阅人姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
    @NotEmpty(message = "借阅人姓名不能为空")
    private String borrowerName;
    @Schema(description = "借阅人部门", example = "秘书处")
    private String borrowerDept;
    @Schema(description = "借阅人联系电话", example = "13800138000")
    private String borrowerPhone;
    @Schema(description = "借阅日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-09-29")
    @NotNull(message = "借阅日期不能为空")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate borrowDate;
    @Schema(description = "借阅目的", example = "撰写年度总结引用历史数据")
    private String borrowPurpose;
    @Schema(description = "归还期限", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-10-15")
    @NotNull(message = "归还期限不能为空")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate returnDeadline;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/ArchiveCategoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
package cn.iocoder.yudao.module.archive.controller.admin.category;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategoryListReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategoryRespVO;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategorySaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import cn.iocoder.yudao.module.archive.service.category.ArchiveCategoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - æ¡£æ¡ˆåˆ†ç±»")
@RestController
@RequestMapping("/archive/category")
@Validated
public class ArchiveCategoryController {
    /** é¡¶çº§åˆ†ç±»çš„父分类编号 */
    private static final Long ROOT_PARENT_ID = 0L;
    @Resource
    private ArchiveCategoryService categoryService;
    @PostMapping("/create")
    @Operation(summary = "创建档案分类")
    @PreAuthorize("@ss.hasPermission('archive:category:create')")
    public CommonResult<Long> createCategory(@Valid @RequestBody ArchiveCategorySaveReqVO createReqVO) {
        return success(categoryService.createCategory(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新档案分类")
    @PreAuthorize("@ss.hasPermission('archive:category:update')")
    public CommonResult<Boolean> updateCategory(@Valid @RequestBody ArchiveCategorySaveReqVO updateReqVO) {
        categoryService.updateCategory(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除档案分类")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('archive:category:delete')")
    public CommonResult<Boolean> deleteCategory(@RequestParam("id") Long id) {
        categoryService.deleteCategory(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得档案分类")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('archive:category:query')")
    public CommonResult<ArchiveCategoryRespVO> getCategory(@RequestParam("id") Long id) {
        ArchiveCategoryDO category = categoryService.getCategory(id);
        return success(BeanUtils.toBean(category, ArchiveCategoryRespVO.class));
    }
    @GetMapping("/list")
    @Operation(summary = "获得档案分类列表(不分页)")
    @PreAuthorize("@ss.hasPermission('archive:category:query')")
    public CommonResult<List<ArchiveCategoryRespVO>> getCategoryList(@Valid ArchiveCategoryListReqVO reqVO) {
        return success(BeanUtils.toBean(categoryService.getCategoryList(reqVO), ArchiveCategoryRespVO.class));
    }
    @GetMapping("/tree")
    @Operation(summary = "获得档案分类树(供左树右表与档案分类下拉使用)")
    @PreAuthorize("@ss.hasPermission('archive:category:query')")
    public CommonResult<List<ArchiveCategoryRespVO>> getCategoryTree() {
        return success(buildTree(BeanUtils.toBean(categoryService.getCategoryList(), ArchiveCategoryRespVO.class)));
    }
    // ==================== å†…部方法 ====================
    private List<ArchiveCategoryRespVO> buildTree(List<ArchiveCategoryRespVO> categoryList) {
        if (CollUtil.isEmpty(categoryList)) {
            return Collections.emptyList();
        }
        // 1. å»ºç«‹ã€Œç¼–号 -> èŠ‚ç‚¹ã€ç´¢å¼•ï¼Œå¹¶åˆå§‹åŒ– children
        Map<Long, ArchiveCategoryRespVO> nodeMap = new LinkedHashMap<>();
        for (ArchiveCategoryRespVO category : categoryList) {
            category.setChildren(new ArrayList<>());
            nodeMap.put(category.getId(), category);
        }
        // 2. æŒ‚载子节点;父节点不存在时视为顶级,避免分类被隐藏
        List<ArchiveCategoryRespVO> roots = new ArrayList<>();
        for (ArchiveCategoryRespVO category : categoryList) {
            ArchiveCategoryRespVO parent = nodeMap.get(category.getParentId());
            if (parent == null || ROOT_PARENT_ID.equals(category.getParentId())) {
                roots.add(category);
            } else {
                parent.getChildren().add(category);
            }
        }
        return roots;
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/vo/ArchiveCategoryListReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.archive.controller.admin.category.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - æ¡£æ¡ˆåˆ†ç±»åˆ—表 Request VO")
@Data
public class ArchiveCategoryListReqVO {
    @Schema(description = "分类名称(模糊匹配)", example = "课题")
    private String name;
    @Schema(description = "状态:0-启用 1-停用", example = "0")
    private Integer status;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/vo/ArchiveCategoryRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.archive.controller.admin.category.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - æ¡£æ¡ˆåˆ†ç±» Response VO")
@Data
public class ArchiveCategoryRespVO {
    @Schema(description = "编号", example = "1")
    private Long id;
    @Schema(description = "父分类 ID,0=顶级", example = "0")
    private Long parentId;
    @Schema(description = "分类名称", example = "课题档案")
    private String name;
    @Schema(description = "分类编码,唯一", example = "research")
    private String code;
    @Schema(description = "显示排序", example = "2")
    private Integer sort;
    @Schema(description = "分类说明", example = "课题立项、结项、成果材料")
    private String description;
    @Schema(description = "该分类下档案默认密级:1-公开 2-内部 3-保密", example = "2")
    private Integer defaultSecretLevel;
    @Schema(description = "状态:0-启用 1-停用", example = "0")
    private Integer status;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "子分类")
    private List<ArchiveCategoryRespVO> children;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/category/vo/ArchiveCategorySaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.archive.controller.admin.category.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - æ¡£æ¡ˆåˆ†ç±»æ–°å¢ž/修改 Request VO")
@Data
public class ArchiveCategorySaveReqVO {
    @Schema(description = "编号", example = "1")
    private Long id;
    @Schema(description = "父分类 ID,0=顶级", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    @NotNull(message = "父分类不能为空")
    private Long parentId;
    @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "课题档案")
    @NotEmpty(message = "分类名称不能为空")
    private String name;
    @Schema(description = "分类编码,唯一", requiredMode = Schema.RequiredMode.REQUIRED, example = "research")
    @NotEmpty(message = "分类编码不能为空")
    private String code;
    @Schema(description = "显示排序", example = "2")
    private Integer sort;
    @Schema(description = "分类说明", example = "课题立项、结项、成果材料")
    private String description;
    @Schema(description = "该分类下档案默认密级:1-公开 2-内部 3-保密", example = "2")
    private Integer defaultSecretLevel;
    @Schema(description = "状态:0-启用 1-停用", example = "0")
    private Integer status;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/ArchiveDocumentController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,157 @@
package cn.iocoder.yudao.module.archive.controller.admin.document;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentRespVO;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentSaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO;
import cn.iocoder.yudao.module.archive.service.category.ArchiveCategoryService;
import cn.iocoder.yudao.module.archive.service.document.ArchiveDocumentService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.module.archive.service.document.ArchiveDocumentService.DOCUMENT_RECORD_TYPE;
@Tag(name = "管理后台 - æ¡£æ¡ˆ")
@RestController
@RequestMapping("/archive/document")
@Validated
public class ArchiveDocumentController {
    @Resource
    private ArchiveDocumentService documentService;
    @Resource
    private ArchiveCategoryService categoryService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @PostMapping("/create")
    @Operation(summary = "创建档案")
    @PreAuthorize("@ss.hasPermission('archive:document:create')")
    public CommonResult<Long> createDocument(@Valid @RequestBody ArchiveDocumentSaveReqVO createReqVO) {
        return success(documentService.createDocument(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新档案(借出中的档案不允许修改)")
    @PreAuthorize("@ss.hasPermission('archive:document:update')")
    public CommonResult<Boolean> updateDocument(@Valid @RequestBody ArchiveDocumentSaveReqVO updateReqVO) {
        documentService.updateDocument(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除档案")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('archive:document:delete')")
    public CommonResult<Boolean> deleteDocument(@RequestParam("id") Long id) {
        documentService.deleteDocument(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得档案(保密级档案需要 archive:document:sensitive æƒé™ï¼‰")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('archive:document:query')")
    public CommonResult<ArchiveDocumentRespVO> getDocument(@RequestParam("id") Long id) {
        ArchiveDocumentDO document = documentService.getDocument(id);
        return success(buildDocumentDetail(document));
    }
    @GetMapping("/page")
    @Operation(summary = "获得档案分页(无敏感档案权限时自动过滤保密档案)")
    @PreAuthorize("@ss.hasPermission('archive:document:query')")
    public CommonResult<PageResult<ArchiveDocumentRespVO>> getDocumentPage(
            @Valid ArchiveDocumentPageReqVO pageReqVO) {
        PageResult<ArchiveDocumentDO> pageResult = documentService.getDocumentPage(pageReqVO);
        return success(new PageResult<>(buildDocumentDetailList(pageResult.getList()), pageResult.getTotal()));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出档案 Excel")
    @PreAuthorize("@ss.hasPermission('archive:document:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportDocumentExcel(@Valid ArchiveDocumentPageReqVO pageReqVO,
                                    HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PAGE_SIZE_NONE);
        List<ArchiveDocumentDO> list = documentService.getDocumentList(pageReqVO);
        ExcelUtils.write(response, "档案.xls", "数据", ArchiveDocumentRespVO.class, buildDocumentDetailList(list));
    }
    // ==================== å†…部方法 ====================
    private ArchiveDocumentRespVO buildDocumentDetail(ArchiveDocumentDO document) {
        if (document == null) {
            return null;
        }
        return buildDocumentDetailList(Collections.singletonList(document)).get(0);
    }
    private List<ArchiveDocumentRespVO> buildDocumentDetailList(List<ArchiveDocumentDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // 1.1 åˆ†ç±»åç§°
        Set<Long> categoryIds = convertSet(list, ArchiveDocumentDO::getCategoryId);
        Map<Long, ArchiveCategoryDO> categoryMap = categoryIds.isEmpty() ? Collections.emptyMap()
                : categoryService.getCategoryList().stream()
                        .filter(category -> categoryIds.contains(category.getId()))
                        .collect(Collectors.toMap(ArchiveCategoryDO::getId, Function.identity(), (a, b) -> a));
        // 1.2 å½’档人姓名
        Set<Long> archivistIds = convertSet(list, ArchiveDocumentDO::getArchivistUserId);
        Map<Long, AdminUserRespDTO> archivistMap = adminUserApi.getUserMap(archivistIds);
        // 1.3 é™„ä»¶
        Map<Long, List<StorageBlobRespDTO>> attachmentMap = list.stream()
                .collect(Collectors.toMap(ArchiveDocumentDO::getId,
                        document -> storageAttachmentApi.listAttachments(DOCUMENT_RECORD_TYPE, document.getId())));
        // 2. æ‹¼æŽ¥ç»“æžœ
        return BeanUtils.toBean(list, ArchiveDocumentRespVO.class, vo -> {
            ArchiveCategoryDO category = categoryMap.get(vo.getCategoryId());
            if (category != null) {
                vo.setCategoryName(category.getName());
            }
            findAndThen(archivistMap, vo.getArchivistUserId(), user -> vo.setArchivistName(user.getNickname()));
            List<StorageBlobRespDTO> attachments = attachmentMap.getOrDefault(vo.getId(), Collections.emptyList());
            vo.setAttachmentList(attachments);
            vo.setHasAttachment(CollUtil.isNotEmpty(attachments));
        });
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/vo/ArchiveDocumentPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.archive.controller.admin.document.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - æ¡£æ¡ˆåˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ArchiveDocumentPageReqVO extends PageParam {
    @Schema(description = "档案编号", example = "DA20260929000001")
    private String no;
    @Schema(description = "档案标题", example = "2026年度学会工作总结")
    private String title;
    @Schema(description = "档案分类 ID", example = "1")
    private Long categoryId;
    @Schema(description = "密级:1-公开 2-内部 3-保密", example = "2")
    private Integer secretLevel;
    @Schema(description = "载体:1-电子 2-实体 3-电子+实体", example = "3")
    private Integer carrierType;
    @Schema(description = "来源类型:1-手工 2-课题 3-会员 4-财务", example = "2")
    private Integer sourceType;
    @Schema(description = "来源单号", example = "LX20260928000001")
    private String sourceNo;
    @Schema(description = "关键词(模糊匹配 tags å­—段)", example = "工作总结")
    private String tags;
    @Schema(description = "档案状态:10-在库 20-借出中", example = "10")
    private Integer status;
    @Schema(description = "归档日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate[] archiveDate;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/vo/ArchiveDocumentRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
package cn.iocoder.yudao.module.archive.controller.admin.document.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.archive.enums.DictTypeConstants;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - æ¡£æ¡ˆ Response VO")
@Data
@ExcelIgnoreUnannotated
public class ArchiveDocumentRespVO {
    @Schema(description = "编号", example = "1")
    private Long id;
    @Schema(description = "档案编号", example = "DA20260929000001")
    @ExcelProperty("档案编号")
    private String no;
    @Schema(description = "档案标题", example = "2026年度学会工作总结")
    @ExcelProperty("档案标题")
    private String title;
    @Schema(description = "档案分类 ID", example = "1")
    private Long categoryId;
    @Schema(description = "档案分类名称", example = "文书档案")
    @ExcelProperty("档案分类")
    private String categoryName;
    @Schema(description = "密级:1-公开 2-内部 3-保密", example = "2")
    @ExcelProperty(value = "密级", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.SECRET_LEVEL)
    private Integer secretLevel;
    @Schema(description = "载体:1-电子 2-实体 3-电子+实体", example = "3")
    @ExcelProperty(value = "载体", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.CARRIER_TYPE)
    private Integer carrierType;
    @Schema(description = "实体-书架号", example = "A")
    @ExcelProperty("书架号")
    private String shelfNo;
    @Schema(description = "实体-层号", example = "2")
    @ExcelProperty("层号")
    private String layerNo;
    @Schema(description = "实体-列号", example = "3")
    @ExcelProperty("列号")
    private String columnNo;
    @Schema(description = "实体-册号/档案盒号", example = "B-03")
    @ExcelProperty("册号/档案盒号")
    private String boxNo;
    @Schema(description = "实体存放位置描述", example = "3号库房A架2层")
    @ExcelProperty("存放位置")
    private String storePlace;
    @Schema(description = "来源类型:1-手工 2-课题 3-会员 4-财务", example = "1")
    @ExcelProperty(value = "来源类型", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.SOURCE_TYPE)
    private Integer sourceType;
    @Schema(description = "来源单号", example = "LX20260929000001")
    @ExcelProperty("来源单号")
    private String sourceNo;
    @Schema(description = "来源单据 ID", example = "1024")
    private Long sourceId;
    @Schema(description = "来源单据标题(冗余快照)", example = "XX技术研究课题")
    @ExcelProperty("来源单据标题")
    private String sourceTitle;
    @Schema(description = "归档日期", example = "2026-09-29")
    @ExcelProperty("归档日期")
    private LocalDate archiveDate;
    @Schema(description = "归档人用户 ID", example = "1")
    private Long archivistUserId;
    @Schema(description = "归档人姓名", example = "管理员")
    @ExcelProperty("归档人")
    private String archivistName;
    @Schema(description = "档案状态:10-在库 20-借出中", example = "10")
    @ExcelProperty(value = "档案状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.DOCUMENT_STATUS)
    private Integer status;
    @Schema(description = "摘要")
    @ExcelProperty("摘要")
    private String summary;
    @Schema(description = "关键词,英文逗号分隔", example = "工作总结,年度计划")
    @ExcelProperty("关键词")
    private String tags;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "附件列表")
    private List<StorageBlobRespDTO> attachmentList;
    @Schema(description = "是否有附件")
    private Boolean hasAttachment;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/document/vo/ArchiveDocumentSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.archive.controller.admin.document.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - æ¡£æ¡ˆæ–°å¢ž/修改 Request VO")
@Data
public class ArchiveDocumentSaveReqVO {
    @Schema(description = "编号", example = "1")
    private Long id;
    @Schema(description = "档案标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026年度学会工作总结")
    @NotEmpty(message = "档案标题不能为空")
    private String title;
    @Schema(description = "档案分类 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "档案分类不能为空")
    private Long categoryId;
    @Schema(description = "密级:1-公开 2-内部 3-保密,为空时取分类默认密级", example = "2")
    private Integer secretLevel;
    @Schema(description = "载体:1-电子 2-实体 3-电子+实体,为空时取默认值 3", example = "3")
    private Integer carrierType;
    @Schema(description = "实体-书架号", example = "A")
    private String shelfNo;
    @Schema(description = "实体-层号", example = "2")
    private String layerNo;
    @Schema(description = "实体-列号", example = "3")
    private String columnNo;
    @Schema(description = "实体-册号/档案盒号", example = "B-03")
    private String boxNo;
    @Schema(description = "实体存放位置描述", example = "3号库房A架2层")
    private String storePlace;
    @Schema(description = "归档日期,为空时取当天", example = "2026-09-29")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate archiveDate;
    @Schema(description = "摘要", example = "2026年度学会工作总结及下一年度计划")
    private String summary;
    @Schema(description = "关键词,英文逗号分隔", example = "工作总结,年度计划")
    private String tags;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "附件 BlobId åˆ—表")
    private List<Long> blobIds;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/ArchiveStatController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.archive.controller.admin.stat;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.archive.controller.admin.stat.vo.ArchiveStatRespVO;
import cn.iocoder.yudao.module.archive.service.stat.ArchiveStatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - æ¡£æ¡ˆç»Ÿè®¡")
@RestController
@RequestMapping("/archive/stat")
@Validated
public class ArchiveStatController {
    @Resource
    private ArchiveStatService statService;
    @GetMapping("/overview")
    @Operation(summary = "获得档案统计总览(存量 + æµè½¬ + åˆ†ç±»/密级/载体分布)")
    @PreAuthorize("@ss.hasPermission('archive:stat:query')")
    public CommonResult<ArchiveStatRespVO> getOverview() {
        return success(statService.getOverview());
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/vo/ArchiveStatCategoryVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.archive.controller.admin.stat.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - æ¡£æ¡ˆç»Ÿè®¡-分类分布")
@Data
public class ArchiveStatCategoryVO {
    @Schema(description = "分类编号", example = "2")
    private Long categoryId;
    @Schema(description = "分类名称", example = "课题档案")
    private String name;
    @Schema(description = "档案数量", example = "12")
    private Long total;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/vo/ArchiveStatRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.archive.controller.admin.stat.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - æ¡£æ¡ˆç»Ÿè®¡ Response VO")
@Data
public class ArchiveStatRespVO {
    @Schema(description = "档案总数", example = "128")
    private Long totalCount;
    @Schema(description = "在库档案数量", example = "120")
    private Long inStockCount;
    @Schema(description = "借出中档案数量", example = "8")
    private Long borrowedCount;
    @Schema(description = "借出中借阅单数量", example = "8")
    private Long borrowingCount;
    @Schema(description = "逾期未还借阅单数量", example = "2")
    private Long overdueCount;
    @Schema(description = "按分类分布")
    private List<ArchiveStatCategoryVO> byCategory;
    @Schema(description = "按密级分布")
    private List<ArchiveStatValueVO> bySecretLevel;
    @Schema(description = "按载体分布")
    private List<ArchiveStatValueVO> byCarrierType;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/controller/admin/stat/vo/ArchiveStatValueVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.archive.controller.admin.stat.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - æ¡£æ¡ˆç»Ÿè®¡-枚举值分布(密级 / è½½ä½“)")
@Data
public class ArchiveStatValueVO {
    @Schema(description = "枚举值", example = "3")
    private Integer value;
    @Schema(description = "枚举名称", example = "保密")
    private String name;
    @Schema(description = "档案数量", example = "5")
    private Long total;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/dataobject/borrow/ArchiveBorrowDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
package cn.iocoder.yudao.module.archive.dal.dataobject.borrow;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * æ¡£æ¡ˆ-借阅单 DO(借阅登记 + å½’还登记合一)
 *
 * @author è¶…级管理员
 */
@TableName("archive_borrow")
@KeySequence("archive_borrow_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArchiveBorrowDO extends BaseDO {
    @TableId
    private Long id;
    /** å€Ÿé˜…单号 JY+yyyyMMdd+6位流水 */
    private String no;
    /** æ¡£æ¡ˆ ID */
    private Long documentId;
    /** æ¡£æ¡ˆç¼–号(快照) */
    private String documentNo;
    /** æ¡£æ¡ˆæ ‡é¢˜ï¼ˆå¿«ç…§ï¼‰ */
    private String documentTitle;
    /** å€Ÿé˜…人用户 ID */
    private Long borrowerUserId;
    /** å€Ÿé˜…人姓名 */
    private String borrowerName;
    /** å€Ÿé˜…人部门 */
    private String borrowerDept;
    /** å€Ÿé˜…人联系电话 */
    private String borrowerPhone;
    /** å€Ÿé˜…日期 */
    private LocalDate borrowDate;
    /** å€Ÿé˜…目的 */
    private String borrowPurpose;
    /** å½’还期限(逾期实时计算基准) */
    private LocalDate returnDeadline;
    /** çŠ¶æ€ï¼š0-草稿 15-审批中 20-借出中 30-已归还 35-已驳回 */
    private Integer status;
    /** BPM æµç¨‹å®žä¾‹ ID */
    private String processInstanceId;
    /** å®¡æ‰¹é€šè¿‡ï¼ˆå®žé™…借出)时间 */
    private LocalDateTime approveTime;
    /** å®žé™…归还时间 */
    private LocalDateTime returnTime;
    /** å½’还确认人用户 ID */
    private Long returnConfirmUserId;
    /** å½’还备注 */
    private String returnRemark;
    /** é©³å›žåŽŸå› ï¼ˆBPM å›žå†™ï¼‰ */
    private String rejectReason;
    /** é©³å›žæ—¶é—´ */
    private LocalDateTime rejectTime;
    /** å¤‡æ³¨ */
    private String remark;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/dataobject/category/ArchiveCategoryDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.archive.dal.dataobject.category;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * æ¡£æ¡ˆ-分类 DO(书架分类,分层)
 *
 * @author è¶…级管理员
 */
@TableName("archive_category")
@KeySequence("archive_category_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArchiveCategoryDO extends BaseDO {
    @TableId
    private Long id;
    /** çˆ¶åˆ†ç±» ID,0=顶级 */
    private Long parentId;
    /** åˆ†ç±»åç§° */
    private String name;
    /** åˆ†ç±»ç¼–码,唯一 */
    private String code;
    /** æ˜¾ç¤ºæŽ’序 */
    private Integer sort;
    /** åˆ†ç±»è¯´æ˜Ž */
    private String description;
    /** è¯¥åˆ†ç±»ä¸‹æ¡£æ¡ˆé»˜è®¤å¯†çº§ï¼š1-公开 2-内部 3-保密 */
    private Integer defaultSecretLevel;
    /** çŠ¶æ€ï¼š0-启用 1-停用 */
    private Integer status;
    /** å¤‡æ³¨ */
    private String remark;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/dataobject/document/ArchiveDocumentDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.archive.dal.dataobject.document;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDate;
/**
 * æ¡£æ¡ˆ-档案主表 DO
 *
 * @author è¶…级管理员
 */
@TableName("archive_document")
@KeySequence("archive_document_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArchiveDocumentDO extends BaseDO {
    @TableId
    private Long id;
    /** æ¡£æ¡ˆç¼–号 DA+yyyyMMdd+6位流水 */
    private String no;
    /** æ¡£æ¡ˆæ ‡é¢˜ */
    private String title;
    /** æ¡£æ¡ˆåˆ†ç±» ID */
    private Long categoryId;
    /** å¯†çº§ï¼š1-公开 2-内部 3-保密 */
    private Integer secretLevel;
    /** è½½ä½“:1-电子 2-实体 3-电子+实体 */
    private Integer carrierType;
    /** å®žä½“-书架号 */
    private String shelfNo;
    /** å®žä½“-层号 */
    private String layerNo;
    /** å®žä½“-列号 */
    private String columnNo;
    /** å®žä½“-册号/档案盒号 */
    private String boxNo;
    /** å®žä½“存放位置描述 */
    private String storePlace;
    /** æ¥æºç±»åž‹ï¼š1-手工 2-课题 3-会员 4-财务 */
    private Integer sourceType;
    /** æ¥æºå•号 */
    private String sourceNo;
    /** æ¥æºå•据 ID */
    private Long sourceId;
    /** æ¥æºå•据标题(冗余快照) */
    private String sourceTitle;
    /** å½’档日期 */
    private LocalDate archiveDate;
    /** å½’档人用户 ID */
    private Long archivistUserId;
    /** æ¡£æ¡ˆçŠ¶æ€ï¼š10-在库 20-借出中 */
    private Integer status;
    /** æ‘˜è¦ */
    private String summary;
    /** å…³é”®è¯ï¼Œè‹±æ–‡é€—号分隔 */
    private String tags;
    /** å¤‡æ³¨ */
    private String remark;
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/mysql/borrow/ArchiveBorrowMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.archive.dal.mysql.borrow;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowPageReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.borrow.ArchiveBorrowDO;
import cn.iocoder.yudao.module.archive.enums.ArchiveBorrowStatusEnum;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
@Mapper
public interface ArchiveBorrowMapper extends BaseMapperX<ArchiveBorrowDO> {
    /** å€Ÿé˜…单分页查询 */
    default PageResult<ArchiveBorrowDO> selectPage(ArchiveBorrowPageReqVO reqVO) {
        return selectPage(reqVO, buildQuery(reqVO, false));
    }
    /** é€¾æœŸå€Ÿé˜…单分页查询:借出中 ä¸” å½’还期限已过 */
    default PageResult<ArchiveBorrowDO> selectOverduePage(ArchiveBorrowPageReqVO reqVO) {
        return selectPage(reqVO, buildQuery(reqVO, true));
    }
    /** é€¾æœŸå€Ÿé˜…单列表(不分页,统计与导出用) */
    default List<ArchiveBorrowDO> selectOverdueList() {
        return selectList(new LambdaQueryWrapperX<ArchiveBorrowDO>()
                .eq(ArchiveBorrowDO::getStatus, ArchiveBorrowStatusEnum.BORROWED.getStatus())
                .lt(ArchiveBorrowDO::getReturnDeadline, LocalDate.now()));
    }
    /**
     * æž„建借阅单查询条件
     *
     * @param overdueOnly true=仅查询逾期(借出中 ä¸” å½’还期限 < ä»Šå¤©ï¼‰
     */
    default LambdaQueryWrapperX<ArchiveBorrowDO> buildQuery(ArchiveBorrowPageReqVO reqVO, boolean overdueOnly) {
        LambdaQueryWrapperX<ArchiveBorrowDO> query = new LambdaQueryWrapperX<ArchiveBorrowDO>()
                .likeIfPresent(ArchiveBorrowDO::getNo, reqVO.getNo())
                .likeIfPresent(ArchiveBorrowDO::getDocumentNo, reqVO.getDocumentNo())
                .likeIfPresent(ArchiveBorrowDO::getDocumentTitle, reqVO.getDocumentTitle())
                .likeIfPresent(ArchiveBorrowDO::getBorrowerName, reqVO.getBorrowerName())
                .eqIfPresent(ArchiveBorrowDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(ArchiveBorrowDO::getBorrowDate, reqVO.getBorrowDate())
                .betweenIfPresent(ArchiveBorrowDO::getReturnDeadline, reqVO.getReturnDeadline())
                .orderByDesc(ArchiveBorrowDO::getId);
        if (overdueOnly) {
            query.eq(ArchiveBorrowDO::getStatus, ArchiveBorrowStatusEnum.BORROWED.getStatus())
                    .lt(ArchiveBorrowDO::getReturnDeadline, LocalDate.now());
        }
        return query;
    }
    /**
     * æŸ¥è¯¢æŒ‡å®šæ¡£æ¡ˆå¤„于未归还(审批中 / å€Ÿå‡ºä¸­ï¼‰çŠ¶æ€çš„å€Ÿé˜…å•
     * ç”¨äºŽã€ŒåŒä¸€æ¡£æ¡ˆåŒä¸€æ—¶é—´åªèƒ½æœ‰ä¸€æ¡æœªå½’还借阅单」的唯一性校验
     */
    default List<ArchiveBorrowDO> selectListByDocumentIdAndStatusIn(Long documentId, Collection<Integer> statuses) {
        return selectList(new LambdaQueryWrapperX<ArchiveBorrowDO>()
                .eq(ArchiveBorrowDO::getDocumentId, documentId)
                .inIfPresent(ArchiveBorrowDO::getStatus, statuses));
    }
    /** æŒ‰çŠ¶æ€ç»Ÿè®¡å€Ÿé˜…å•æ•°é‡ */
    default Long selectCountByStatus(Integer status) {
        return selectCount(ArchiveBorrowDO::getStatus, status);
    }
    /** å€Ÿå‡ºä¸­å€Ÿé˜…单数量 */
    default Long selectBorrowingCount() {
        return selectCountByStatus(ArchiveBorrowStatusEnum.BORROWED.getStatus());
    }
    /** é€¾æœŸå€Ÿé˜…单数量 */
    default Long selectOverdueCount() {
        return selectCount(new LambdaQueryWrapperX<ArchiveBorrowDO>()
                .eq(ArchiveBorrowDO::getStatus, ArchiveBorrowStatusEnum.BORROWED.getStatus())
                .lt(ArchiveBorrowDO::getReturnDeadline, LocalDate.now()));
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/mysql/category/ArchiveCategoryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.archive.dal.mysql.category;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ArchiveCategoryMapper extends BaseMapperX<ArchiveCategoryDO> {
    /** æŒ‰æŽ’序值 + ç¼–号升序取全部分类,用于构建分类树 */
    default List<ArchiveCategoryDO> selectListOrdered() {
        return selectList(new LambdaQueryWrapperX<ArchiveCategoryDO>()
                .orderByAsc(ArchiveCategoryDO::getSort)
                .orderByAsc(ArchiveCategoryDO::getId));
    }
    default List<ArchiveCategoryDO> selectListByParentId(Long parentId) {
        return selectList(ArchiveCategoryDO::getParentId, parentId);
    }
    default ArchiveCategoryDO selectByCode(String code) {
        return selectOne(ArchiveCategoryDO::getCode, code);
    }
    /**
     * ç»Ÿè®¡åŒçº§ä¸‹åŒååˆ†ç±»æ•°é‡ï¼ˆæ ¡éªŒå”¯ä¸€æ€§ç”¨ï¼‰
     *
     * @param excludeId éœ€è¦æŽ’除的分类 ID(更新场景传自身 ID,新增场景传 null)
     */
    default Long selectCountByName(String name, Long parentId, Long excludeId) {
        return selectCount(new LambdaQueryWrapperX<ArchiveCategoryDO>()
                .eq(ArchiveCategoryDO::getName, name)
                .eq(ArchiveCategoryDO::getParentId, parentId)
                .neIfPresent(ArchiveCategoryDO::getId, excludeId));
    }
    /**
     * ç»Ÿè®¡ç¼–码重复数量(校验唯一性用)
     *
     * @param excludeId éœ€è¦æŽ’除的分类 ID(更新场景传自身 ID,新增场景传 null)
     */
    default Long selectCountByCode(String code, Long excludeId) {
        return selectCount(new LambdaQueryWrapperX<ArchiveCategoryDO>()
                .eq(ArchiveCategoryDO::getCode, code)
                .neIfPresent(ArchiveCategoryDO::getId, excludeId));
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/mysql/document/ArchiveDocumentMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
package cn.iocoder.yudao.module.archive.dal.mysql.document;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.stat.vo.ArchiveStatCategoryVO;
import cn.iocoder.yudao.module.archive.controller.admin.stat.vo.ArchiveStatValueVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO;
import cn.iocoder.yudao.module.archive.enums.ArchiveDocumentStatusEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveSecretLevelEnum;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface ArchiveDocumentMapper extends BaseMapperX<ArchiveDocumentDO> {
    /**
     * æŒ‰æ¥æºå•据查询档案(归档联动幂等判断用)
     */
    default ArchiveDocumentDO selectBySource(Integer sourceType, Long sourceId) {
        return selectFirstOne(ArchiveDocumentDO::getSourceType, sourceType,
                ArchiveDocumentDO::getSourceId, sourceId);
    }
    /** åˆ†ç±»ä¸‹æ¡£æ¡ˆæ•°é‡ï¼ˆåˆ†ç±»åˆ é™¤å®ˆå«ç”¨ï¼‰ */
    default Long selectCountByCategoryId(Long categoryId) {
        return selectCount(ArchiveDocumentDO::getCategoryId, categoryId);
    }
    /**
     * æ¡£æ¡ˆåˆ†é¡µæŸ¥è¯¢
     *
     * @param excludeSensitive true=当前用户无敏感档案权限,强制排除「保密」档案
     */
    default PageResult<ArchiveDocumentDO> selectPage(ArchiveDocumentPageReqVO reqVO, boolean excludeSensitive) {
        return selectPage(reqVO, buildQuery(reqVO, excludeSensitive));
    }
    /**
     * æ¡£æ¡ˆåˆ—表查询(导出用,不分页)
     *
     * @param excludeSensitive true=当前用户无敏感档案权限,强制排除「保密」档案
     */
    default List<ArchiveDocumentDO> selectList(ArchiveDocumentPageReqVO reqVO, boolean excludeSensitive) {
        return selectList(buildQuery(reqVO, excludeSensitive));
    }
    /**
     * æž„建档案查询条件。敏感档案过滤统一走此方法,禁止在其它入口重复拼接。
     */
    default LambdaQueryWrapperX<ArchiveDocumentDO> buildQuery(ArchiveDocumentPageReqVO reqVO, boolean excludeSensitive) {
        LambdaQueryWrapperX<ArchiveDocumentDO> query = new LambdaQueryWrapperX<ArchiveDocumentDO>()
                .likeIfPresent(ArchiveDocumentDO::getNo, reqVO.getNo())
                .likeIfPresent(ArchiveDocumentDO::getTitle, reqVO.getTitle())
                .eqIfPresent(ArchiveDocumentDO::getCategoryId, reqVO.getCategoryId())
                .eqIfPresent(ArchiveDocumentDO::getSecretLevel, reqVO.getSecretLevel())
                .eqIfPresent(ArchiveDocumentDO::getCarrierType, reqVO.getCarrierType())
                .eqIfPresent(ArchiveDocumentDO::getSourceType, reqVO.getSourceType())
                .likeIfPresent(ArchiveDocumentDO::getSourceNo, reqVO.getSourceNo())
                .likeIfPresent(ArchiveDocumentDO::getTags, reqVO.getTags())
                .eqIfPresent(ArchiveDocumentDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(ArchiveDocumentDO::getArchiveDate, reqVO.getArchiveDate())
                .orderByDesc(ArchiveDocumentDO::getId);
        if (excludeSensitive) {
            query.ne(ArchiveDocumentDO::getSecretLevel, ArchiveSecretLevelEnum.SECRET.getLevel());
        }
        return query;
    }
    /** æ¡£æ¡ˆæ€»æ•° */
    default Long selectTotalCount() {
        return selectCount();
    }
    /** æŒ‰æ¡£æ¡ˆçŠ¶æ€ç»Ÿè®¡æ•°é‡ */
    default Long selectCountByStatus(Integer status) {
        return selectCount(ArchiveDocumentDO::getStatus, status);
    }
    /** åœ¨åº“档案数量 */
    default Long selectInStockCount() {
        return selectCountByStatus(ArchiveDocumentStatusEnum.IN_STOCK.getStatus());
    }
    /** å€Ÿå‡ºä¸­æ¡£æ¡ˆæ•°é‡ */
    default Long selectBorrowedCount() {
        return selectCountByStatus(ArchiveDocumentStatusEnum.BORROWED.getStatus());
    }
    /**
     * æŒ‰åˆ†ç±»èšåˆæ¡£æ¡ˆæ•°é‡ï¼ˆå« 0 æ¡çš„分类,便于前端展示全部分类)
     */
    @Select("SELECT c.id AS categoryId, c.name AS name, COUNT(d.id) AS total "
            + "FROM archive_category c LEFT JOIN archive_document d ON d.category_id = c.id AND d.deleted = 0 "
            + "WHERE c.deleted = 0 GROUP BY c.id, c.name ORDER BY c.sort ASC, c.id ASC")
    List<ArchiveStatCategoryVO> selectCountGroupByCategory();
    /** æŒ‰å¯†çº§èšåˆæ¡£æ¡ˆæ•°é‡ */
    @Select("SELECT secret_level AS value, COUNT(id) AS total FROM archive_document WHERE deleted = 0 "
            + "GROUP BY secret_level ORDER BY secret_level ASC")
    List<ArchiveStatValueVO> selectCountGroupBySecretLevel();
    /** æŒ‰è½½ä½“聚合档案数量 */
    @Select("SELECT carrier_type AS value, COUNT(id) AS total FROM archive_document WHERE deleted = 0 "
            + "GROUP BY carrier_type ORDER BY carrier_type ASC")
    List<ArchiveStatValueVO> selectCountGroupByCarrierType();
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/redis/ArchiveNoRedisDAO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.archive.dal.redis;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import java.time.Duration;
import java.time.LocalDateTime;
/**
 * Archive ç¼–号 Redis DAO
 *
 * @author è¶…级管理员
 */
@Repository
public class ArchiveNoRedisDAO {
    /**
     * æ¡£æ¡ˆç¼–号 {@link cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO}
     */
    public static final String DOCUMENT_NO_PREFIX = "DA";
    /**
     * å€Ÿé˜…单号 {@link cn.iocoder.yudao.module.archive.dal.dataobject.borrow.ArchiveBorrowDO}
     */
    public static final String BORROW_NO_PREFIX = "JY";
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    public String generate(String prefix) {
        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
        String key = RedisKeyConstants.NO + noPrefix;
        Long no = stringRedisTemplate.opsForValue().increment(key);
        stringRedisTemplate.expire(key, Duration.ofDays(1L));
        return noPrefix + String.format("%06d", no);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/dal/redis/RedisKeyConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package cn.iocoder.yudao.module.archive.dal.redis;
/**
 * Archive Redis Key å¸¸é‡
 *
 * @author è¶…级管理员
 */
public interface RedisKeyConstants {
    String NO = "archive:seq_no:";
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveBorrowStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * æ¡£æ¡ˆå€Ÿé˜…单状态枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum ArchiveBorrowStatusEnum {
    DRAFT(0, "草稿"),
    APPROVING(15, "审批中"),
    BORROWED(20, "借出中"),
    RETURNED(30, "已归还"),
    REJECTED(35, "已驳回"),
    ;
    private final Integer status;
    private final String name;
    public static ArchiveBorrowStatusEnum valueOf(Integer status) {
        return ArrayUtil.firstMatch(e -> e.getStatus().equals(status), values());
    }
    /** æŒ‰çŠ¶æ€å€¼å–ä¸­æ–‡åï¼ŒæœªåŒ¹é…æ—¶å›žé€€åŽŸå§‹å€¼ï¼Œä¾¿äºŽé”™è¯¯æç¤ºå¯è¯» */
    public static String nameOf(Integer status) {
        ArchiveBorrowStatusEnum item = valueOf(status);
        return item != null ? item.getName() : String.valueOf(status);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveCarrierTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * æ¡£æ¡ˆè½½ä½“类型枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum ArchiveCarrierTypeEnum {
    ELECTRONIC(1, "电子"),
    PHYSICAL(2, "实体"),
    BOTH(3, "电子+实体"),
    ;
    private final Integer type;
    private final String name;
    public static ArchiveCarrierTypeEnum valueOf(Integer type) {
        return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
    }
    /** æŒ‰è½½ä½“值取中文名,未匹配时回退原始值,便于错误提示可读 */
    public static String nameOf(Integer type) {
        ArchiveCarrierTypeEnum item = valueOf(type);
        return item != null ? item.getName() : String.valueOf(type);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveCategoryStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * æ¡£æ¡ˆåˆ†ç±»çŠ¶æ€æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum ArchiveCategoryStatusEnum {
    ENABLE(0, "启用"),
    DISABLE(1, "停用"),
    ;
    private final Integer status;
    private final String name;
    public static ArchiveCategoryStatusEnum valueOf(Integer status) {
        return ArrayUtil.firstMatch(e -> e.getStatus().equals(status), values());
    }
    /** æŒ‰çŠ¶æ€å€¼å–ä¸­æ–‡åï¼ŒæœªåŒ¹é…æ—¶å›žé€€åŽŸå§‹å€¼ï¼Œä¾¿äºŽé”™è¯¯æç¤ºå¯è¯» */
    public static String nameOf(Integer status) {
        ArchiveCategoryStatusEnum item = valueOf(status);
        return item != null ? item.getName() : String.valueOf(status);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveDocumentStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * æ¡£æ¡ˆçŠ¶æ€æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum ArchiveDocumentStatusEnum {
    IN_STOCK(10, "在库"),
    BORROWED(20, "借出中"),
    ;
    private final Integer status;
    private final String name;
    public static ArchiveDocumentStatusEnum valueOf(Integer status) {
        return ArrayUtil.firstMatch(e -> e.getStatus().equals(status), values());
    }
    /** æŒ‰çŠ¶æ€å€¼å–ä¸­æ–‡åï¼ŒæœªåŒ¹é…æ—¶å›žé€€åŽŸå§‹å€¼ï¼Œä¾¿äºŽé”™è¯¯æç¤ºå¯è¯» */
    public static String nameOf(Integer status) {
        ArchiveDocumentStatusEnum item = valueOf(status);
        return item != null ? item.getName() : String.valueOf(status);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveSecretLevelEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * æ¡£æ¡ˆå¯†çº§æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum ArchiveSecretLevelEnum {
    PUBLIC(1, "公开"),
    INTERNAL(2, "内部"),
    SECRET(3, "保密"),
    ;
    private final Integer level;
    private final String name;
    public static ArchiveSecretLevelEnum valueOf(Integer level) {
        return ArrayUtil.firstMatch(e -> e.getLevel().equals(level), values());
    }
    /** æŒ‰å¯†çº§å€¼å–中文名,未匹配时回退原始值,便于错误提示可读 */
    public static String nameOf(Integer level) {
        ArchiveSecretLevelEnum item = valueOf(level);
        return item != null ? item.getName() : String.valueOf(level);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ArchiveSourceTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * æ¡£æ¡ˆæ¥æºç±»åž‹æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum ArchiveSourceTypeEnum {
    MANUAL(1, "手工录入"),
    RESEARCH(2, "课题"),
    MEMBER(3, "会员"),
    FINANCE(4, "财务"),
    ;
    private final Integer type;
    private final String name;
    public static ArchiveSourceTypeEnum valueOf(Integer type) {
        return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
    }
    /** æŒ‰æ¥æºå€¼å–中文名,未匹配时回退原始值,便于错误提示可读 */
    public static String nameOf(Integer type) {
        ArchiveSourceTypeEnum item = valueOf(type);
        return item != null ? item.getName() : String.valueOf(type);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/DictTypeConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.archive.enums;
/**
 * Archive å­—典类型常量
 *
 * @author è¶…级管理员
 */
public interface DictTypeConstants {
    /** æ¡£æ¡ˆåˆ†ç±»çŠ¶æ€ */
    String CATEGORY_STATUS = "archive_category_status";
    /** æ¡£æ¡ˆçŠ¶æ€ */
    String DOCUMENT_STATUS = "archive_document_status";
    /** æ¡£æ¡ˆå¯†çº§ */
    String SECRET_LEVEL = "archive_secret_level";
    /** æ¡£æ¡ˆè½½ä½“ */
    String CARRIER_TYPE = "archive_carrier_type";
    /** æ¡£æ¡ˆæ¥æºç±»åž‹ */
    String SOURCE_TYPE = "archive_source_type";
    /** å€Ÿé˜…单状态 */
    String BORROW_STATUS = "archive_borrow_status";
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/enums/ErrorCodeConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.archive.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
 * Archive é”™è¯¯ç å¸¸é‡
 *
 * archive æ¨¡å—,使用 1-030-110-000 æ®µ
 *
 * @author è¶…级管理员
 */
public interface ErrorCodeConstants {
    // ========== æ¡£æ¡ˆåˆ†ç±» 1_030_110_000 ==========
    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_030_110_000, "档案分类不存在,请刷新页面后重新选择档案分类");
    ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_030_110_001, "保存失败:同级分类下已存在名为〔{}〕的档案分类,请更换分类名称");
    ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_030_110_002, "保存失败:分类编码({})已被分类〔{}〕使用,请更换分类编码");
    ErrorCode CATEGORY_HAS_CHILDREN = new ErrorCode(1_030_110_003, "删除失败:档案分类〔{}〕下还有 {} ä¸ªå­åˆ†ç±»ï¼Œè¯·å…ˆåˆ é™¤æˆ–迁移这些子分类后再删除");
    ErrorCode CATEGORY_HAS_DOCUMENT = new ErrorCode(1_030_110_004, "删除失败:档案分类〔{}〕下还有 {} æ¡æ¡£æ¡ˆï¼Œè¯·å…ˆå°†è¿™äº›æ¡£æ¡ˆè¿ç§»åˆ°å…¶ä»–分类后再删除");
    ErrorCode CATEGORY_PARENT_INVALID = new ErrorCode(1_030_110_005, "保存失败:不能把分类〔{}〕的上级设置为它自己或其子分类,请重新选择上级分类");
    ErrorCode CATEGORY_DISABLED = new ErrorCode(1_030_110_006, "保存失败:所选档案分类〔{}〕当前状态为〔停用〕,请选择已启用的档案分类");
    // ========== æ¡£æ¡ˆ 1_030_110_100 ==========
    ErrorCode DOCUMENT_NOT_EXISTS = new ErrorCode(1_030_110_100, "档案不存在,请刷新页面后重试");
    ErrorCode DOCUMENT_NO_EXISTS = new ErrorCode(1_030_110_101, "生成档案编号失败,请重新提交");
    ErrorCode DOCUMENT_UPDATE_FAIL_BORROWED = new ErrorCode(1_030_110_102, "更新失败:档案〔{}〕当前状态为〔借出中〕,请等待归还后再修改");
    ErrorCode DOCUMENT_DELETE_FAIL_STATUS = new ErrorCode(1_030_110_103, "删除失败:档案〔{}〕当前状态为〔{}〕或存在未归还的借阅单,只有「在库」且无未归还借阅单的档案才能删除");
    ErrorCode DOCUMENT_SENSITIVE_NO_PERMISSION = new ErrorCode(1_030_110_104, "查看失败:档案〔{}〕为〔保密〕级,您没有访问保密档案的权限,请联系档案管理员开通「archive:document:sensitive」权限");
    ErrorCode DOCUMENT_SOURCE_NOT_EXISTS = new ErrorCode(1_030_110_105, "归档失败:来源单据({})不存在或已被删除,请刷新列表后重试");
    ErrorCode DOCUMENT_SOURCE_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_110_106, "归档失败:档案分类编码({})不存在,请先执行档案模块初始化脚本创建对应档案分类后重试");
    ErrorCode DOCUMENT_SOURCE_TYPE_UNSUPPORTED = new ErrorCode(1_030_110_107, "归档失败:不支持的来源类型({}),请使用课题/会员/财务模块列表页的「归档」入口");
    ErrorCode DOCUMENT_ARCHIVE_FAIL_SOURCE_STATUS = new ErrorCode(1_030_110_108, "归档失败:来源单据({})当前状态为〔{}〕,只有已通过/已完结的单据才能归档");
    ErrorCode DOCUMENT_DUPLICATE_SOURCE = new ErrorCode(1_030_110_109, "重复归档:来源单据〔{}〕已生成档案〔{}〕,请勿重复归档");
    ErrorCode DOCUMENT_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_110_110, "保存失败:所选档案分类不存在,请刷新页面后重新选择档案分类");
    ErrorCode DOCUMENT_BORROWED_CANNOT_BORROW = new ErrorCode(1_030_110_111, "操作失败:档案〔{}〕当前状态为〔借出中〕,请等待归还后再操作");
    // ========== å€Ÿé˜… 1_030_110_200 ==========
    ErrorCode BORROW_NOT_EXISTS = new ErrorCode(1_030_110_200, "借阅单不存在,请刷新页面后重试");
    ErrorCode BORROW_NO_EXISTS = new ErrorCode(1_030_110_201, "生成借阅单号失败,请重新提交");
    ErrorCode BORROW_UPDATE_FAIL_STATUS = new ErrorCode(1_030_110_202, "更新失败:借阅单({})当前状态为〔{}〕,只有「草稿」或「已驳回」的借阅单才能修改");
    ErrorCode BORROW_DELETE_FAIL_STATUS = new ErrorCode(1_030_110_203, "删除失败:借阅单({})当前状态为〔{}〕,只有「草稿」状态的借阅单才能删除");
    ErrorCode BORROW_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_110_204, "提交审批失败:借阅单({})当前状态为〔{}〕,只有「草稿」或「已驳回」的借阅单才能提交");
    ErrorCode BORROW_DEADLINE_BEFORE_DATE = new ErrorCode(1_030_110_205, "保存失败:归还期限({})不能早于借阅日期({}),请重新选择归还期限");
    ErrorCode BORROW_DOCUMENT_NOT_IN_STOCK = new ErrorCode(1_030_110_206, "提交失败:档案〔{}〕当前状态为〔{}〕,只有「在库」的档案才能借阅");
    ErrorCode BORROW_DOCUMENT_ALREADY_BORROWED = new ErrorCode(1_030_110_207, "提交失败:档案〔{}〕已存在未归还的借阅单({}),请等待其归还后再借阅");
    ErrorCode BORROW_RETURN_FAIL_STATUS = new ErrorCode(1_030_110_208, "归还失败:借阅单({})当前状态为〔{}〕,只有「借出中」的借阅单才能登记归还");
    ErrorCode BORROW_DOCUMENT_LOCKED = new ErrorCode(1_030_110_209, "操作失败:档案〔{}〕正在被其他借阅流程处理,请稍后重试;若持续失败请联系档案管理员核对借阅记录");
    ErrorCode BORROW_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_110_210, "档案借阅审批分类不存在,请先在BPM模块创建档案借阅审批流程分类");
    ErrorCode BORROW_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_110_211, "档案借阅审批分类(archive_borrow_approve)下没有已发布的流程,无法提交借阅审批。请先在「BPM â†’ æµç¨‹å®šä¹‰ã€ä¸­åˆ›å»ºå¹¶å‘布档案借阅审批流程后重试");
    ErrorCode BORROW_DOCUMENT_NOT_EXISTS = new ErrorCode(1_030_110_212, "保存失败:所选档案不存在或已被删除,请刷新列表后重新选择档案");
    ErrorCode BORROW_BORROWER_REQUIRED = new ErrorCode(1_030_110_213, "保存失败:借阅人不能为空,请填写或选择借阅人后再保存");
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/framework/web/config/ArchiveWebConfiguration.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.archive.framework.web.config;
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class ArchiveWebConfiguration {
    @Bean
    public GroupedOpenApi archiveGroupedOpenApi() {
        return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("archive");
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/borrow/ArchiveBorrowService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.archive.service.borrow;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowReturnReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowSaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.borrow.ArchiveBorrowDO;
import java.util.List;
import java.util.Map;
/**
 * æ¡£æ¡ˆå€Ÿé˜…单 Service æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface ArchiveBorrowService {
    /**
     * åˆ›å»ºå€Ÿé˜…单(草稿)
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return å€Ÿé˜…单编号
     */
    Long createBorrow(ArchiveBorrowSaveReqVO createReqVO);
    /**
     * æ›´æ–°å€Ÿé˜…单(仅草稿或已驳回可修改)
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateBorrow(ArchiveBorrowSaveReqVO updateReqVO);
    /**
     * åˆ é™¤å€Ÿé˜…单(仅草稿可删除)
     *
     * @param id å€Ÿé˜…单编号
     */
    void deleteBorrow(Long id);
    /**
     * èŽ·å¾—å€Ÿé˜…å•
     *
     * @param id å€Ÿé˜…单编号
     * @return å€Ÿé˜…单
     */
    ArchiveBorrowDO getBorrow(Long id);
    /**
     * èŽ·å¾—å€Ÿé˜…å•åˆ†é¡µ
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return å€Ÿé˜…单分页
     */
    PageResult<ArchiveBorrowDO> getBorrowPage(ArchiveBorrowPageReqVO reqVO);
    /**
     * èŽ·å¾—é€¾æœŸå€Ÿé˜…å•åˆ†é¡µï¼ˆå€Ÿå‡ºä¸­ ä¸” å½’还期限已过)
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return å€Ÿé˜…单分页
     */
    PageResult<ArchiveBorrowDO> getOverdueBorrowPage(ArchiveBorrowPageReqVO reqVO);
    /**
     * æäº¤å€Ÿé˜…审批
     *
     * @param id                   å€Ÿé˜…单编号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ key,为空时自动取档案借阅审批分类下最新的已发布流程
     */
    void submitBorrow(Long id, String processDefinitionKey);
    /**
     * èŽ·å¾—æ¡£æ¡ˆå€Ÿé˜…å®¡æ‰¹å¯ç”¨çš„æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表(key / name)
     */
    List<Map<String, Object>> getApproveProcessDefinitionList();
    /**
     * å½’还登记(仅借出中可登记)
     *
     * @param returnReqVO å½’还信息
     */
    void returnBorrow(ArchiveBorrowReturnReqVO returnReqVO);
    /**
     * å›žå†™å€Ÿé˜…单审批结果(BPM ç›‘听器调用)
     *
     * @param id        å€Ÿé˜…单编号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     * @param reason    é©³å›žåŽŸå› 
     */
    void updateBorrowAuditStatus(Long id, Integer bpmResult, String reason);
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/borrow/ArchiveBorrowServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,351 @@
package cn.iocoder.yudao.module.archive.service.borrow;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowReturnReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.borrow.vo.ArchiveBorrowSaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.borrow.ArchiveBorrowDO;
import cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO;
import cn.iocoder.yudao.module.archive.dal.mysql.borrow.ArchiveBorrowMapper;
import cn.iocoder.yudao.module.archive.dal.redis.ArchiveNoRedisDAO;
import cn.iocoder.yudao.module.archive.enums.ArchiveBorrowStatusEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveDocumentStatusEnum;
import cn.iocoder.yudao.module.archive.service.document.ArchiveDocumentService;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.archive.enums.ErrorCodeConstants.*;
/**
 * æ¡£æ¡ˆå€Ÿé˜…单 Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Service
@Validated
@Slf4j
public class ArchiveBorrowServiceImpl implements ArchiveBorrowService {
    /** æ¡£æ¡ˆå€Ÿé˜…审批的 BPM æµç¨‹åˆ†ç±»ç¼–码 */
    public static final String BORROW_APPROVE_CATEGORY_CODE = "archive_borrow_approve";
    @Resource
    private ArchiveBorrowMapper borrowMapper;
    @Resource
    private ArchiveDocumentService documentService;
    @Resource
    private ArchiveNoRedisDAO archiveNoRedisDAO;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBorrow(ArchiveBorrowSaveReqVO createReqVO) {
        // 1. æ ¡éªŒå€Ÿé˜…人、归还期限、档案可借
        validateBorrower(createReqVO.getBorrowerName());
        validateReturnDeadline(createReqVO);
        ArchiveDocumentDO document = validateDocumentBorrowable(createReqVO.getDocumentId(), null);
        // 2. ç»„装借阅单(档案编号/标题取快照,避免档案改名后历史单据失真)
        ArchiveBorrowDO borrow = BeanUtils.toBean(createReqVO, ArchiveBorrowDO.class);
        borrow.setId(null);
        borrow.setNo(archiveNoRedisDAO.generate(ArchiveNoRedisDAO.BORROW_NO_PREFIX));
        borrow.setDocumentNo(document.getNo());
        borrow.setDocumentTitle(document.getTitle());
        borrow.setStatus(ArchiveBorrowStatusEnum.DRAFT.getStatus());
        borrowMapper.insert(borrow);
        return borrow.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBorrow(ArchiveBorrowSaveReqVO updateReqVO) {
        // 1.1 æ ¡éªŒå€Ÿé˜…单存在且状态允许修改
        ArchiveBorrowDO oldBorrow = validateBorrowExists(updateReqVO.getId());
        if (!ArchiveBorrowStatusEnum.DRAFT.getStatus().equals(oldBorrow.getStatus())
                && !ArchiveBorrowStatusEnum.REJECTED.getStatus().equals(oldBorrow.getStatus())) {
            throw exception(BORROW_UPDATE_FAIL_STATUS, oldBorrow.getNo(),
                    ArchiveBorrowStatusEnum.nameOf(oldBorrow.getStatus()));
        }
        // 1.2 æ ¡éªŒå€Ÿé˜…人、归还期限、档案可借(排除自身)
        validateBorrower(updateReqVO.getBorrowerName());
        validateReturnDeadline(updateReqVO);
        ArchiveDocumentDO document = validateDocumentBorrowable(updateReqVO.getDocumentId(), updateReqVO.getId());
        // 2. æ›´æ–°ï¼ˆæ¡£æ¡ˆç¼–号/标题刷新为最新快照)
        ArchiveBorrowDO updateObj = BeanUtils.toBean(updateReqVO, ArchiveBorrowDO.class);
        updateObj.setDocumentNo(document.getNo());
        updateObj.setDocumentTitle(document.getTitle());
        borrowMapper.updateById(updateObj);
    }
    @Override
    public void deleteBorrow(Long id) {
        // 1. æ ¡éªŒå€Ÿé˜…单存在且为草稿
        ArchiveBorrowDO borrow = validateBorrowExists(id);
        if (!ArchiveBorrowStatusEnum.DRAFT.getStatus().equals(borrow.getStatus())) {
            throw exception(BORROW_DELETE_FAIL_STATUS, borrow.getNo(),
                    ArchiveBorrowStatusEnum.nameOf(borrow.getStatus()));
        }
        borrowMapper.deleteById(id);
    }
    @Override
    public ArchiveBorrowDO getBorrow(Long id) {
        return borrowMapper.selectById(id);
    }
    @Override
    public PageResult<ArchiveBorrowDO> getBorrowPage(ArchiveBorrowPageReqVO reqVO) {
        return borrowMapper.selectPage(reqVO);
    }
    @Override
    public PageResult<ArchiveBorrowDO> getOverdueBorrowPage(ArchiveBorrowPageReqVO reqVO) {
        return borrowMapper.selectOverduePage(reqVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitBorrow(Long id, String processDefinitionKey) {
        // 1.1 æ ¡éªŒå€Ÿé˜…单存在且状态允许提交
        ArchiveBorrowDO borrow = validateBorrowExists(id);
        if (!ArchiveBorrowStatusEnum.DRAFT.getStatus().equals(borrow.getStatus())
                && !ArchiveBorrowStatusEnum.REJECTED.getStatus().equals(borrow.getStatus())) {
            throw exception(BORROW_SUBMIT_FAIL_STATUS, borrow.getNo(),
                    ArchiveBorrowStatusEnum.nameOf(borrow.getStatus()));
        }
        // 1.2 æ ¡éªŒæ¡£æ¡ˆä»å¯å€Ÿï¼ˆæäº¤å‰äºŒæ¬¡æ ¡éªŒï¼Œé¿å…è‰ç¨¿æœŸé—´è¢«ä»–人借走)
        validateDocumentBorrowable(borrow.getDocumentId(), borrow.getId());
        // 1.3 å…ˆè§£æžå¯ç”¨æµç¨‹ï¼šæ²¡æœ‰å·²å‘布流程时直接失败,避免留下无法提交的孤儿草稿
        String definitionKey = resolveProcessDefinitionKey(processDefinitionKey);
        // 2. å‘起流程并回写状态
        String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(definitionKey)
                        .setBusinessKey(String.valueOf(id)));
        borrowMapper.updateById(new ArchiveBorrowDO()
                .setId(id)
                .setStatus(ArchiveBorrowStatusEnum.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId)
                // é‡æ–°æäº¤æ—¶æ¸…空上一次的驳回信息
                .setRejectReason("")
                .setRejectTime(null));
    }
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList() {
        validateApproveCategory();
        List<BpmProcessDefinitionInfoDO> definitionInfoList =
                bpmProcessDefinitionService.getProcessDefinitionInfoListByCategory(BORROW_APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void returnBorrow(ArchiveBorrowReturnReqVO returnReqVO) {
        // 1. æ ¡éªŒå€Ÿé˜…单存在且为借出中
        ArchiveBorrowDO borrow = validateBorrowExists(returnReqVO.getId());
        if (!ArchiveBorrowStatusEnum.BORROWED.getStatus().equals(borrow.getStatus())) {
            throw exception(BORROW_RETURN_FAIL_STATUS, borrow.getNo(),
                    ArchiveBorrowStatusEnum.nameOf(borrow.getStatus()));
        }
        // 2. æ¡ä»¶æ›´æ–°å€Ÿé˜…单为已归还,避免并发重复归还
        int rows = borrowMapper.update(null, new LambdaUpdateWrapper<ArchiveBorrowDO>()
                .eq(ArchiveBorrowDO::getId, borrow.getId())
                .eq(ArchiveBorrowDO::getStatus, ArchiveBorrowStatusEnum.BORROWED.getStatus())
                .set(ArchiveBorrowDO::getStatus, ArchiveBorrowStatusEnum.RETURNED.getStatus())
                .set(ArchiveBorrowDO::getReturnTime, LocalDateTime.now())
                .set(ArchiveBorrowDO::getReturnConfirmUserId, getLoginUserId())
                .set(ArchiveBorrowDO::getReturnRemark, returnReqVO.getReturnRemark()));
        if (rows == 0) {
            throw exception(BORROW_RETURN_FAIL_STATUS, borrow.getNo(),
                    ArchiveBorrowStatusEnum.nameOf(borrow.getStatus()));
        }
        // 3. æ¡£æ¡ˆçŠ¶æ€å›žåˆ°ã€Œåœ¨åº“ã€
        if (!documentService.updateDocumentStatusToInStock(borrow.getDocumentId())) {
            log.warn("[returnBorrow] å€Ÿé˜…单({}) å½’还成功,但档案({}) çŠ¶æ€æœªä»Žã€Œå€Ÿå‡ºä¸­ã€æ›´æ–°ä¸ºã€Œåœ¨åº“ã€ï¼Œè¯·äººå·¥æ ¸å¯¹",
                    borrow.getNo(), borrow.getDocumentNo());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBorrowAuditStatus(Long id, Integer bpmResult, String reason) {
        ArchiveBorrowDO borrow = borrowMapper.selectById(id);
        if (borrow == null) {
            log.warn("[updateBorrowAuditStatus] å€Ÿé˜…单({}) ä¸å­˜åœ¨ï¼Œå¿½ç•¥ BPM ç»“果回写", id);
            return;
        }
        if (!ArchiveBorrowStatusEnum.APPROVING.getStatus().equals(borrow.getStatus())) {
            log.warn("[updateBorrowAuditStatus] å€Ÿé˜…单({}) ä¸å¤„于审批中状态,忽略 BPM ç»“果回写", borrow.getNo());
            return;
        }
        // å®¡æ‰¹é€šè¿‡ï¼šæ¡£æ¡ˆæ¡ä»¶æ›´æ–°ä¸ºã€Œå€Ÿå‡ºä¸­ã€ï¼›è‹¥æ¡£æ¡ˆå·²è¢«ä»–人借出,条件更新失败则整体回滚,避免出现「借阅单已借出但档案未锁定」的脏状态
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            if (!documentService.updateDocumentStatusToBorrowed(borrow.getDocumentId())) {
                throw exception(BORROW_DOCUMENT_LOCKED, borrow.getDocumentNo());
            }
            borrowMapper.updateById(new ArchiveBorrowDO()
                    .setId(id)
                    .setStatus(ArchiveBorrowStatusEnum.BORROWED.getStatus())
                    .setApproveTime(LocalDateTime.now()));
            return;
        }
        // é©³å›ž
        if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            borrowMapper.updateById(new ArchiveBorrowDO()
                    .setId(id)
                    .setStatus(ArchiveBorrowStatusEnum.REJECTED.getStatus())
                    .setRejectReason(reason)
                    .setRejectTime(LocalDateTime.now()));
            return;
        }
        // æ’¤å›ž / å–消:回到草稿,清空流程实例
        borrowMapper.updateById(new ArchiveBorrowDO()
                .setId(id)
                .setStatus(ArchiveBorrowStatusEnum.DRAFT.getStatus())
                .setProcessInstanceId("")
                .setRejectReason("")
                .setRejectTime(null));
    }
    // ==================== æ ¡éªŒé€»è¾‘ ====================
    private ArchiveBorrowDO validateBorrowExists(Long id) {
        ArchiveBorrowDO borrow = id != null ? borrowMapper.selectById(id) : null;
        if (borrow == null) {
            throw exception(BORROW_NOT_EXISTS);
        }
        return borrow;
    }
    private void validateBorrower(String borrowerName) {
        if (StrUtil.isBlank(borrowerName)) {
            throw exception(BORROW_BORROWER_REQUIRED);
        }
    }
    private void validateReturnDeadline(ArchiveBorrowSaveReqVO reqVO) {
        if (reqVO.getReturnDeadline() != null && reqVO.getBorrowDate() != null
                && reqVO.getReturnDeadline().isBefore(reqVO.getBorrowDate())) {
            throw exception(BORROW_DEADLINE_BEFORE_DATE, reqVO.getReturnDeadline(), reqVO.getBorrowDate());
        }
    }
    /**
     * æ ¡éªŒæ¡£æ¡ˆå¯å€Ÿï¼šæ¡£æ¡ˆå­˜åœ¨ã€å¤„于在库、且没有未归还的借阅单
     *
     * @param excludeBorrowId éœ€è¦æŽ’除的借阅单编号(更新 / é‡æ–°æäº¤æ—¶ä¼ è‡ªèº«ï¼‰
     */
    private ArchiveDocumentDO validateDocumentBorrowable(Long documentId, Long excludeBorrowId) {
        ArchiveDocumentDO document = documentId != null
                ? documentService.getDocumentWithoutPermissionCheck(documentId) : null;
        if (document == null) {
            throw exception(BORROW_DOCUMENT_NOT_EXISTS);
        }
        if (!ArchiveDocumentStatusEnum.IN_STOCK.getStatus().equals(document.getStatus())) {
            throw exception(BORROW_DOCUMENT_NOT_IN_STOCK, document.getNo(),
                    ArchiveDocumentStatusEnum.nameOf(document.getStatus()));
        }
        List<ArchiveBorrowDO> openBorrows = borrowMapper.selectListByDocumentIdAndStatusIn(documentId,
                List.of(ArchiveBorrowStatusEnum.APPROVING.getStatus(), ArchiveBorrowStatusEnum.BORROWED.getStatus()));
        for (ArchiveBorrowDO openBorrow : openBorrows) {
            if (excludeBorrowId != null && excludeBorrowId.equals(openBorrow.getId())) {
                continue;
            }
            throw exception(BORROW_DOCUMENT_ALREADY_BORROWED, document.getNo(), openBorrow.getNo());
        }
        return document;
    }
    private void validateApproveCategory() {
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(BORROW_APPROVE_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(BORROW_BPM_CATEGORY_NOT_EXISTS);
        }
    }
    /** è§£æžå¯ç”¨çš„æµç¨‹å®šä¹‰ key:优先使用前端传入且确实已发布的最新流程,否则取分类下最新已发布流程 */
    private String resolveProcessDefinitionKey(String processDefinitionKey) {
        List<Map<String, Object>> definitionList = getApproveProcessDefinitionList();
        if (CollUtil.isEmpty(definitionList)) {
            throw exception(BORROW_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        if (StrUtil.isNotBlank(processDefinitionKey)) {
            for (Map<String, Object> item : definitionList) {
                if (processDefinitionKey.equals(String.valueOf(item.get("key")))) {
                    return processDefinitionKey;
                }
            }
        }
        return String.valueOf(definitionList.get(0).get("key"));
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/borrow/listener/ArchiveBorrowStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.archive.service.borrow.listener;
import cn.iocoder.yudao.module.archive.service.borrow.ArchiveBorrowService;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * æ¡£æ¡ˆå€Ÿé˜…单审批状态监听器
 *
 * ç›‘听 BPM æµç¨‹å®žä¾‹çŠ¶æ€å˜æ›´äº‹ä»¶ï¼Œå›žå†™å€Ÿé˜…å•çŠ¶æ€ï¼šå®¡æ‰¹é€šè¿‡â†’å€Ÿå‡ºä¸­ï¼ˆå¹¶é”å®šæ¡£æ¡ˆï¼‰ï¼Œé©³å›žâ†’å·²é©³å›žï¼Œæ’¤å›žâ†’è‰ç¨¿
 */
@Component
@Slf4j
public class ArchiveBorrowStatusListener extends BpmProcessInstanceStatusEventListener {
    /** æ¡£æ¡ˆå€Ÿé˜…审批流程定义 key,需与 {@code docs/sql/archive_borrow_approve.bpmn} ä¿æŒä¸€è‡´ */
    private static final String BORROW_PROCESS_DEFINITION_KEY = "archive_borrow_approve";
    @Resource
    private ArchiveBorrowService borrowService;
    @Override
    protected String getProcessDefinitionKey() {
        return BORROW_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long borrowId;
        try {
            borrowId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„æ¡£æ¡ˆå€Ÿé˜…单编号", event.getBusinessKey());
            return;
        }
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,borrowId={}, status={}", borrowId, event.getStatus());
        // å›žå†™å€Ÿé˜…单状态;审批通过时若档案已被他人借出,会抛出异常并回滚本次回写,避免脏状态
        try {
            borrowService.updateBorrowAuditStatus(borrowId, event.getStatus(), event.getReason());
            log.info("[onEvent] æ¡£æ¡ˆå€Ÿé˜…单({}) å®¡æ‰¹ç»“果回写完成", borrowId);
        } catch (Exception e) {
            log.error("[onEvent] å›žå†™æ¡£æ¡ˆå€Ÿé˜…单({}) å®¡æ‰¹ç»“果失败", borrowId, e);
        }
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/category/ArchiveCategoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.archive.service.category;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategoryListReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategorySaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import java.util.List;
/**
 * æ¡£æ¡ˆåˆ†ç±» Service æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface ArchiveCategoryService {
    /**
     * åˆ›å»ºæ¡£æ¡ˆåˆ†ç±»
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return åˆ†ç±»ç¼–号
     */
    Long createCategory(ArchiveCategorySaveReqVO createReqVO);
    /**
     * æ›´æ–°æ¡£æ¡ˆåˆ†ç±»
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateCategory(ArchiveCategorySaveReqVO updateReqVO);
    /**
     * åˆ é™¤æ¡£æ¡ˆåˆ†ç±»ï¼ˆå­˜åœ¨å­åˆ†ç±»æˆ–档案时不允许删除)
     *
     * @param id åˆ†ç±»ç¼–号
     */
    void deleteCategory(Long id);
    /**
     * èŽ·å¾—æ¡£æ¡ˆåˆ†ç±»
     *
     * @param id åˆ†ç±»ç¼–号
     * @return åˆ†ç±»
     */
    ArchiveCategoryDO getCategory(Long id);
    /**
     * èŽ·å¾—æ¡£æ¡ˆåˆ†ç±»åˆ—è¡¨ï¼ˆæŒ‰æŽ’åºå€¼å‡åºï¼‰
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return åˆ†ç±»åˆ—表
     */
    List<ArchiveCategoryDO> getCategoryList(ArchiveCategoryListReqVO reqVO);
    /**
     * èŽ·å¾—å…¨éƒ¨æ¡£æ¡ˆåˆ†ç±»ï¼ˆæž„å»ºåˆ†ç±»æ ‘ç”¨ï¼‰
     *
     * @return åˆ†ç±»åˆ—表
     */
    List<ArchiveCategoryDO> getCategoryList();
    /**
     * æŒ‰ç¼–码获得档案分类
     *
     * @param code åˆ†ç±»ç¼–码
     * @return åˆ†ç±»
     */
    ArchiveCategoryDO getCategoryByCode(String code);
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/category/ArchiveCategoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,194 @@
package cn.iocoder.yudao.module.archive.service.category;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategoryListReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.category.vo.ArchiveCategorySaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import cn.iocoder.yudao.module.archive.dal.mysql.category.ArchiveCategoryMapper;
import cn.iocoder.yudao.module.archive.dal.mysql.document.ArchiveDocumentMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.archive.enums.ErrorCodeConstants.*;
/**
 * æ¡£æ¡ˆåˆ†ç±» Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class ArchiveCategoryServiceImpl implements ArchiveCategoryService {
    /** é¡¶çº§åˆ†ç±»çš„父分类编号 */
    private static final Long ROOT_PARENT_ID = 0L;
    @Resource
    private ArchiveCategoryMapper categoryMapper;
    @Resource
    private ArchiveDocumentMapper documentMapper;
    @Override
    public Long createCategory(ArchiveCategorySaveReqVO createReqVO) {
        // 1.1 æ ¡éªŒçˆ¶åˆ†ç±»å­˜åœ¨
        validateParentCategory(createReqVO.getParentId(), createReqVO.getName());
        // 1.2 æ ¡éªŒåŒçº§åˆ†ç±»åç§°ä¸é‡å¤ã€åˆ†ç±»ç¼–码不重复
        validateNameUnique(createReqVO.getName(), createReqVO.getParentId(), null);
        validateCodeUnique(createReqVO.getCode(), null);
        // 2. æ’入(补默认值)
        ArchiveCategoryDO category = BeanUtils.toBean(createReqVO, ArchiveCategoryDO.class);
        category.setId(null);
        if (category.getSort() == null) {
            category.setSort(0);
        }
        if (category.getDefaultSecretLevel() == null) {
            category.setDefaultSecretLevel(1);
        }
        if (category.getStatus() == null) {
            category.setStatus(0);
        }
        categoryMapper.insert(category);
        return category.getId();
    }
    @Override
    public void updateCategory(ArchiveCategorySaveReqVO updateReqVO) {
        // 1.1 æ ¡éªŒåˆ†ç±»å­˜åœ¨
        ArchiveCategoryDO oldCategory = validateCategoryExists(updateReqVO.getId());
        // 1.2 æ ¡éªŒçˆ¶åˆ†ç±»å­˜åœ¨ï¼Œä¸”不能把分类挂到自身或其子孙下(避免形成环)
        validateParentCategory(updateReqVO.getParentId(), updateReqVO.getName());
        validateParentNotSelfOrDescendant(oldCategory, updateReqVO.getParentId());
        // 1.3 æ ¡éªŒåŒçº§åç§°ä¸é‡å¤ã€ç¼–码不重复
        validateNameUnique(updateReqVO.getName(), updateReqVO.getParentId(), updateReqVO.getId());
        validateCodeUnique(updateReqVO.getCode(), updateReqVO.getId());
        // 2. æ›´æ–°
        ArchiveCategoryDO updateObj = BeanUtils.toBean(updateReqVO, ArchiveCategoryDO.class);
        categoryMapper.updateById(updateObj);
    }
    @Override
    public void deleteCategory(Long id) {
        // 1.1 æ ¡éªŒåˆ†ç±»å­˜åœ¨
        ArchiveCategoryDO category = validateCategoryExists(id);
        // 1.2 å­˜åœ¨å­åˆ†ç±»æ—¶ä¸å…è®¸åˆ é™¤
        List<ArchiveCategoryDO> children = categoryMapper.selectListByParentId(id);
        if (CollUtil.isNotEmpty(children)) {
            throw exception(CATEGORY_HAS_CHILDREN, category.getName(), children.size());
        }
        // 1.3 åˆ†ç±»ä¸‹å­˜åœ¨æ¡£æ¡ˆæ—¶ä¸å…è®¸åˆ é™¤
        Long documentCount = documentMapper.selectCountByCategoryId(id);
        if (documentCount != null && documentCount > 0) {
            throw exception(CATEGORY_HAS_DOCUMENT, category.getName(), documentCount);
        }
        categoryMapper.deleteById(id);
    }
    @Override
    public ArchiveCategoryDO getCategory(Long id) {
        return categoryMapper.selectById(id);
    }
    @Override
    public List<ArchiveCategoryDO> getCategoryList(ArchiveCategoryListReqVO reqVO) {
        return categoryMapper.selectList(new LambdaQueryWrapperX<ArchiveCategoryDO>()
                .likeIfPresent(ArchiveCategoryDO::getName, reqVO.getName())
                .eqIfPresent(ArchiveCategoryDO::getStatus, reqVO.getStatus())
                .orderByAsc(ArchiveCategoryDO::getSort)
                .orderByAsc(ArchiveCategoryDO::getId));
    }
    @Override
    public List<ArchiveCategoryDO> getCategoryList() {
        return categoryMapper.selectListOrdered();
    }
    @Override
    public ArchiveCategoryDO getCategoryByCode(String code) {
        if (StrUtil.isBlank(code)) {
            return null;
        }
        return categoryMapper.selectByCode(code);
    }
    // ==================== æ ¡éªŒé€»è¾‘ ====================
    /** æ ¡éªŒåˆ†ç±»å­˜åœ¨ï¼Œä¸å­˜åœ¨æ—¶æŠ›å‡ºç²¾ç¡®æç¤º */
    private ArchiveCategoryDO validateCategoryExists(Long id) {
        ArchiveCategoryDO category = id != null ? categoryMapper.selectById(id) : null;
        if (category == null) {
            throw exception(CATEGORY_NOT_EXISTS);
        }
        return category;
    }
    /** æ ¡éªŒçˆ¶åˆ†ç±»ï¼š0 è¡¨ç¤ºé¡¶çº§ï¼›éž 0 æ—¶å¿…须存在 */
    private void validateParentCategory(Long parentId, String categoryName) {
        if (parentId == null) {
            throw exception(CATEGORY_PARENT_INVALID, categoryName);
        }
        if (Objects.equals(parentId, ROOT_PARENT_ID)) {
            return;
        }
        if (categoryMapper.selectById(parentId) == null) {
            throw exception(CATEGORY_PARENT_INVALID, categoryName);
        }
    }
    /** æ ¡éªŒçˆ¶åˆ†ç±»ä¸èƒ½æ˜¯è‡ªèº«ï¼Œä¹Ÿä¸èƒ½æ˜¯è‡ªå·±çš„子孙分类 */
    private void validateParentNotSelfOrDescendant(ArchiveCategoryDO category, Long parentId) {
        if (Objects.equals(category.getId(), parentId)) {
            throw exception(CATEGORY_PARENT_INVALID, category.getName());
        }
        Set<Long> descendantIds = collectDescendantIds(category.getId());
        if (descendantIds.contains(parentId)) {
            throw exception(CATEGORY_PARENT_INVALID, category.getName());
        }
    }
    /** æ”¶é›†åˆ†ç±»çš„全部子孙分类编号(含自身) */
    private Set<Long> collectDescendantIds(Long id) {
        List<ArchiveCategoryDO> all = categoryMapper.selectListOrdered();
        Set<Long> result = new HashSet<>();
        result.add(id);
        boolean changed = true;
        while (changed) {
            changed = false;
            for (ArchiveCategoryDO category : all) {
                if (result.contains(category.getParentId()) && result.add(category.getId())) {
                    changed = true;
                }
            }
        }
        return result;
    }
    private void validateNameUnique(String name, Long parentId, Long excludeId) {
        Long count = categoryMapper.selectCountByName(name, parentId, excludeId);
        if (count != null && count > 0) {
            throw exception(CATEGORY_NAME_DUPLICATE, name);
        }
    }
    private void validateCodeUnique(String code, Long excludeId) {
        Long count = categoryMapper.selectCountByCode(code, excludeId);
        if (count != null && count > 0) {
            ArchiveCategoryDO exists = categoryMapper.selectByCode(code);
            throw exception(CATEGORY_CODE_DUPLICATE, code, exists != null ? exists.getName() : code);
        }
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/document/ArchiveDocumentService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.archive.service.document;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentSaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO;
import java.util.List;
/**
 * æ¡£æ¡ˆ Service æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface ArchiveDocumentService {
    /** æ¡£æ¡ˆé™„件的业务记录类型({@code system_storage_attachment.record_type}) */
    String DOCUMENT_RECORD_TYPE = "archive_document";
    /**
     * åˆ›å»ºæ¡£æ¡ˆ
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return æ¡£æ¡ˆç¼–号
     */
    Long createDocument(ArchiveDocumentSaveReqVO createReqVO);
    /**
     * æ›´æ–°æ¡£æ¡ˆï¼ˆå€Ÿå‡ºä¸­çš„æ¡£æ¡ˆä¸å…è®¸ä¿®æ”¹ï¼‰
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updateDocument(ArchiveDocumentSaveReqVO updateReqVO);
    /**
     * åˆ é™¤æ¡£æ¡ˆï¼ˆå€Ÿå‡ºä¸­æˆ–存在未归还借阅单时不允许删除)
     *
     * @param id æ¡£æ¡ˆç¼–号
     */
    void deleteDocument(Long id);
    /**
     * èŽ·å¾—æ¡£æ¡ˆï¼ˆå«æ•æ„Ÿæ¡£æ¡ˆæƒé™æ ¡éªŒï¼‰
     *
     * @param id æ¡£æ¡ˆç¼–号
     * @return æ¡£æ¡ˆ
     */
    ArchiveDocumentDO getDocument(Long id);
    /**
     * èŽ·å¾—æ¡£æ¡ˆï¼ˆè·³è¿‡æ•æ„Ÿæ¡£æ¡ˆæƒé™æ ¡éªŒï¼Œä¾›æ¨¡å—å†…éƒ¨ä¸Ž RPC è°ƒç”¨ä½¿ç”¨ï¼‰
     *
     * @param id æ¡£æ¡ˆç¼–号
     * @return æ¡£æ¡ˆ
     */
    ArchiveDocumentDO getDocumentWithoutPermissionCheck(Long id);
    /**
     * èŽ·å¾—æ¡£æ¡ˆåˆ†é¡µ
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return æ¡£æ¡ˆåˆ†é¡µ
     */
    PageResult<ArchiveDocumentDO> getDocumentPage(ArchiveDocumentPageReqVO reqVO);
    /**
     * èŽ·å¾—æ¡£æ¡ˆåˆ—è¡¨ï¼ˆå¯¼å‡ºç”¨ï¼Œä¸åˆ†é¡µï¼‰
     *
     * @param reqVO æŸ¥è¯¢æ¡ä»¶
     * @return æ¡£æ¡ˆåˆ—表
     */
    List<ArchiveDocumentDO> getDocumentList(ArchiveDocumentPageReqVO reqVO);
    /**
     * æŒ‰æ¥æºå•据归档(幂等失败语义:重复归档抛「重复归档」异常,并回显已生成的档案编号)
     *
     * @param reqDTO å½’档信息
     * @return æ¡£æ¡ˆç¼–号
     */
    Long archiveBySource(ArchiveDocumentCreateReqDTO reqDTO);
    /**
     * åˆ¤æ–­æ¥æºå•据是否已归档
     *
     * @param sourceType æ¥æºç±»åž‹
     * @param sourceId   æ¥æºå•据编号
     * @return æ˜¯å¦å·²å½’æ¡£
     */
    Boolean isArchived(Integer sourceType, Long sourceId);
    /**
     * æŒ‰æ¥æºå•据获得档案
     *
     * @param sourceType æ¥æºç±»åž‹
     * @param sourceId   æ¥æºå•据编号
     * @return æ¡£æ¡ˆ
     */
    ArchiveDocumentDO getBySource(Integer sourceType, Long sourceId);
    /**
     * æ¡ä»¶æ›´æ–°æ¡£æ¡ˆçŠ¶æ€ä¸ºã€Œå€Ÿå‡ºä¸­ã€ï¼ˆä»…å½“å½“å‰ä¸ºã€Œåœ¨åº“ã€æ—¶ç”Ÿæ•ˆï¼‰
     *
     * @param documentId æ¡£æ¡ˆç¼–号
     * @return æ˜¯å¦æ›´æ–°æˆåŠŸï¼›false è¡¨ç¤ºæ¡£æ¡ˆå·²è¢«å…¶å®ƒæµç¨‹å€Ÿå‡ºæˆ–状态异常
     */
    boolean updateDocumentStatusToBorrowed(Long documentId);
    /**
     * æ¡ä»¶æ›´æ–°æ¡£æ¡ˆçŠ¶æ€ä¸ºã€Œåœ¨åº“ã€ï¼ˆä»…å½“å½“å‰ä¸ºã€Œå€Ÿå‡ºä¸­ã€æ—¶ç”Ÿæ•ˆï¼‰
     *
     * @param documentId æ¡£æ¡ˆç¼–号
     * @return æ˜¯å¦æ›´æ–°æˆåŠŸ
     */
    boolean updateDocumentStatusToInStock(Long documentId);
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/document/ArchiveDocumentServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,269 @@
package cn.iocoder.yudao.module.archive.service.document;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentPageReqVO;
import cn.iocoder.yudao.module.archive.controller.admin.document.vo.ArchiveDocumentSaveReqVO;
import cn.iocoder.yudao.module.archive.dal.dataobject.category.ArchiveCategoryDO;
import cn.iocoder.yudao.module.archive.dal.dataobject.document.ArchiveDocumentDO;
import cn.iocoder.yudao.module.archive.dal.mysql.borrow.ArchiveBorrowMapper;
import cn.iocoder.yudao.module.archive.dal.mysql.document.ArchiveDocumentMapper;
import cn.iocoder.yudao.module.archive.dal.redis.ArchiveNoRedisDAO;
import cn.iocoder.yudao.module.archive.enums.ArchiveBorrowStatusEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveCategoryStatusEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveDocumentStatusEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveSecretLevelEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveSourceTypeEnum;
import cn.iocoder.yudao.module.archive.service.category.ArchiveCategoryService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.archive.enums.ErrorCodeConstants.*;
/**
 * æ¡£æ¡ˆ Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class ArchiveDocumentServiceImpl implements ArchiveDocumentService {
    /** æ•æ„Ÿæ¡£æ¡ˆï¼ˆä¿å¯†çº§ï¼‰æŸ¥çœ‹æƒé™ç‚¹ */
    public static final String SENSITIVE_PERMISSION = "archive:document:sensitive";
    @Resource
    private ArchiveDocumentMapper documentMapper;
    @Resource
    private ArchiveBorrowMapper borrowMapper;
    @Resource
    private ArchiveCategoryService categoryService;
    @Resource
    private ArchiveNoRedisDAO archiveNoRedisDAO;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource(name = "ss")
    private SecurityFrameworkService securityFrameworkService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createDocument(ArchiveDocumentSaveReqVO createReqVO) {
        // 1. æ ¡éªŒåˆ†ç±»
        ArchiveCategoryDO category = validateCategory(createReqVO.getCategoryId());
        // 2. ç»„装档案(密级/载体/归档日期/归档人补默认值)
        ArchiveDocumentDO document = BeanUtils.toBean(createReqVO, ArchiveDocumentDO.class);
        document.setId(null);
        document.setNo(archiveNoRedisDAO.generate(ArchiveNoRedisDAO.DOCUMENT_NO_PREFIX));
        document.setSecretLevel(createReqVO.getSecretLevel() != null ? createReqVO.getSecretLevel()
                : category.getDefaultSecretLevel());
        document.setCarrierType(createReqVO.getCarrierType() != null ? createReqVO.getCarrierType() : 3);
        document.setSourceType(ArchiveSourceTypeEnum.MANUAL.getType());
        document.setArchiveDate(createReqVO.getArchiveDate() != null ? createReqVO.getArchiveDate() : LocalDate.now());
        document.setArchivistUserId(getLoginUserId());
        document.setStatus(ArchiveDocumentStatusEnum.IN_STOCK.getStatus());
        documentMapper.insert(document);
        // 3. ç»‘定附件
        storageAttachmentApi.bindAttachments("file", DOCUMENT_RECORD_TYPE, document.getId(), createReqVO.getBlobIds());
        return document.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateDocument(ArchiveDocumentSaveReqVO updateReqVO) {
        // 1.1 æ ¡éªŒæ¡£æ¡ˆå­˜åœ¨
        ArchiveDocumentDO oldDocument = validateDocumentExists(updateReqVO.getId());
        // 1.2 å€Ÿå‡ºä¸­çš„æ¡£æ¡ˆä¸å…è®¸ä¿®æ”¹
        if (ArchiveDocumentStatusEnum.BORROWED.getStatus().equals(oldDocument.getStatus())) {
            throw exception(DOCUMENT_UPDATE_FAIL_BORROWED, oldDocument.getNo());
        }
        // 1.3 æ•æ„Ÿæ¡£æ¡ˆéœ€è¦æœ‰æ•æ„Ÿæ¡£æ¡ˆæƒé™æ‰èƒ½ä¿®æ”¹
        validateSecretLevelVisible(oldDocument);
        // 1.4 æ ¡éªŒåˆ†ç±»
        validateCategory(updateReqVO.getCategoryId());
        // 2. æ›´æ–°ï¼ˆå¯†çº§ä¸ºç©ºæ—¶ä¿ç•™åŽŸå€¼ï¼Œé¿å…è¢«ç½®ç©ºï¼‰
        ArchiveDocumentDO updateObj = BeanUtils.toBean(updateReqVO, ArchiveDocumentDO.class);
        if (updateObj.getSecretLevel() == null) {
            updateObj.setSecretLevel(oldDocument.getSecretLevel());
        }
        documentMapper.updateById(updateObj);
        // 3. å¢žé‡æ›´æ–°é™„ä»¶
        storageAttachmentApi.updateAttachments("file", DOCUMENT_RECORD_TYPE, updateReqVO.getId(),
                updateReqVO.getBlobIds());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDocument(Long id) {
        // 1.1 æ ¡éªŒæ¡£æ¡ˆå­˜åœ¨
        ArchiveDocumentDO document = validateDocumentExists(id);
        // 1.2 å€Ÿå‡ºä¸­æˆ–存在未归还借阅单时不允许删除
        boolean hasOpenBorrow = CollUtil.isNotEmpty(borrowMapper.selectListByDocumentIdAndStatusIn(id,
                List.of(ArchiveBorrowStatusEnum.APPROVING.getStatus(), ArchiveBorrowStatusEnum.BORROWED.getStatus())));
        if (ArchiveDocumentStatusEnum.BORROWED.getStatus().equals(document.getStatus()) || hasOpenBorrow) {
            throw exception(DOCUMENT_DELETE_FAIL_STATUS, document.getNo(),
                    ArchiveDocumentStatusEnum.nameOf(document.getStatus()));
        }
        // 2. åˆ é™¤æ¡£æ¡ˆä¸Žé™„ä»¶
        documentMapper.deleteById(id);
        storageAttachmentApi.deleteAttachmentsByRecord(DOCUMENT_RECORD_TYPE, id);
    }
    @Override
    public ArchiveDocumentDO getDocument(Long id) {
        ArchiveDocumentDO document = validateDocumentExists(id);
        validateSecretLevelVisible(document);
        return document;
    }
    @Override
    public ArchiveDocumentDO getDocumentWithoutPermissionCheck(Long id) {
        return documentMapper.selectById(id);
    }
    @Override
    public PageResult<ArchiveDocumentDO> getDocumentPage(ArchiveDocumentPageReqVO reqVO) {
        return documentMapper.selectPage(reqVO, !hasSensitivePermission());
    }
    @Override
    public List<ArchiveDocumentDO> getDocumentList(ArchiveDocumentPageReqVO reqVO) {
        return documentMapper.selectList(reqVO, !hasSensitivePermission());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long archiveBySource(ArchiveDocumentCreateReqDTO reqDTO) {
        // 1. æ ¡éªŒæ¥æºç±»åž‹ï¼ˆåªå…è®¸è¯¾é¢˜/会员/财务)
        ArchiveSourceTypeEnum sourceType = ArchiveSourceTypeEnum.valueOf(reqDTO.getSourceType());
        if (sourceType == null || sourceType == ArchiveSourceTypeEnum.MANUAL) {
            throw exception(DOCUMENT_SOURCE_TYPE_UNSUPPORTED, reqDTO.getSourceType());
        }
        // 2. é‡å¤å½’档校验:同一来源单据只允许生成一份档案
        ArchiveDocumentDO exists = documentMapper.selectBySource(reqDTO.getSourceType(), reqDTO.getSourceId());
        if (exists != null) {
            throw exception(DOCUMENT_DUPLICATE_SOURCE,
                    StrUtil.blankToDefault(reqDTO.getSourceNo(), String.valueOf(reqDTO.getSourceId())),
                    exists.getNo());
        }
        // 3. æŒ‰åˆ†ç±»ç¼–码定位档案分类
        ArchiveCategoryDO category = categoryService.getCategoryByCode(reqDTO.getCategoryCode());
        if (category == null) {
            throw exception(DOCUMENT_SOURCE_CATEGORY_NOT_EXISTS, reqDTO.getCategoryCode());
        }
        // 4. ç»„装并落库档案(标题、密级为空时分别取来源标题、分类默认密级)
        ArchiveDocumentDO document = new ArchiveDocumentDO();
        document.setNo(archiveNoRedisDAO.generate(ArchiveNoRedisDAO.DOCUMENT_NO_PREFIX));
        document.setTitle(StrUtil.blankToDefault(reqDTO.getTitle(), reqDTO.getSourceTitle()));
        document.setCategoryId(category.getId());
        document.setSecretLevel(reqDTO.getSecretLevel() != null ? reqDTO.getSecretLevel()
                : category.getDefaultSecretLevel());
        // è”动归档默认同时保留电子件与来源关联,载体取「电子+实体」,由档案管理员后续补充实体位置
        document.setCarrierType(3);
        document.setSourceType(reqDTO.getSourceType());
        document.setSourceNo(reqDTO.getSourceNo());
        document.setSourceId(reqDTO.getSourceId());
        document.setSourceTitle(reqDTO.getSourceTitle());
        document.setArchiveDate(reqDTO.getArchiveDate() != null ? reqDTO.getArchiveDate() : LocalDate.now());
        document.setArchivistUserId(getLoginUserId());
        document.setStatus(ArchiveDocumentStatusEnum.IN_STOCK.getStatus());
        document.setSummary(reqDTO.getSummary());
        document.setTags(reqDTO.getTags());
        document.setRemark(reqDTO.getRemark());
        documentMapper.insert(document);
        // 5. ç»‘定来源单据的附件
        storageAttachmentApi.bindAttachments("file", DOCUMENT_RECORD_TYPE, document.getId(), reqDTO.getBlobIds());
        return document.getId();
    }
    @Override
    public Boolean isArchived(Integer sourceType, Long sourceId) {
        return documentMapper.selectBySource(sourceType, sourceId) != null;
    }
    @Override
    public ArchiveDocumentDO getBySource(Integer sourceType, Long sourceId) {
        if (sourceType == null || sourceId == null) {
            return null;
        }
        return documentMapper.selectBySource(sourceType, sourceId);
    }
    @Override
    public boolean updateDocumentStatusToBorrowed(Long documentId) {
        int rows = documentMapper.update(null, new LambdaUpdateWrapper<ArchiveDocumentDO>()
                .eq(ArchiveDocumentDO::getId, documentId)
                .eq(ArchiveDocumentDO::getStatus, ArchiveDocumentStatusEnum.IN_STOCK.getStatus())
                .set(ArchiveDocumentDO::getStatus, ArchiveDocumentStatusEnum.BORROWED.getStatus()));
        return rows > 0;
    }
    @Override
    public boolean updateDocumentStatusToInStock(Long documentId) {
        int rows = documentMapper.update(null, new LambdaUpdateWrapper<ArchiveDocumentDO>()
                .eq(ArchiveDocumentDO::getId, documentId)
                .eq(ArchiveDocumentDO::getStatus, ArchiveDocumentStatusEnum.BORROWED.getStatus())
                .set(ArchiveDocumentDO::getStatus, ArchiveDocumentStatusEnum.IN_STOCK.getStatus()));
        return rows > 0;
    }
    // ==================== æ ¡éªŒé€»è¾‘ ====================
    private ArchiveDocumentDO validateDocumentExists(Long id) {
        ArchiveDocumentDO document = id != null ? documentMapper.selectById(id) : null;
        if (document == null) {
            throw exception(DOCUMENT_NOT_EXISTS);
        }
        return document;
    }
    /** æ ¡éªŒåˆ†ç±»å­˜åœ¨ä¸”启用 */
    private ArchiveCategoryDO validateCategory(Long categoryId) {
        ArchiveCategoryDO category = categoryService.getCategory(categoryId);
        if (category == null) {
            throw exception(DOCUMENT_CATEGORY_NOT_EXISTS);
        }
        if (ArchiveCategoryStatusEnum.DISABLE.getStatus().equals(category.getStatus())) {
            throw exception(CATEGORY_DISABLED, category.getName());
        }
        return category;
    }
    /** æ•æ„Ÿæ¡£æ¡ˆå¯è§æ€§æ ¡éªŒï¼šä¿å¯†çº§æ¡£æ¡ˆéœ€è¦ {@code archive:document:sensitive} æƒé™ */
    private void validateSecretLevelVisible(ArchiveDocumentDO document) {
        if (ArchiveSecretLevelEnum.SECRET.getLevel().equals(document.getSecretLevel()) && !hasSensitivePermission()) {
            throw exception(DOCUMENT_SENSITIVE_NO_PERMISSION, document.getTitle());
        }
    }
    private boolean hasSensitivePermission() {
        return securityFrameworkService.hasPermission(SENSITIVE_PERMISSION);
    }
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/stat/ArchiveStatService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.archive.service.stat;
import cn.iocoder.yudao.module.archive.controller.admin.stat.vo.ArchiveStatRespVO;
/**
 * æ¡£æ¡ˆç»Ÿè®¡ Service æŽ¥å£
 *
 * @author è¶…级管理员
 */
public interface ArchiveStatService {
    /**
     * èŽ·å¾—æ¡£æ¡ˆç»Ÿè®¡æ€»è§ˆï¼ˆå­˜é‡ + æµè½¬ + åˆ†ç±»/密级/载体分布)
     *
     * @return ç»Ÿè®¡ç»“æžœ
     */
    ArchiveStatRespVO getOverview();
}
yudao-module-archive/src/main/java/cn/iocoder/yudao/module/archive/service/stat/ArchiveStatServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.archive.service.stat;
import cn.iocoder.yudao.module.archive.controller.admin.stat.vo.ArchiveStatRespVO;
import cn.iocoder.yudao.module.archive.controller.admin.stat.vo.ArchiveStatValueVO;
import cn.iocoder.yudao.module.archive.dal.mysql.borrow.ArchiveBorrowMapper;
import cn.iocoder.yudao.module.archive.dal.mysql.document.ArchiveDocumentMapper;
import cn.iocoder.yudao.module.archive.enums.ArchiveCarrierTypeEnum;
import cn.iocoder.yudao.module.archive.enums.ArchiveSecretLevelEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
 * æ¡£æ¡ˆç»Ÿè®¡ Service å®žçŽ°ç±»
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class ArchiveStatServiceImpl implements ArchiveStatService {
    @Resource
    private ArchiveDocumentMapper documentMapper;
    @Resource
    private ArchiveBorrowMapper borrowMapper;
    @Override
    public ArchiveStatRespVO getOverview() {
        ArchiveStatRespVO respVO = new ArchiveStatRespVO();
        // 1. æ¡£æ¡ˆå­˜é‡
        respVO.setTotalCount(documentMapper.selectTotalCount());
        respVO.setInStockCount(documentMapper.selectInStockCount());
        respVO.setBorrowedCount(documentMapper.selectBorrowedCount());
        // 2. å€Ÿé˜…流转(借出中 / é€¾æœŸæœªè¿˜ï¼Œé€¾æœŸå®žæ—¶è®¡ç®—)
        respVO.setBorrowingCount(borrowMapper.selectBorrowingCount());
        respVO.setOverdueCount(borrowMapper.selectOverdueCount());
        // 3. åˆ†å¸ƒç»Ÿè®¡ï¼ˆå¯†çº§ / è½½ä½“补枚举名称,便于前端直接渲染)
        respVO.setByCategory(documentMapper.selectCountGroupByCategory());
        respVO.setBySecretLevel(fillSecretLevelName(documentMapper.selectCountGroupBySecretLevel()));
        respVO.setByCarrierType(fillCarrierTypeName(documentMapper.selectCountGroupByCarrierType()));
        return respVO;
    }
    private List<ArchiveStatValueVO> fillSecretLevelName(List<ArchiveStatValueVO> list) {
        list.forEach(item -> item.setName(ArchiveSecretLevelEnum.nameOf(item.getValue())));
        return list;
    }
    private List<ArchiveStatValueVO> fillCarrierTypeName(List<ArchiveStatValueVO> list) {
        list.forEach(item -> item.setName(ArchiveCarrierTypeEnum.nameOf(item.getValue())));
        return list;
    }
}
yudao-module-finance/pom.xml
@@ -36,6 +36,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- æ¡£æ¡ˆæ¨¡å— API(报销凭证按来源单据归档) -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-archive-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/FinanceReimbursementController.java
@@ -121,12 +121,11 @@
    }
    @PostMapping("/archive")
    @Operation(summary = "归档报销单")
    @Operation(summary = "归档报销单(同时生成财务档案)")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('finance:reimbursement:archive')")
    public CommonResult<Boolean> archiveReimbursement(@RequestParam("id") Long id) {
        reimbursementService.archiveReimbursement(id);
        return success(true);
    public CommonResult<String> archiveReimbursement(@RequestParam("id") Long id) {
        return success(reimbursementService.archiveReimbursement(id));
    }
    @GetMapping("/approve-process-list")
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/controller/admin/reimbursement/vo/FinanceReimbursementRespVO.java
@@ -58,6 +58,12 @@
    @ExcelProperty("归档时间")
    private LocalDateTime archiveTime;
    @Schema(description = "归档生成的档案 ID(非空表示已归档)", example = "1024")
    private Long archiveDocumentId;
    @Schema(description = "是否已归档(archiveDocumentId éžç©ºå³ä¸º true)", example = "false")
    private Boolean archived;
    @Schema(description = "备注", example = "出差北京")
    @ExcelProperty("备注")
    private String remark;
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/dal/dataobject/reimbursement/FinanceReimbursementDO.java
@@ -52,6 +52,9 @@
    /** å½’档时间 */
    private LocalDateTime archiveTime;
    /** å½’档生成的档案 ID(archive_document.id,归档联动回写) */
    private Long archiveDocumentId;
    /** å¤‡æ³¨ */
    private String remark;
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java
@@ -16,11 +16,12 @@
    ErrorCode REIMBURSEMENT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_107_003, "报销单({})不是草稿或已驳回状态,无法修改,只有草稿和已驳回的报销单可以编辑");
    ErrorCode REIMBURSEMENT_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_107_004, "提交审批失败,只有草稿或已驳回状态的报销单才能提交");
    ErrorCode REIMBURSEMENT_SUBMIT_FAIL_ITEM_EMPTY = new ErrorCode(1_030_107_005, "提交审批失败,报销单没有明细行,无法提交");
    ErrorCode REIMBURSEMENT_ARCHIVE_FAIL_STATUS = new ErrorCode(1_030_107_006, "归档失败,只有已通过状态的报销单才能归档");
    ErrorCode REIMBURSEMENT_ARCHIVE_FAIL_STATUS = new ErrorCode(1_030_107_006, "归档失败:报销单({})当前状态为〔{}〕,只有「已通过」状态的报销单才能归档到档案管理");
    ErrorCode REIMBURSEMENT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_107_007, "更新审批状态失败,只有审批中的报销单才能更新");
    ErrorCode REIMBURSEMENT_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_107_008, "报销审批分类不存在,请先在BPM模块创建报销审批流程分类");
    ErrorCode REIMBURSEMENT_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_107_009, "报销审批分类(finance_reimbursement_approve)下没有已发布的流程,无法提交报销审批。请先在「BPM â†’ æµç¨‹å®šä¹‰ã€ä¸­åˆ›å»ºå¹¶å‘布报销审批流程后重试");
    ErrorCode REIMBURSEMENT_CUSTOM_NO_EXISTS = new ErrorCode(1_030_107_010, "报销单号({})已被其他报销单使用,请更换其他单号,或清空该字段由系统自动生成");
    ErrorCode REIMBURSEMENT_CUSTOM_NO_INVALID = new ErrorCode(1_030_107_011, "报销单号({})中间不能包含空格,请去掉空格后重新输入,或清空该字段由系统自动生成");
    ErrorCode REIMBURSEMENT_ALREADY_ARCHIVED = new ErrorCode(1_030_107_012, "重复归档:报销单({})已于此前归档并生成档案(编号={}),请勿重复归档,如需查看请前往档案管理模块");
}
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/FinanceReimbursementStatusEnum.java
@@ -21,4 +21,17 @@
    private final Integer status;
    private final String name;
    /** æŒ‰çŠ¶æ€å€¼å–ä¸­æ–‡åï¼ŒæœªåŒ¹é…æ—¶å›žé€€ä¸ºã€ŒæœªçŸ¥ã€ */
    public static String nameOf(Integer status) {
        if (status == null) {
            return "未知";
        }
        for (FinanceReimbursementStatusEnum item : values()) {
            if (item.getStatus().equals(status)) {
                return item.getName();
            }
        }
        return "未知";
    }
}
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementService.java
@@ -30,7 +30,13 @@
    void submitReimbursement(Long id, String processDefinitionKey, Long userId);
    void archiveReimbursement(Long id);
    /**
     * å½’档报销单(状态置为已归档 + ç”Ÿæˆæ¡£æ¡ˆæ¡ç›®å¹¶å›žå†™æ¡£æ¡ˆ ID)
     *
     * @param id æŠ¥é”€å•编号
     * @return ç”Ÿæˆçš„æ¡£æ¡ˆç¼–号(用于前端提示)
     */
    String archiveReimbursement(Long id);
    List<Map<String, Object>> getReimbursementApproveProcessDefinitionList();
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/service/reimbursement/FinanceReimbursementServiceImpl.java
@@ -4,6 +4,9 @@
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
@@ -20,6 +23,7 @@
import cn.iocoder.yudao.module.finance.dal.redis.FinanceNoRedisDAO;
import cn.iocoder.yudao.module.finance.enums.FinanceReimbursementStatusEnum;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
@@ -57,6 +61,15 @@
     */
    public static final String REIMBURSEMENT_RECORD_TYPE = "finance_reimbursement";
    /** å½’档来源类型:4-财务(对应 archive æ¨¡å— source_type) */
    private static final Integer ARCHIVE_SOURCE_TYPE_FINANCE = 4;
    /** å½’档密级:3-保密(财务档案默认保密) */
    private static final Integer ARCHIVE_SECRET_LEVEL_SECRET = 3;
    /** è´¢åŠ¡æ¡£æ¡ˆåˆ†ç±»ç¼–ç ï¼ˆå¯¹åº” archive_category.code) */
    private static final String ARCHIVE_CATEGORY_CODE_FINANCE = "finance";
    @Resource
    private FinanceReimbursementMapper reimbursementMapper;
    @Resource
@@ -67,6 +80,9 @@
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private ArchiveDocumentApi archiveDocumentApi;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
@@ -204,18 +220,44 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void archiveReimbursement(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + å·²é€šè¿‡çŠ¶æ€
    public String archiveReimbursement(Long id) {
        // 1. æ ¡éªŒå­˜åœ¨ + çŠ¶æ€
        FinanceReimbursementDO reimbursement = validateReimbursementExists(id);
        if (!FinanceReimbursementStatusEnum.APPROVED.getStatus().equals(reimbursement.getStatus())) {
            throw exception(REIMBURSEMENT_ARCHIVE_FAIL_STATUS);
        boolean archived = FinanceReimbursementStatusEnum.ARCHIVED.getStatus().equals(reimbursement.getStatus());
        if (archived && reimbursement.getArchiveDocumentId() != null) {
            throw exception(REIMBURSEMENT_ALREADY_ARCHIVED, reimbursement.getNo(),
                    StrUtil.blankToDefault(archiveNo(id), "未知"));
        }
        if (!archived && !FinanceReimbursementStatusEnum.APPROVED.getStatus().equals(reimbursement.getStatus())) {
            throw exception(REIMBURSEMENT_ARCHIVE_FAIL_STATUS, reimbursement.getNo(),
                    FinanceReimbursementStatusEnum.nameOf(reimbursement.getStatus()));
        }
        // 2. æ›´æ–°ä¸ºå·²å½’æ¡£
        // 2. æ›´æ–°ä¸ºå·²å½’档(历史已归档但尚未生成档案条目的,跳过状态更新,仅补生成档案)
        if (!archived) {
            reimbursementMapper.updateById(new FinanceReimbursementDO()
                    .setId(id)
                    .setStatus(FinanceReimbursementStatusEnum.ARCHIVED.getStatus())
                    .setArchiveTime(LocalDateTime.now()));
        }
        // 3. æŒ‰æ¥æºå•据生成档案条目(财务档案默认「保密」级),并回写档案 ID
        List<Long> blobIds = storageAttachmentApi.listAttachments(REIMBURSEMENT_RECORD_TYPE, id).stream()
                .map(StorageBlobRespDTO::getId).collect(Collectors.toList());
        Long archiveDocumentId = archiveDocumentApi.archiveBySource(new ArchiveDocumentCreateReqDTO()
                .setSourceType(ARCHIVE_SOURCE_TYPE_FINANCE)
                .setSourceId(id)
                .setSourceNo(reimbursement.getNo())
                .setSourceTitle(reimbursement.getTitle())
                .setCategoryCode(ARCHIVE_CATEGORY_CODE_FINANCE)
                .setSecretLevel(ARCHIVE_SECRET_LEVEL_SECRET)
                .setSummary("财务报销凭证归档")
                .setBlobIds(blobIds)).getCheckedData();
        reimbursementMapper.updateById(new FinanceReimbursementDO()
                .setId(id)
                .setStatus(FinanceReimbursementStatusEnum.ARCHIVED.getStatus())
                .setArchiveTime(LocalDateTime.now()));
                .setArchiveDocumentId(archiveDocumentId));
        return StrUtil.blankToDefault(archiveNo(id), "未知");
    }
    @Override
@@ -283,6 +325,13 @@
        return reimbursement;
    }
    /** æŸ¥è¯¢æ¥æºå•据已生成的档案编号(未归档时返回 null) */
    private String archiveNo(Long reimbursementId) {
        ArchiveDocumentRespDTO archive = archiveDocumentApi
                .getBySource(ARCHIVE_SOURCE_TYPE_FINANCE, reimbursementId).getCheckedData();
        return archive != null ? archive.getNo() : null;
    }
    /**
     * è§£æžæ–°å¢žæ—¶çš„æŠ¥é”€å•号:去掉首尾空格后若为空,则由系统按 BX + æ—¥æœŸ + 6 ä½æµæ°´ç”Ÿæˆï¼Œ
     * å¦åˆ™ä½¿ç”¨ç”¨æˆ·è‡ªå®šä¹‰å•号。
yudao-module-research/pom.xml
@@ -44,6 +44,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- æ¡£æ¡ˆæ¨¡å— API(课题申报书按来源单据归档) -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-archive-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/ResearchApplicationController.java
@@ -120,6 +120,14 @@
        return success(true);
    }
    @PostMapping("/archive")
    @Operation(summary = "课题申报书归档到档案管理")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('research:application:archive')")
    public CommonResult<String> archiveApplication(@RequestParam("id") Long id) {
        return success(applicationService.archiveApplication(id));
    }
    // ==================== å†…部方法 ====================
    private ResearchApplicationRespVO buildApplicationDetail(ResearchApplicationDO application) {
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/application/vo/ResearchApplicationRespVO.java
@@ -81,6 +81,12 @@
    @Schema(description = "立项通知公告 ID")
    private Long noticeId;
    @Schema(description = "归档生成的档案 ID(非空表示已归档)", example = "1024")
    private Long archiveDocumentId;
    @Schema(description = "是否已归档(archiveDocumentId éžç©ºå³ä¸º true)", example = "false")
    private Boolean archived;
    @Schema(description = "备注")
    private String remark;
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/application/ResearchApplicationDO.java
@@ -90,6 +90,9 @@
    /** å·²å‘布的立项通知公告 ID */
    private Long noticeId;
    /** å½’档生成的档案 ID(archive_document.id,归档联动回写) */
    private Long archiveDocumentId;
    /** å¤‡æ³¨ */
    private String remark;
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/enums/ErrorCodeConstants.java
@@ -34,6 +34,8 @@
    ErrorCode APPLICATION_PUBLISH_NOTICE_FAIL_STATUS = new ErrorCode(1_030_109_109, "发布立项通知失败:课题申报书({})当前状态为〔{}〕,只有已立项的课题才能发布立项通知");
    ErrorCode APPLICATION_NOTICE_EXISTS = new ErrorCode(1_030_109_110, "发布失败:该课题({})的立项通知已于 {} å‘布,请勿重复发布");
    ErrorCode APPLICATION_MEMBER_NOT_EXISTS = new ErrorCode(1_030_109_111, "保存失败:所选申报会员(ID={})已不存在,请重新从会员列表选择申报人");
    ErrorCode APPLICATION_ARCHIVE_FAIL_STATUS = new ErrorCode(1_030_109_112, "归档失败:课题申报书({})当前状态为〔{}〕,只有「已立项」的课题才能归档到档案管理");
    ErrorCode APPLICATION_ALREADY_ARCHIVED = new ErrorCode(1_030_109_113, "重复归档:课题申报书({})已于此前归档并生成档案(编号={}),请勿重复归档,如需查看请前往档案管理模块");
    // ========== ä¸“家评审明细 1_030_109_200 ==========
    ErrorCode REVIEW_NOT_EXISTS = new ErrorCode(1_030_109_200, "专家评审记录不存在");
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationService.java
@@ -34,6 +34,14 @@
    /** å‘布立项通知(生成系统公告,防重复) */
    void publishNotice(Long id);
    /**
     * å°†ã€Œå·²ç«‹é¡¹ã€çš„课题申报书归档到档案管理,并回写归档档案 ID
     *
     * @param id ç”³æŠ¥ä¹¦ç¼–号
     * @return ç”Ÿæˆçš„æ¡£æ¡ˆç¼–号(用于前端提示)
     */
    String archiveApplication(Long id);
    /** ä¸“家评审汇总结果回写主单(由评审服务调用) */
    void updateReviewSummary(Long id, BigDecimal reviewAvgScore, Integer reviewPass);
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java
@@ -7,6 +7,9 @@
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
@@ -24,6 +27,7 @@
import cn.iocoder.yudao.module.research.enums.ResearchGuidelineStatusEnum;
import cn.iocoder.yudao.module.research.service.guideline.ResearchGuidelineService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.notice.NoticeDO;
import cn.iocoder.yudao.module.system.service.notice.NoticeService;
@@ -40,6 +44,7 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -54,6 +59,15 @@
    private static final String APPLICATION_APPROVE_CATEGORY_CODE = "research_topic_approve";
    public static final String APPLICATION_RECORD_TYPE = "research_application";
    /** å½’档来源类型:2-课题(对应 archive æ¨¡å— source_type) */
    private static final Integer ARCHIVE_SOURCE_TYPE_RESEARCH = 2;
    /** å½’档密级:2-内部 */
    private static final Integer ARCHIVE_SECRET_LEVEL_INTERNAL = 2;
    /** è¯¾é¢˜æ¡£æ¡ˆåˆ†ç±»ç¼–码(对应 archive_category.code) */
    private static final String ARCHIVE_CATEGORY_CODE_RESEARCH = "research";
    /** ç«‹é¡¹é€šçŸ¥ä½¿ç”¨ã€Œå…¬å‘Šã€ç±»åž‹ï¼ˆsystem_notice_type: 1-通知 2-公告) */
    private static final Integer NOTICE_TYPE_ANNOUNCEMENT = 2;
@@ -62,6 +76,9 @@
    @Resource
    private ResearchApplicationMapper applicationMapper;
    @Resource
    private ArchiveDocumentApi archiveDocumentApi;
    @Resource
    private ResearchNoRedisDAO noRedisDAO;
@@ -275,6 +292,42 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String archiveApplication(Long id) {
        // 1.1 æ ¡éªŒå­˜åœ¨
        ResearchApplicationDO application = validateApplicationExists(id);
        // 1.2 åªæœ‰ã€Œå·²ç«‹é¡¹ã€çš„课题才能归档
        if (!ResearchApplicationStatusEnum.APPROVED.getStatus().equals(application.getStatus())) {
            throw exception(APPLICATION_ARCHIVE_FAIL_STATUS,
                    application.getNo(), ResearchApplicationStatusEnum.nameOf(application.getStatus()));
        }
        // 1.3 é‡å¤å½’档校验
        if (application.getArchiveDocumentId() != null) {
            throw exception(APPLICATION_ALREADY_ARCHIVED, application.getNo(),
                    StrUtil.blankToDefault(archiveNo(id), "未知"));
        }
        // 2. å¸¦å…¥ç”³æŠ¥ä¹¦é™„件,按来源单据归档(来源单号取立项编号,未生成时回退申报编号)
        String sourceNo = StrUtil.blankToDefault(application.getResearchNo(), application.getNo());
        List<Long> blobIds = storageAttachmentApi.listAttachments(APPLICATION_RECORD_TYPE, id).stream()
                .map(StorageBlobRespDTO::getId).collect(Collectors.toList());
        Long archiveDocumentId = archiveDocumentApi.archiveBySource(new ArchiveDocumentCreateReqDTO()
                .setSourceType(ARCHIVE_SOURCE_TYPE_RESEARCH)
                .setSourceId(id)
                .setSourceNo(sourceNo)
                .setSourceTitle(application.getTitle())
                .setCategoryCode(ARCHIVE_CATEGORY_CODE_RESEARCH)
                .setSecretLevel(ARCHIVE_SECRET_LEVEL_INTERNAL)
                .setSummary("课题申报书归档")
                .setBlobIds(blobIds)).getCheckedData();
        // 3. å›žå†™å½’档档案 ID(作为已归档硬标记)
        applicationMapper.updateById(new ResearchApplicationDO()
                .setId(id).setArchiveDocumentId(archiveDocumentId));
        return StrUtil.blankToDefault(archiveNo(id), "未知");
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateReviewSummary(Long id, BigDecimal reviewAvgScore, Integer reviewPass) {
        // ç”¨ UpdateWrapper æ˜¾å¼ set,保证「评审明细被删空」时能把汇总值清成 null
        applicationMapper.update(null, new LambdaUpdateWrapper<ResearchApplicationDO>()
@@ -293,6 +346,13 @@
        return application;
    }
    /** æŸ¥è¯¢æ¥æºå•据已生成的档案编号(未归档时返回 null) */
    private String archiveNo(Long applicationId) {
        ArchiveDocumentRespDTO archive = archiveDocumentApi
                .getBySource(ARCHIVE_SOURCE_TYPE_RESEARCH, applicationId).getCheckedData();
        return archive != null ? archive.getNo() : null;
    }
    /** ä¿å­˜ï¼ˆè‰ç¨¿ï¼‰é˜¶æ®µæ ¡éªŒæŒ‡å—:必须存在且已发布 */
    private ResearchGuidelineDO validateGuidelineForSave(Long guidelineId) {
        ResearchGuidelineDO guideline = guidelineService.getGuideline(guidelineId);
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -81,6 +81,10 @@
    RESEARCH_APPLICATION("research_application", "课题申报书"),
    RESEARCH_FUND_RECORD("research_fund_record", "经费拨付凭证"),
    // ===== æ¡£æ¡ˆç®¡ç† =====
    ARCHIVE_DOCUMENT("archive_document", "档案"),
    RESEARCH_FUND_RECORD("research_fund_record", "经费拨付凭证"),
    // ===== ä¼ä¸šå¾®ä¿¡æ´»åЍ =====
    WXCP_ACTIVITY_SIGNUP("wxcp_activity_signup", "企业微信活动报名");
yudao-module-ums/pom.xml
@@ -30,6 +30,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- æ¡£æ¡ˆæ¨¡å— API(会员材料按来源单据归档) -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-archive-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/UmsMemberController.java
@@ -102,6 +102,14 @@
        return success(new PageResult<>(buildMemberDetailList(pageResult.getList()), pageResult.getTotal()));
    }
    @PostMapping("/archive")
    @Operation(summary = "会员材料归档到档案管理")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('ums:member:archive')")
    public CommonResult<String> archiveMember(@RequestParam("id") Long id) {
        return success(memberService.archiveMember(id));
    }
    // ==================== å†…部方法 ====================
    private UmsMemberRespVO buildMemberDetail(UmsMemberDO member) {
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/controller/admin/member/vo/UmsMemberRespVO.java
@@ -78,6 +78,12 @@
    @Schema(description = "状态", example = "1")
    private Integer status;
    @Schema(description = "归档生成的档案 ID(非空表示已归档)", example = "1024")
    private Long archiveDocumentId;
    @Schema(description = "是否已归档(archiveDocumentId éžç©ºå³ä¸º true)", example = "false")
    private Boolean archived;
    @Schema(description = "备注", example = "")
    private String remark;
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/dal/dataobject/member/UmsMemberDO.java
@@ -86,6 +86,9 @@
    /** çŠ¶æ€ï¼š1-正常 0-暂停 */
    private Integer status;
    /** å½’档生成的档案 ID(archive_document.id,归档联动回写) */
    private Long archiveDocumentId;
    /** å¤‡æ³¨ */
    private String remark;
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/enums/ErrorCodeConstants.java
@@ -23,5 +23,7 @@
    ErrorCode MEMBER_USER_CREATE_FAIL = new ErrorCode(1_030_108_102, "创建会员系统账号失败,请联系管理员");
    ErrorCode MEMBER_USER_MOBILE_EXISTS = new ErrorCode(1_030_108_103, "该手机号已被其他会员账号使用");
    ErrorCode MEMBER_GRADE_NOT_MATCH_TYPE = new ErrorCode(1_030_108_104, "会员等级与会员类型不匹配:个人会员不能选择单位等级,单位会员不能选择个人等级");
    ErrorCode MEMBER_ARCHIVE_FAIL_STATUS = new ErrorCode(1_030_108_105, "归档失败:会员({})当前状态为〔{}〕,只有「正常」状态的会员才能归档到档案管理,请先启用该会员");
    ErrorCode MEMBER_ALREADY_ARCHIVED = new ErrorCode(1_030_108_106, "重复归档:会员({})已于此前归档并生成档案(编号={}),请勿重复归档,如需查看请前往档案管理模块");
}
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberService.java
@@ -27,4 +27,12 @@
    PageResult<UmsMemberDO> getMemberPage(UmsMemberPageReqVO pageReqVO);
    /**
     * å°†ã€Œæ­£å¸¸ã€çŠ¶æ€çš„ä¼šå‘˜æŒ‰æ¥æºå•æ®å½’æ¡£åˆ°æ¡£æ¡ˆç®¡ç†ï¼Œå¹¶å›žå†™å½’æ¡£æ¡£æ¡ˆ ID
     *
     * @param id ä¼šå‘˜ç¼–号
     * @return ç”Ÿæˆçš„æ¡£æ¡ˆç¼–号(用于前端提示)
     */
    String archiveMember(Long id);
}
yudao-module-ums/src/main/java/cn/iocoder/yudao/module/ums/service/member/UmsMemberServiceImpl.java
@@ -3,7 +3,11 @@
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.ums.controller.admin.member.vo.UmsMemberPageReqVO;
@@ -20,7 +24,9 @@
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.ums.enums.ErrorCodeConstants.*;
@@ -37,6 +43,18 @@
    public static final String MEMBER_RECORD_TYPE = "ums_member";
    /** ä¼šå‘˜çŠ¶æ€ï¼šæ­£å¸¸ï¼ˆå¯å½’æ¡£ï¼‰ */
    private static final Integer MEMBER_STATUS_NORMAL = 1;
    /** å½’档来源类型:3-会员(对应 archive æ¨¡å— source_type) */
    private static final Integer ARCHIVE_SOURCE_TYPE_MEMBER = 3;
    /** å½’档密级:2-内部 */
    private static final Integer ARCHIVE_SECRET_LEVEL_INTERNAL = 2;
    /** ä¼šå‘˜æ¡£æ¡ˆåˆ†ç±»ç¼–码(对应 archive_category.code) */
    private static final String ARCHIVE_CATEGORY_CODE_MEMBER = "member";
    @Resource
    private UmsMemberMapper memberMapper;
@@ -48,6 +66,9 @@
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private ArchiveDocumentApi archiveDocumentApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -145,6 +166,39 @@
        return memberMapper.selectPage(pageReqVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String archiveMember(Long id) {
        // 1.1 æ ¡éªŒä¼šå‘˜å­˜åœ¨
        UmsMemberDO member = validateMemberExists(id);
        // 1.2 åªæœ‰ã€Œæ­£å¸¸ã€çŠ¶æ€çš„ä¼šå‘˜æ‰èƒ½å½’æ¡£
        if (!MEMBER_STATUS_NORMAL.equals(member.getStatus())) {
            throw exception(MEMBER_ARCHIVE_FAIL_STATUS, member.getNo(), memberStatusName(member.getStatus()));
        }
        // 1.3 é‡å¤å½’档校验
        if (member.getArchiveDocumentId() != null) {
            throw exception(MEMBER_ALREADY_ARCHIVED, member.getNo(),
                    StrUtil.blankToDefault(archiveNo(id), "未知"));
        }
        // 2. å¸¦å…¥ä¼šå‘˜ææ–™é™„件,按来源单据归档
        List<Long> blobIds = storageAttachmentApi.listAttachments(MEMBER_RECORD_TYPE, id).stream()
                .map(StorageBlobRespDTO::getId).collect(Collectors.toList());
        Long archiveDocumentId = archiveDocumentApi.archiveBySource(new ArchiveDocumentCreateReqDTO()
                .setSourceType(ARCHIVE_SOURCE_TYPE_MEMBER)
                .setSourceId(id)
                .setSourceNo(member.getNo())
                .setSourceTitle(member.getName())
                .setCategoryCode(ARCHIVE_CATEGORY_CODE_MEMBER)
                .setSecretLevel(ARCHIVE_SECRET_LEVEL_INTERNAL)
                .setSummary("会员材料归档")
                .setBlobIds(blobIds)).getCheckedData();
        // 3. å›žå†™å½’档档案 ID(作为已归档硬标记)
        memberMapper.updateById(new UmsMemberDO().setId(id).setArchiveDocumentId(archiveDocumentId));
        return StrUtil.blankToDefault(archiveNo(id), "未知");
    }
    // =================== å†…部方法 ===================
    private Long createSystemUser(UmsApplicationDO application) {
@@ -193,4 +247,19 @@
        return member;
    }
    /** ä¼šå‘˜çŠ¶æ€ä¸­æ–‡åï¼š1-正常 0-暂停(用于归档失败提示) */
    private String memberStatusName(Integer status) {
        if (status == null) {
            return "未知";
        }
        return MEMBER_STATUS_NORMAL.equals(status) ? "正常" : "暂停";
    }
    /** æŸ¥è¯¢æ¥æºå•据已生成的档案编号(未归档时返回 null) */
    private String archiveNo(Long memberId) {
        ArchiveDocumentRespDTO archive = archiveDocumentApi
                .getBySource(ARCHIVE_SOURCE_TYPE_MEMBER, memberId).getCheckedData();
        return archive != null ? archive.getNo() : null;
    }
}
yudao-server/pom.xml
@@ -199,6 +199,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- æ¡£æ¡ˆç®¡ç†æ¨¡å— -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-archive</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- spring boot é…ç½®æ‰€éœ€ä¾èµ– -->
        <dependency>
            <groupId>org.springframework.boot</groupId>