Merge remote-tracking branch 'origin/dev_江苏商贸供销社' into dev_江苏商贸供销社
已修改23个文件
已添加7个文件
766 ■■■■■ 文件已修改
docs/project-business/README.md 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/business.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/flows.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/modules.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/objects.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/index.html 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/08-module-relation.mmd 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/09-data-flow.mmd 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/10-business-object.mmd 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/12-finance-flow.mmd 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance-api/pom.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApi.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/dto/FinanceReimbursementCreateReqDTO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/dto/FinanceReimbursementRespDTO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/dto/FinanceReimbursementUpdateReqDTO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/fund/vo/ResearchFundRecordRespVO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/fund/vo/ResearchFundRecordSaveReqVO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/fund/ResearchFundRecordDO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/mysql/fund/ResearchFundRecordMapper.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/enums/ErrorCodeConstants.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/enums/ResearchFundStatusEnum.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/fund/ResearchFundRecordService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/fund/ResearchFundRecordServiceImpl.java 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/fund/listener/ResearchFundStatusListener.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/application-local.yaml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/README.md
@@ -8,7 +8,7 @@
- **业务定位**:离散制造工厂产供销人财物一体化:覆盖『销售接单 â†’ é‡‡è´­/生产 â†’ è´¨æ£€ â†’ ä»“储 â†’ å‘è´§ â†’ å›žæ¬¾ â†’ å”®åŽã€å…¨é“¾è·¯é—­çޝ
- **技术底座**: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-ums`,入会申请/秘书处与理事会两级审批/会员/通讯录)各自成一条链路,均不与其他业务模块产生单据关联(UMS ä¾‹å¤–:审批通过会经 AdminUserApi åˆ›å»ºç³»ç»Ÿç™»å½•账号);课题管理(`yudao-module-research`,课题指南/在线申报/专家评审/三级立项审批/立项通知/经费拨付)为另一条独立科研主线,仅向外取 `ums_member` ä½œä¸ºç”³æŠ¥äººã€å‘内写 `system_notice` ç«‹é¡¹å…¬å‘Š
- **独立模块**:财务报销(`yudao-module-finance`,报销单/明细/发票附件/审批/归档,对外提供 `yudao-module-finance-api` ä¾›ä»–模块建单)与会员管理(`yudao-module-ums`,入会申请/秘书处与理事会两级审批/会员/通讯录)各自成一条链路(UMS ä¾‹å¤–:审批通过会经 AdminUserApi åˆ›å»ºç³»ç»Ÿç™»å½•账号);课题管理(`yudao-module-research`,课题指南/在线申报/专家评审/三级立项审批/立项通知/经费拨付)为另一条独立科研主线,向外取 `ums_member` ä½œä¸ºç”³æŠ¥äººã€å†™ `system_notice` ç«‹é¡¹å…¬å‘Šï¼Œ**并已对接财务**:经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆè´¢åŠ¡æŠ¥é”€å•å¹¶æäº¤å®¡æ‰¹ï¼Œå®¡æ‰¹ç»“æžœç» BPM ç›‘听器回写课题侧拨款台账
## 2. ç›®å½•结构
@@ -46,9 +46,9 @@
| 09-data-flow | è·¨æ¨¡å—数据流(单据生成、字段带入、数量回写、事件) |
| 10-business-object | æ ¸å¿ƒä¸šåŠ¡å¯¹è±¡ ER(只含业务对象,非全库表) |
| 11-ai-business-flow | AI ä¸šåŠ¡é“¾è·¯ï¼ˆOCR/质检判定/四类预测/RAG,含 API ä¾èµ–) |
| 12-finance-flow | è´¢åŠ¡æŠ¥é”€ï¼ˆç‹¬ç«‹æ¨¡å—ï¼‰ï¼šæŠ¥é”€å•+明细+发票附件 â†’ æäº¤ BPM å®¡æ‰¹ â†’ é€šè¿‡/驳回 â†’ å½’档终态 |
| 12-finance-flow | è´¢åŠ¡æŠ¥é”€ï¼ˆç‹¬ç«‹æ¨¡å—ï¼‰ï¼šæŠ¥é”€å•+明细+发票附件 â†’ æäº¤ BPM å®¡æ‰¹ â†’ é€šè¿‡/驳回 â†’ å½’档终态;单据来源含手工新建与「课题经费拨付自动生成」两类 |
| 13-ums-membership-flow | ä¼šå‘˜å…¥ä¼šï¼ˆç‹¬ç«‹æ¨¡å—):入会申请 + ææ–™é™„ä»¶ â†’ æäº¤ BPM ä¸¤çº§å®¡æ‰¹ï¼ˆç§˜ä¹¦å¤„初审→理事会审议)→ é€šè¿‡è‡ªåŠ¨å»ºä¼šå‘˜ä¸Žç™»å½•è´¦å· â†’ ä¼šå‘˜åˆ—表/通讯录检索 |
| 14-research-flow | è¯¾é¢˜ç®¡ç†ï¼ˆç‹¬ç«‹æ¨¡å—):指南发布(重点/一般)→在线申报(选会员带出申报人)→专家评分明细加权排名→BPM ä¸‰çº§å®¡æ‰¹(秘书处形式审查→专家评审→学会立项审批)→立项编号与公告→经费拨付记录 |
| 14-research-flow | è¯¾é¢˜ç®¡ç†ï¼ˆç‹¬ç«‹æ¨¡å—):指南发布(重点/一般)→在线申报(选会员带出申报人)→专家评分明细加权排名→BPM ä¸‰çº§å®¡æ‰¹(秘书处形式审查→专家评审→学会立项审批)→立项编号与公告→经费拨付(凭证必填→1:1 è‡ªåŠ¨ç”Ÿæˆå¹¶æäº¤è´¢åŠ¡æŠ¥é”€å•â†’è´¢åŠ¡å®¡æ ¸/财务主管两级审批→结果经监听器回写台账:通过已拨付/驳回已驳回可重发/撤回回待拨付)|
图中状态数字均为源码枚举真实值(如工单 0草稿/1已确认/2完成/3取消;ERP å®¡æ‰¹ 15审批中/20已审核)。
@@ -69,8 +69,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;二期结项验收与成果归档尚未做,图中已标注未覆盖范围)。
- ç”ŸæˆåŸºçº¿ï¼š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。
## 7. å·²çŸ¥å¾…确认业务
见 `data/business.json` çš„ `pendingConfirm`(页面「待确认事项」栏),要点:仓库类型/合格仓改造未落地、HRM äº¤æŽ¥ expandUserIds å°šæœªè¢«ä¸šåŠ¡æŸ¥è¯¢æŽ¥å…¥ã€å›žæ¬¾ä¸Žæ”¶æ¬¾å•æœªèµ° BPM、财务勾稽回写方法为空、NCR å¤„置不回写源单、财务报销的 BPM æµç¨‹å®šä¹‰æœªå‘布与普通角色未授菜单、会员管理的 BPM æµç¨‹æœªå‘布且缺审批自定义表单页、自动建账号用手机号作初始密码存在弱口令风险、课题管理的评分权重与通过阈值为占位规则待学会确认、一期经费拨付与财务模块无数据同步、申报单与会员为软关联可能悬空等。
见 `data/business.json` çš„ `pendingConfirm`(页面「待确认事项」栏),要点:仓库类型/合格仓改造未落地、HRM äº¤æŽ¥ expandUserIds å°šæœªè¢«ä¸šåŠ¡æŸ¥è¯¢æŽ¥å…¥ã€å›žæ¬¾ä¸Žæ”¶æ¬¾å•æœªèµ° BPM、财务勾稽回写方法为空、NCR å¤„置不回写源单、财务报销的 BPM æµç¨‹å®šä¹‰æœªå‘布与普通角色未授菜单、会员管理的 BPM æµç¨‹æœªå‘布且缺审批自定义表单页、自动建账号用手机号作初始密码存在弱口令风险、课题管理的评分权重与通过阈值为占位规则待学会确认、课题经费拨付虽已对接财务报销但台账与报销单为软关联(财务侧删单会留悬空 `reimbursement_id`)、新建的财务报销审批流程节点审批人需上线前在 BPM è®¾è®¡å™¨æŒ‰å®žé™…组织调整、申报单与会员为软关联可能悬空等。
docs/project-business/data/business.json
@@ -27,7 +27,7 @@
    "businessModules": 16,
    "coreObjects": 56,
    "documentFlows": 9,
    "approvalListeners": 14,
    "approvalListeners": 15,
    "aiCapabilities": 8,
    "wmBillTypes": 15
  },
@@ -147,18 +147,19 @@
    "转正申请(hrm_regularize_application)、培训证书等仅有 SQL ä¸Žèœå•初始化,Java ä¾§ Service/Controller æœªå®žçް",
    "NCR å¤„置结果(返工/退货)未回写触发源单据,仅台账记录",
    "MPS çŠ¶æ€æžšä¸¾ç‹¬ç«‹ç¼–å·(0/1/2/3),与通用单据常量(0/1/4/5)不一致",
    "财务报销:BPM åˆ†ç±» finance_reimbursement_approve ä¸‹å°šæ— å·²å‘布的流程定义(act_re_cprocdef ä¸ºç©ºï¼‰ï¼Œä¸”流程需把「自定义表单路径」配为 /finance/reimbursement/detail/approve/index.vue,否则前端「提交审批」被禁用、审批中心看不到报销单内容【需 BPM è®¾è®¡å™¨ä¾§é…ç½®ã€‘",
    "财务报销:菜单仅授予 super_admin(超管由代码直返全部菜单),普通角色未配 system_role_menu,非超管登录看不到「报销单」菜单【待确认是否补授权】",
    "会员管理:会员管理菜单(1075490~1075503)仅 super_admin å¯è§ï¼Œæ™®é€šè§’色未配 system_role_menu,非超管登录看不到会员管理目录【待确认是否补授权】",
    "会员管理:审批通过自动建系统账号用「联系电话」同时作用户名与初始密码,若联系电话为空会用字面量 unknown å…œåº•,存在弱口令与重名风险【待确认是否改造】",
    "会员管理:自动建档不搬运 contactUser/unitIntro/expireDate(ums_application æ— å¯¹åº”字段),单位会员的「联系人/单位简介/有效期」只能在会员列表手工补录【待确认是否需要申请单增字段】",
    "会员管理:单位会员的等级字典 ums_unit_grade ä»…到「会长单位」(4),但校验逻辑额外硬编码禁止单位选「理事长」(grade=5)——该值本不在单位字典内,属防御性冗余校验【待确认是否精简】",
    "课题管理:专家评分权重(选题0.3/方案0.3/团队0.2/成果0.2)与「加权均分≥60 ä¸” å¤šæ•°ç»“论为推荐立项」的通过阈值是占位规则,需学会最终确认;已集中为常量便于调整【待确认,改动只需改常量】",
    "课题管理:一期经费拨付只在本模块落拨款记录,与财务模块无任何数据同步,同一笔钱在两处记录存在不一致风险;二期对接时需明确以哪侧为准【待确认】",
    "课题管理:经费拨付已对接财务报销(1:1 ç”Ÿæˆ finance_reimbursement å¹¶è‡ªåŠ¨æäº¤å®¡æ‰¹ï¼‰ï¼Œä½†å°è´¦ä¸ŽæŠ¥é”€å•æ˜¯è½¯å…³è”ï¼ˆæ— å¤–é”®ï¼Œä»…é  reimbursement_id),财务侧直接删除报销单会留下台账的悬空 reimbursement_id;已用 FUND_REIMBURSEMENT_NOT_EXISTS ç²¾ç¡®æç¤ºå…œåº•,不做数据库级约束【已接受的风险】",
    "课题管理:新建的财务报销审批流程 finance_reimbursement_approve ä¸º 2 èŠ‚ç‚¹ï¼ˆè´¢åŠ¡å®¡æ ¸ â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰ï¼ŒèŠ‚ç‚¹é»˜è®¤å®¡æ‰¹äººç­–ç•¥ä¸ºã€Œè§’è‰² = è¶…级管理员(1)、依次审批」;上线前需在 BPM è®¾è®¡å™¨ä¸­æŒ‰å­¦ä¼š/财务实际审批人调整,否则该角色下多用户会变成逐人会签【环境配置项,非代码缺陷】",
    "课题管理:经费拨付台账的「已拨付(20)」等价于报销单「已通过(20)」,不含出纳实际付款动作;若日后需要独立的「已付款」状态,需另增状态与审批节点【二期可扩展】",
    "课题管理:申报单与 ums_member æ˜¯è½¯å…³è”(无外键),会员被删除后申报单会留下悬空 memberId;成员列表需容错展示「会员已删除」【待确认是否加外键或删除保护】",
    "课题管理:BPM æµç¨‹ research_topic_approve ä¸‰èŠ‚ç‚¹é»˜è®¤å®¡æ‰¹äººä¸ºã€Œè§’è‰²=超级管理员(1) + ä¾æ¬¡å®¡æ‰¹ã€ï¼Œè¯¥ dev åº“超级管理员角色下有 4 ä¸ªç”¨æˆ·ï¼ˆadmin/张娟/马芳/马贞),每个节点需 4 äººä¼šç­¾æ‰æŽ¨è¿›ï¼›å…¨æ–°å®‰è£…库该角色仅 admin ä¸€äººã€æµç¨‹å¯å¼€ç®±å³ç”¨ã€‚dev åº“需减少超级管理员用户或改在 BPM è®¾è®¡å™¨ä¸­ä¸ºæ¯èŠ‚ç‚¹æŒ‡å®šå®¡æ‰¹äººã€çŽ¯å¢ƒé…ç½®é¡¹ï¼Œéžä»£ç ç¼ºé™·ã€‘",
    "课题管理:立项通知复用 system_notice ç³»ç»Ÿå…¬å‘Šï¼Œä¸æ–°å¢ž system_notify_template、不发站内信;如需短信/站内信定向通知需另行配置【待确认通知渠道】"
  ],
  "generatedAt": "2026-09-28",
  "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"
  "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 é˜¶æ®µä¸€ï¼Œä¸¤çº§å®¡æ‰¹å…¨é€šè¿‡/驳回/取消三条回写链路均已端到端验证"
}
docs/project-business/data/flows.json
@@ -74,7 +74,7 @@
       {"stage": "建档", "action": "审批通过钩子自动建会员:①生成会员编号 HY+yyyyMMdd+6位流水 â‘¡AdminUserApi.createUser å»ºç³»ç»Ÿè´¦å·ï¼ˆç”¨æˆ·åä¸Žåˆå§‹å¯†ç =联系电话、昵称=申请人姓名、部门=学会会员)③会员落库(入会日期=当天、状态 1 æ­£å¸¸ã€applicationId å›žå¡«æ¥æºç”³è¯·å•);手机号重复会明确报错", "roles": ["系统自动"]},
       {"stage": "管理与检索", "action": "会员列表支持手工新增/编辑/删除(等级与类型校验:单位会员不可选『理事长』);会员通讯录为只读页,按地区编码与行业检索", "roles": ["会员管理员", "全体可查通讯录"]}]},
    {"id": "research", "name": "课题管理流程", "mermaid": "mermaid/14-research-flow.mmd", "color": "#e070a8",
     "summary": "独立科研模块(yudao-module-research):学会发布课题指南(重点/一般分类)→ ä¼šå‘˜åœ¨çº¿æäº¤è¯¾é¢˜ç”³æŠ¥ä¹¦ â†’ æäº¤ BPM ä¸‰çº§å®¡æ‰¹ï¼ˆç§˜ä¹¦å¤„形式审查 â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ï¼‰ï¼Œä¸“家评分以结构化明细子表记录并由后端自动汇总加权均分、排名与推荐结论 â†’ å®¡æ‰¹é€šè¿‡ç”Ÿæˆç«‹é¡¹ç¼–号并发布系统公告 â†’ æŒ‰æ‰¹æ¬¡ç™»è®°ç»è´¹æ‹¨ä»˜è®°å½•(含凭证附件)。院级二期再补结项验收与成果归档。",
     "summary": "独立科研模块(yudao-module-research):学会发布课题指南(重点/一般分类)→ ä¼šå‘˜åœ¨çº¿æäº¤è¯¾é¢˜ç”³æŠ¥ä¹¦ â†’ æäº¤ BPM ä¸‰çº§å®¡æ‰¹ï¼ˆç§˜ä¹¦å¤„形式审查 â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ï¼‰ï¼Œä¸“家评分以结构化明细子表记录并由后端自动汇总加权均分、排名与推荐结论 â†’ å®¡æ‰¹é€šè¿‡ç”Ÿæˆç«‹é¡¹ç¼–号并发布系统公告 â†’ æŒ‰æ‰¹æ¬¡ç™»è®°ç»è´¹æ‹¨ä»˜ï¼ˆå‡­è¯å¿…填),每条拨款记录自动生成并提交一张 1:1 çš„财务报销单(finance_reimbursement),走 BPM ä¸¤çº§å®¡æ‰¹åŽç”±ç›‘听器把结果回写台账。院级二期再补结项验收与成果归档。",
     "steps": [
       {"stage": "发布指南", "action": "新建指南选类别 1重点课题 / 2一般课题,填申报起止时间与指南正文;编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®ï¼‰ï¼›status: 0草稿/20已发布/30已截止。仅草稿可改可删,发布后即冻结;已产生申报单的指南禁止删除(提示该指南下已有 N å¼ ç”³æŠ¥å•)", "roles": ["学会秘书处", "课题管理员"]},
       {"stage": "在线申报", "action": "从已发布且在申报期内的指南新建申报书;选会员(ums_member)自动带出姓名/工作单位/职称/联系电话/邮箱,可改;填课题名称、课题组成员、申请经费、申报书正文;编号 SB+yyyyMMdd+6位流水;类别冗余自指南,不信任前端传值;status: 0草稿", "roles": ["课题申报人(会员)", "课题管理员"]},
@@ -82,7 +82,7 @@
       {"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)」的申报单可登记拨款记录(批次名称/拨付金额/拨付日期/收款单位/状态/备注/凭证附件);金额校验:已登记金额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出该单申请经费,超出时提示报出本次、已登记、合计、预算四项数值;status: 0待拨付/20已拨付,凭证明细走 system é™„件中心 recordType=research_fund_record,状态改为已拨付时凭证必填;仅待拨付状态可改可删,编辑时会把本行金额加回余额显示", "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": "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
@@ -35,10 +35,10 @@
    {"key": "fin", "name": "财务报销(独立)", "color": "#3ec9a7", "tag": "FIN", "flow": "finance",
     "positioning": "独立的发票报销单据与归档",
     "problem": "纸质发票难归集、报销审批无留痕、归档后无法追溯",
     "features": ["报销单(主表 + 1..n æ˜Žç»†è¡Œ)", "发票附件多文件上传(附件中心多态关联 storage_attachment)", "单号 Redis ç”Ÿæˆ BX+yyyyMMdd+6位流水·每日重置", "明细随主表一次性提交(先删后增全量覆盖)", "BPM å®¡æ‰¹(通过20/驳回35/取消退回草稿0)", "已驳回可改发票后重新提交", "归档终态 40 å¹¶è®°å½•归档时间", "费用类型/报销状态双字典 + Excel å¯¼å‡º"],
     "features": ["报销单(主表 + 1..n æ˜Žç»†è¡Œ)", "发票附件多文件上传(附件中心多态关联 storage_attachment)", "单号 Redis ç”Ÿæˆ BX+yyyyMMdd+6位流水·每日重置", "明细随主表一次性提交(先删后增全量覆盖)", "BPM å®¡æ‰¹(通过20/驳回35/取消退回草稿0)", "已驳回可改发票后重新提交", "归档终态 40 å¹¶è®°å½•归档时间", "费用类型/报销状态双字典 + Excel å¯¼å‡º", "对外提供 FinanceReimbursementApi(yudao-module-finance-api)供课题管理经费拨付自动建单并提交(1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他·金额一致)"],
     "objects": ["报销单", "报销明细"],
     "roles": ["报销人", "财务", "审批人"],
     "upstream": [], "downstream": [],
     "upstream": ["课题管理(经费拨付 1:1 è‡ªåŠ¨ç”ŸæˆæŠ¥é”€å•Â·èµ° yudao-module-finance-api·本地 Bean è°ƒç”¨)"], "downstream": [],
     "coreTables": ["finance_reimbursement", "finance_reimbursement_item"]},
    {"key": "pro", "name": "生产管理", "color": "#e0b34f", "tag": "MES-PRO", "flow": "production",
     "positioning": "生产执行核心:计划-工单-任务-报工",
@@ -107,16 +107,16 @@
    {"key": "res", "name": "课题管理(独立)", "color": "#e070a8", "tag": "RES", "flow": "research",
     "positioning": "学会/协会型科研课题全生命周期:指南发布 â†’ åœ¨çº¿ç”³æŠ¥ â†’ ä¸“家评审 â†’ ç«‹é¡¹å®¡æ‰¹ â†’ ç»è´¹æ‹¨ä»˜ï¼ˆç«‹é¡¹é€šçŸ¥èµ°ç³»ç»Ÿå…¬å‘Šï¼‰",
     "problem": "课题指南靠线下发布、申报书靠纸质或邮件收集、专家评分散落在表格里无法自动汇总排名、立项与拨款无留痕",
     "features": ["课题指南(重点1/一般2 åˆ†ç±»æ ‡æ³¨)", "指南状态 0草稿/20已发布/30已截止·发布/截止两个动作", "指南编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)", "在线申报书(选指南+选会员带出申报人信息+课题组成员+申请经费+申报书正文)", "申报编号 SB+同日流水·类别冗余自指南不信任前端", "申报状态 0草稿/15审批中/20已立项/35已驳回", "提交前校验指南已发布且在申报期内(逐条报具体维度与时间)", "专家评审明细子表(一位专家一行·四项加权 0.3/0.3/0.2/0.2)", "后端自动重算加权合计与排名·回写主单均分与推荐结论", "BPM ä¸‰çº§å®¡æ‰¹ research_topic_approve(秘书处形式审查→专家评审→学会立项审批)", "审批通过生成立项编号 LX+同日流水 ä¸Žç«‹é¡¹æ—¶é—´(幂等)", "发布立项通知(生成 system_notice å…¬å‘ŠÂ·å†™å›ž noticeId é˜²é‡å¤)", "经费拨付记录(批次/金额/日期/收款单位/状态/凭证附件)·金额不得超申请经费", "附件走 system é™„件中心(recordType=research_guideline / research_application / research_fund_record)"],
     "features": ["课题指南(重点1/一般2 åˆ†ç±»æ ‡æ³¨)", "指南状态 0草稿/20已发布/30已截止·发布/截止两个动作", "指南编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)", "在线申报书(选指南+选会员带出申报人信息+课题组成员+申请经费+申报书正文)", "申报编号 SB+同日流水·类别冗余自指南不信任前端", "申报状态 0草稿/15审批中/20已立项/35已驳回", "提交前校验指南已发布且在申报期内(逐条报具体维度与时间)", "专家评审明细子表(一位专家一行·四项加权 0.3/0.3/0.2/0.2)", "后端自动重算加权合计与排名·回写主单均分与推荐结论", "BPM ä¸‰çº§å®¡æ‰¹ research_topic_approve(秘书处形式审查→专家评审→学会立项审批)", "审批通过生成立项编号 LX+同日流水 ä¸Žç«‹é¡¹æ—¶é—´(幂等)", "发布立项通知(生成 system_notice å…¬å‘ŠÂ·å†™å›ž noticeId é˜²é‡å¤)", "经费拨付记录(批次/金额/日期/收款单位/备注/凭证附件)·凭证必填·金额不得超申请经费", "经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆå¹¶æäº¤è´¢åŠ¡æŠ¥é”€å•(FinanceReimbursementApi·1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他)", "拨付状态 0待拨付/15报销审批中/20已拨付/30已驳回·由 BPM ç›‘听器回写(含驳回原因与时间)", "已驳回可改金额/凭证后重新发起报销·待拨付可删并联动清理财务侧草稿报销单", "附件走 system é™„件中心(recordType=research_guideline / research_application / research_fund_record)"],
     "objects": ["课题指南", "课题申报书", "专家评审明细", "经费拨付记录"],
     "roles": ["课题申报人(会员)", "课题管理员", "评审专家", "学会秘书处", "学会理事会"],
     "upstream": ["会员管理(ums_member å–申报人姓名/单位/职称/电话)", "BPM(流程定义 research_topic_approve)"],
     "downstream": ["system(system_notice ç«‹é¡¹é€šçŸ¥å…¬å‘Š)", "system(附件中心)", "财务(二期预期待对接·一期不接)"],
     "roles": ["课题申报人(会员)", "课题管理员", "评审专家", "学会秘书处", "学会理事会", "学会财务"],
     "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": "bpm", "name": "协同审批", "color": "#e6a23c", "tag": "BPM", "flow": null,
     "positioning": "全系统单据审批中枢(Flowable)",
     "problem": "多模块单据需要灵活可配的多级审批",
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "14个业务监听器", "表单·用户组·抄送·加签"],
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "15个业务监听器(同一流程 key å…è®¸å¤šä¸ªç›‘听器并行·各自按 key è¿‡æ»¤)", "表单·用户组·抄送·加签"],
     "objects": ["流程模型", "流程实例", "审批任务"],
     "roles": ["审批人(按流程配置)", "流程管理员"],
     "upstream": ["各业务提交审批"], "downstream": ["回调更新各业务单据状态"],
docs/project-business/data/objects.json
@@ -48,13 +48,13 @@
    {"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 è¢«å›žå†™ï¼›ç‹¬ç«‹æ¨¡å—,不与其它业务模块产生单据关联", "upstream": [], "downstream": ["报销明细", "发票附件(storage_attachment·recordType=finance_reimbursement)"], "keywords": "报销单 æŠ¥é”€ å‘票 reimbursement BX å½’æ¡£ è´¢åŠ¡ è´¹ç”¨ è‡ªå®šä¹‰å•号"},
    {"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_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": "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_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": "research_fund_record", "doc": "一主多行的拨款记录:batchName æ‰¹æ¬¡åç§°/amount æ‹¨ä»˜é‡‘额/payDate æ‹¨ä»˜æ—¥æœŸ/payee æ”¶æ¬¾å•位/status 0待拨付 20已拨付/remark,凭证附件走 system é™„件中心 recordType=research_fund_record(表内不存文件地址)。仅申报单 status=20 å·²ç«‹é¡¹å¯ç™»è®°ï¼›é‡‘额校验:已登记金额(该单全部记录求和) + æœ¬æ¬¡é‡‘额 ä¸å¾—超出申报单 budgetAmount,超出时提示报出本次、已登记、合计、预算四项数值;status=20 å·²æ‹¨ä»˜ æ—¶å‡­è¯é™„ä»¶å¿…å¡«(前端先行拦截,后端 validateVoucherRequired å…œåº•);仅待拨付状态可改可删,编辑时把本行金额加回可用余额展示避免误解", "upstream": ["课题申报书(必须已立项)"], "downstream": ["拨付凭证(storage_attachment·recordType=research_fund_record)", "财务(二期预期待对接·一期不接)"], "keywords": "经费拨付 æ‹¨æ¬¾ æ‹¨ä»˜è®°å½• æ‰¹æ¬¡ æ‹¨ä»˜é‡‘额 å‡­è¯ å¾…拨付 å·²æ‹¨ä»˜ æ”¶æ¬¾å•位 èµ„助金额 é¢„ç®— è¶…额"}
    {"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/index.html
@@ -213,7 +213,7 @@
    "businessModules": 16,
    "coreObjects": 56,
    "documentFlows": 9,
    "approvalListeners": 14,
    "approvalListeners": 15,
    "aiCapabilities": 8,
    "wmBillTypes": 15
  },
@@ -333,20 +333,21 @@
    "转正申请(hrm_regularize_application)、培训证书等仅有 SQL ä¸Žèœå•初始化,Java ä¾§ Service/Controller æœªå®žçް",
    "NCR å¤„置结果(返工/退货)未回写触发源单据,仅台账记录",
    "MPS çŠ¶æ€æžšä¸¾ç‹¬ç«‹ç¼–å·(0/1/2/3),与通用单据常量(0/1/4/5)不一致",
    "财务报销:BPM åˆ†ç±» finance_reimbursement_approve ä¸‹å°šæ— å·²å‘布的流程定义(act_re_cprocdef ä¸ºç©ºï¼‰ï¼Œä¸”流程需把「自定义表单路径」配为 /finance/reimbursement/detail/approve/index.vue,否则前端「提交审批」被禁用、审批中心看不到报销单内容【需 BPM è®¾è®¡å™¨ä¾§é…ç½®ã€‘",
    "财务报销:菜单仅授予 super_admin(超管由代码直返全部菜单),普通角色未配 system_role_menu,非超管登录看不到「报销单」菜单【待确认是否补授权】",
    "会员管理:会员管理菜单(1075490~1075503)仅 super_admin å¯è§ï¼Œæ™®é€šè§’色未配 system_role_menu,非超管登录看不到会员管理目录【待确认是否补授权】",
    "会员管理:审批通过自动建系统账号用「联系电话」同时作用户名与初始密码,若联系电话为空会用字面量 unknown å…œåº•,存在弱口令与重名风险【待确认是否改造】",
    "会员管理:自动建档不搬运 contactUser/unitIntro/expireDate(ums_application æ— å¯¹åº”字段),单位会员的「联系人/单位简介/有效期」只能在会员列表手工补录【待确认是否需要申请单增字段】",
    "会员管理:单位会员的等级字典 ums_unit_grade ä»…到「会长单位」(4),但校验逻辑额外硬编码禁止单位选「理事长」(grade=5)——该值本不在单位字典内,属防御性冗余校验【待确认是否精简】",
    "课题管理:专家评分权重(选题0.3/方案0.3/团队0.2/成果0.2)与「加权均分≥60 ä¸” å¤šæ•°ç»“论为推荐立项」的通过阈值是占位规则,需学会最终确认;已集中为常量便于调整【待确认,改动只需改常量】",
    "课题管理:一期经费拨付只在本模块落拨款记录,与财务模块无任何数据同步,同一笔钱在两处记录存在不一致风险;二期对接时需明确以哪侧为准【待确认】",
    "课题管理:经费拨付已对接财务报销(1:1 ç”Ÿæˆ finance_reimbursement å¹¶è‡ªåŠ¨æäº¤å®¡æ‰¹ï¼‰ï¼Œä½†å°è´¦ä¸ŽæŠ¥é”€å•æ˜¯è½¯å…³è”ï¼ˆæ— å¤–é”®ï¼Œä»…é  reimbursement_id),财务侧直接删除报销单会留下台账的悬空 reimbursement_id;已用 FUND_REIMBURSEMENT_NOT_EXISTS ç²¾ç¡®æç¤ºå…œåº•,不做数据库级约束【已接受的风险】",
    "课题管理:新建的财务报销审批流程 finance_reimbursement_approve ä¸º 2 èŠ‚ç‚¹ï¼ˆè´¢åŠ¡å®¡æ ¸ â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹ï¼‰ï¼ŒèŠ‚ç‚¹é»˜è®¤å®¡æ‰¹äººç­–ç•¥ä¸ºã€Œè§’è‰² = è¶…级管理员(1)、依次审批」;上线前需在 BPM è®¾è®¡å™¨ä¸­æŒ‰å­¦ä¼š/财务实际审批人调整,否则该角色下多用户会变成逐人会签【环境配置项,非代码缺陷】",
    "课题管理:经费拨付台账的「已拨付(20)」等价于报销单「已通过(20)」,不含出纳实际付款动作;若日后需要独立的「已付款」状态,需另增状态与审批节点【二期可扩展】",
    "课题管理:申报单与 ums_member æ˜¯è½¯å…³è”(无外键),会员被删除后申报单会留下悬空 memberId;成员列表需容错展示「会员已删除」【待确认是否加外键或删除保护】",
    "课题管理:BPM æµç¨‹ research_topic_approve ä¸‰èŠ‚ç‚¹é»˜è®¤å®¡æ‰¹äººä¸ºã€Œè§’è‰²=超级管理员(1) + ä¾æ¬¡å®¡æ‰¹ã€ï¼Œè¯¥ dev åº“超级管理员角色下有 4 ä¸ªç”¨æˆ·ï¼ˆadmin/张娟/马芳/马贞),每个节点需 4 äººä¼šç­¾æ‰æŽ¨è¿›ï¼›å…¨æ–°å®‰è£…库该角色仅 admin ä¸€äººã€æµç¨‹å¯å¼€ç®±å³ç”¨ã€‚dev åº“需减少超级管理员用户或改在 BPM è®¾è®¡å™¨ä¸­ä¸ºæ¯èŠ‚ç‚¹æŒ‡å®šå®¡æ‰¹äººã€çŽ¯å¢ƒé…ç½®é¡¹ï¼Œéžä»£ç ç¼ºé™·ã€‘",
    "课题管理:立项通知复用 system_notice ç³»ç»Ÿå…¬å‘Šï¼Œä¸æ–°å¢ž system_notify_template、不发站内信;如需短信/站内信定向通知需另行配置【待确认通知渠道】"
  ],
  "generatedAt": "2026-09-28",
  "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"
  "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 é˜¶æ®µä¸€ï¼Œä¸¤çº§å®¡æ‰¹å…¨é€šè¿‡/驳回/取消三条回写链路均已端到端验证"
}</script>
<script type="application/json" id="data-modules">{
  "modules": [
@@ -385,10 +386,10 @@
    {"key": "fin", "name": "财务报销(独立)", "color": "#3ec9a7", "tag": "FIN", "flow": "finance",
     "positioning": "独立的发票报销单据与归档",
     "problem": "纸质发票难归集、报销审批无留痕、归档后无法追溯",
     "features": ["报销单(主表 + 1..n æ˜Žç»†è¡Œ)", "发票附件多文件上传(附件中心多态关联 storage_attachment)", "单号 Redis ç”Ÿæˆ BX+yyyyMMdd+6位流水·每日重置", "明细随主表一次性提交(先删后增全量覆盖)", "BPM å®¡æ‰¹(通过20/驳回35/取消退回草稿0)", "已驳回可改发票后重新提交", "归档终态 40 å¹¶è®°å½•归档时间", "费用类型/报销状态双字典 + Excel å¯¼å‡º"],
     "features": ["报销单(主表 + 1..n æ˜Žç»†è¡Œ)", "发票附件多文件上传(附件中心多态关联 storage_attachment)", "单号 Redis ç”Ÿæˆ BX+yyyyMMdd+6位流水·每日重置", "明细随主表一次性提交(先删后增全量覆盖)", "BPM å®¡æ‰¹(通过20/驳回35/取消退回草稿0)", "已驳回可改发票后重新提交", "归档终态 40 å¹¶è®°å½•归档时间", "费用类型/报销状态双字典 + Excel å¯¼å‡º", "对外提供 FinanceReimbursementApi(yudao-module-finance-api)供课题管理经费拨付自动建单并提交(1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他·金额一致)"],
     "objects": ["报销单", "报销明细"],
     "roles": ["报销人", "财务", "审批人"],
     "upstream": [], "downstream": [],
     "upstream": ["课题管理(经费拨付 1:1 è‡ªåŠ¨ç”ŸæˆæŠ¥é”€å•Â·èµ° yudao-module-finance-api·本地 Bean è°ƒç”¨)"], "downstream": [],
     "coreTables": ["finance_reimbursement", "finance_reimbursement_item"]},
    {"key": "pro", "name": "生产管理", "color": "#e0b34f", "tag": "MES-PRO", "flow": "production",
     "positioning": "生产执行核心:计划-工单-任务-报工",
@@ -457,16 +458,16 @@
    {"key": "res", "name": "课题管理(独立)", "color": "#e070a8", "tag": "RES", "flow": "research",
     "positioning": "学会/协会型科研课题全生命周期:指南发布 â†’ åœ¨çº¿ç”³æŠ¥ â†’ ä¸“家评审 â†’ ç«‹é¡¹å®¡æ‰¹ â†’ ç»è´¹æ‹¨ä»˜ï¼ˆç«‹é¡¹é€šçŸ¥èµ°ç³»ç»Ÿå…¬å‘Šï¼‰",
     "problem": "课题指南靠线下发布、申报书靠纸质或邮件收集、专家评分散落在表格里无法自动汇总排名、立项与拨款无留痕",
     "features": ["课题指南(重点1/一般2 åˆ†ç±»æ ‡æ³¨)", "指南状态 0草稿/20已发布/30已截止·发布/截止两个动作", "指南编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)", "在线申报书(选指南+选会员带出申报人信息+课题组成员+申请经费+申报书正文)", "申报编号 SB+同日流水·类别冗余自指南不信任前端", "申报状态 0草稿/15审批中/20已立项/35已驳回", "提交前校验指南已发布且在申报期内(逐条报具体维度与时间)", "专家评审明细子表(一位专家一行·四项加权 0.3/0.3/0.2/0.2)", "后端自动重算加权合计与排名·回写主单均分与推荐结论", "BPM ä¸‰çº§å®¡æ‰¹ research_topic_approve(秘书处形式审查→专家评审→学会立项审批)", "审批通过生成立项编号 LX+同日流水 ä¸Žç«‹é¡¹æ—¶é—´(幂等)", "发布立项通知(生成 system_notice å…¬å‘ŠÂ·å†™å›ž noticeId é˜²é‡å¤)", "经费拨付记录(批次/金额/日期/收款单位/状态/凭证附件)·金额不得超申请经费", "附件走 system é™„件中心(recordType=research_guideline / research_application / research_fund_record)"],
     "features": ["课题指南(重点1/一般2 åˆ†ç±»æ ‡æ³¨)", "指南状态 0草稿/20已发布/30已截止·发布/截止两个动作", "指南编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®)", "在线申报书(选指南+选会员带出申报人信息+课题组成员+申请经费+申报书正文)", "申报编号 SB+同日流水·类别冗余自指南不信任前端", "申报状态 0草稿/15审批中/20已立项/35已驳回", "提交前校验指南已发布且在申报期内(逐条报具体维度与时间)", "专家评审明细子表(一位专家一行·四项加权 0.3/0.3/0.2/0.2)", "后端自动重算加权合计与排名·回写主单均分与推荐结论", "BPM ä¸‰çº§å®¡æ‰¹ research_topic_approve(秘书处形式审查→专家评审→学会立项审批)", "审批通过生成立项编号 LX+同日流水 ä¸Žç«‹é¡¹æ—¶é—´(幂等)", "发布立项通知(生成 system_notice å…¬å‘ŠÂ·å†™å›ž noticeId é˜²é‡å¤)", "经费拨付记录(批次/金额/日期/收款单位/备注/凭证附件)·凭证必填·金额不得超申请经费", "经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆå¹¶æäº¤è´¢åŠ¡æŠ¥é”€å•(FinanceReimbursementApi·1 è¡Œæ˜Žç»†Â·è´¹ç”¨ç±»åž‹=其他)", "拨付状态 0待拨付/15报销审批中/20已拨付/30已驳回·由 BPM ç›‘听器回写(含驳回原因与时间)", "已驳回可改金额/凭证后重新发起报销·待拨付可删并联动清理财务侧草稿报销单", "附件走 system é™„件中心(recordType=research_guideline / research_application / research_fund_record)"],
     "objects": ["课题指南", "课题申报书", "专家评审明细", "经费拨付记录"],
     "roles": ["课题申报人(会员)", "课题管理员", "评审专家", "学会秘书处", "学会理事会"],
     "upstream": ["会员管理(ums_member å–申报人姓名/单位/职称/电话)", "BPM(流程定义 research_topic_approve)"],
     "downstream": ["system(system_notice ç«‹é¡¹é€šçŸ¥å…¬å‘Š)", "system(附件中心)", "财务(二期预期待对接·一期不接)"],
     "roles": ["课题申报人(会员)", "课题管理员", "评审专家", "学会秘书处", "学会理事会", "学会财务"],
     "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": "bpm", "name": "协同审批", "color": "#e6a23c", "tag": "BPM", "flow": null,
     "positioning": "全系统单据审批中枢(Flowable)",
     "problem": "多模块单据需要灵活可配的多级审批",
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "14个业务监听器", "表单·用户组·抄送·加签"],
     "features": ["BPMN+钉钉风格双设计器", "流程定义部署", "审批事件统一回调(BpmProcessInstanceStatusEvent)", "15个业务监听器(同一流程 key å…è®¸å¤šä¸ªç›‘听器并行·各自按 key è¿‡æ»¤)", "表单·用户组·抄送·加签"],
     "objects": ["流程模型", "流程实例", "审批任务"],
     "roles": ["审批人(按流程配置)", "流程管理员"],
     "upstream": ["各业务提交审批"], "downstream": ["回调更新各业务单据状态"],
@@ -536,14 +537,14 @@
    {"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 è¢«å›žå†™ï¼›ç‹¬ç«‹æ¨¡å—,不与其它业务模块产生单据关联", "upstream": [], "downstream": ["报销明细", "发票附件(storage_attachment·recordType=finance_reimbursement)"], "keywords": "报销单 æŠ¥é”€ å‘票 reimbursement BX å½’æ¡£ è´¢åŠ¡ è´¹ç”¨ è‡ªå®šä¹‰å•号"},
    {"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_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": "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_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": "research_fund_record", "doc": "一主多行的拨款记录:batchName æ‰¹æ¬¡åç§°/amount æ‹¨ä»˜é‡‘额/payDate æ‹¨ä»˜æ—¥æœŸ/payee æ”¶æ¬¾å•位/status 0待拨付 20已拨付/remark,凭证附件走 system é™„件中心 recordType=research_fund_record(表内不存文件地址)。仅申报单 status=20 å·²ç«‹é¡¹å¯ç™»è®°ï¼›é‡‘额校验:已登记金额(该单全部记录求和) + æœ¬æ¬¡é‡‘额 ä¸å¾—超出申报单 budgetAmount,超出时提示报出本次、已登记、合计、预算四项数值;status=20 å·²æ‹¨ä»˜ æ—¶å‡­è¯é™„ä»¶å¿…å¡«(前端先行拦截,后端 validateVoucherRequired å…œåº•);仅待拨付状态可改可删,编辑时把本行金额加回可用余额展示避免误解", "upstream": ["课题申报书(必须已立项)"], "downstream": ["拨付凭证(storage_attachment·recordType=research_fund_record)", "财务(二期预期待对接·一期不接)"], "keywords": "经费拨付 æ‹¨æ¬¾ æ‹¨ä»˜è®°å½• æ‰¹æ¬¡ æ‹¨ä»˜é‡‘额 å‡­è¯ å¾…拨付 å·²æ‹¨ä»˜ æ”¶æ¬¾å•位 èµ„助金额 é¢„ç®— è¶…额"}
    {"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>
<script type="application/json" id="data-flows">{
@@ -622,7 +623,7 @@
       {"stage": "建档", "action": "审批通过钩子自动建会员:①生成会员编号 HY+yyyyMMdd+6位流水 â‘¡AdminUserApi.createUser å»ºç³»ç»Ÿè´¦å·ï¼ˆç”¨æˆ·åä¸Žåˆå§‹å¯†ç =联系电话、昵称=申请人姓名、部门=学会会员)③会员落库(入会日期=当天、状态 1 æ­£å¸¸ã€applicationId å›žå¡«æ¥æºç”³è¯·å•);手机号重复会明确报错", "roles": ["系统自动"]},
       {"stage": "管理与检索", "action": "会员列表支持手工新增/编辑/删除(等级与类型校验:单位会员不可选『理事长』);会员通讯录为只读页,按地区编码与行业检索", "roles": ["会员管理员", "全体可查通讯录"]}]},
    {"id": "research", "name": "课题管理流程", "mermaid": "mermaid/14-research-flow.mmd", "color": "#e070a8",
     "summary": "独立科研模块(yudao-module-research):学会发布课题指南(重点/一般分类)→ ä¼šå‘˜åœ¨çº¿æäº¤è¯¾é¢˜ç”³æŠ¥ä¹¦ â†’ æäº¤ BPM ä¸‰çº§å®¡æ‰¹ï¼ˆç§˜ä¹¦å¤„形式审查 â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ï¼‰ï¼Œä¸“家评分以结构化明细子表记录并由后端自动汇总加权均分、排名与推荐结论 â†’ å®¡æ‰¹é€šè¿‡ç”Ÿæˆç«‹é¡¹ç¼–号并发布系统公告 â†’ æŒ‰æ‰¹æ¬¡ç™»è®°ç»è´¹æ‹¨ä»˜è®°å½•(含凭证附件)。院级二期再补结项验收与成果归档。",
     "summary": "独立科研模块(yudao-module-research):学会发布课题指南(重点/一般分类)→ ä¼šå‘˜åœ¨çº¿æäº¤è¯¾é¢˜ç”³æŠ¥ä¹¦ â†’ æäº¤ BPM ä¸‰çº§å®¡æ‰¹ï¼ˆç§˜ä¹¦å¤„形式审查 â†’ ä¸“家评审 â†’ å­¦ä¼šç«‹é¡¹å®¡æ‰¹ï¼‰ï¼Œä¸“家评分以结构化明细子表记录并由后端自动汇总加权均分、排名与推荐结论 â†’ å®¡æ‰¹é€šè¿‡ç”Ÿæˆç«‹é¡¹ç¼–号并发布系统公告 â†’ æŒ‰æ‰¹æ¬¡ç™»è®°ç»è´¹æ‹¨ä»˜ï¼ˆå‡­è¯å¿…填),每条拨款记录自动生成并提交一张 1:1 çš„财务报销单(finance_reimbursement),走 BPM ä¸¤çº§å®¡æ‰¹åŽç”±ç›‘听器把结果回写台账。院级二期再补结项验收与成果归档。",
     "steps": [
       {"stage": "发布指南", "action": "新建指南选类别 1重点课题 / 2一般课题,填申报起止时间与指南正文;编号 GL+yyyyMMdd+6位流水(Redis æ¯æ—¥é‡ç½®ï¼‰ï¼›status: 0草稿/20已发布/30已截止。仅草稿可改可删,发布后即冻结;已产生申报单的指南禁止删除(提示该指南下已有 N å¼ ç”³æŠ¥å•)", "roles": ["学会秘书处", "课题管理员"]},
       {"stage": "在线申报", "action": "从已发布且在申报期内的指南新建申报书;选会员(ums_member)自动带出姓名/工作单位/职称/联系电话/邮箱,可改;填课题名称、课题组成员、申请经费、申报书正文;编号 SB+yyyyMMdd+6位流水;类别冗余自指南,不信任前端传值;status: 0草稿", "roles": ["课题申报人(会员)", "课题管理员"]},
@@ -630,7 +631,7 @@
       {"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)」的申报单可登记拨款记录(批次名称/拨付金额/拨付日期/收款单位/状态/备注/凭证附件);金额校验:已登记金额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出该单申请经费,超出时提示报出本次、已登记、合计、预算四项数值;status: 0待拨付/20已拨付,凭证明细走 system é™„件中心 recordType=research_fund_record,状态改为已拨付时凭证必填;仅待拨付状态可改可删,编辑时会把本行金额加回余额显示", "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": "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": [
@@ -1010,7 +1011,7 @@
  MES -->|退货质检入库回调| AS[售后工单维修退货]
  CRM -->|客户主数据| AS
  HRM[HRM å‘˜å·¥å…¥è½¬è°ƒç¦»äº¤æŽ¥è€ƒå‹¤è–ªé…¬] -.->|expandUserIds æ•°æ®å¯è§æ€§æ‰©å±•·接口已就绪待各模块接入| CRM
  BPM[BPM Flowable å®¡æ‰¹å¼•擎] -.->|状态事件回调·14个监听器| ERP
  BPM[BPM Flowable å®¡æ‰¹å¼•擎] -.->|状态事件回调·15个监听器| ERP
  BPM -.-> CRM
  BPM -.-> SRM
  BPM -.-> MES
@@ -1029,7 +1030,7 @@
  STG[附件中心 storage-blob+attachment å…¨æ¨¡å—统一挂附件] --- ERP
  FIN[财务报销 ç‹¬ç«‹æ¨¡å— yudao-module-finance æŠ¥é”€å• æ˜Žç»† å‘票附件] -.->|复用 BPM å®¡æ‰¹ä¸ŽçŠ¶æ€å›žè°ƒ| BPM
  FIN -.->|发票走附件中心 recordType=finance_reimbursement| STG
  FIN -.->|独立模块·不与其它业务模块产生单据或数据关联| ERP
  FIN -.->|"对外仅提供 yudao-module-finance-api(FinanceReimbursementApi)供他模块建单"| ERP
  UMS[会员管理 ç‹¬ç«‹æ¨¡å— yudao-module-ums å…¥ä¼šç”³è¯· ä¼šå‘˜ é€šè®¯å½•] -.->|复用 BPM ä¸¤çº§å®¡æ‰¹ä¸ŽçŠ¶æ€å›žè°ƒ| BPM
  UMS -.->|证明材料走附件中心 recordType=ums_application / ums_member| STG
  UMS -.->|审批通过经 AdminUserApi.createUser å»ºç³»ç»Ÿç™»å½•账号| SYS
@@ -1038,6 +1039,8 @@
  RES -.->|附件走附件中心 recordType=research_guideline / research_application / research_fund_record| STG
  RES -->|选会员取申报人姓名 å·¥ä½œå•位 èŒç§° è”系电话| UMS
  RES -->|立项通知写 system_notice å…¬å‘Š| SYS
  RES -->|经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement æŠ¥é”€å•并提交审批·走新建的 yudao-module-finance-api| FIN
  FIN -.->|报销审批结果经 BPM äº‹ä»¶å›žè°ƒ ResearchFundStatusListener å›žå†™è¯¾é¢˜æ‹¨æ¬¾å°è´¦| RES
</pre>
<pre style="display:none" class="mmd-src" data-mmd="09-data-flow">
flowchart LR
@@ -1122,7 +1125,7 @@
  RBM -->|submit å¸¦ id ä¸Ž processDefinitionKey èµ° query| RBS
  RBS -.->|BPM ç›‘听器结果回写 é€šè¿‡20 é©³å›ž35 å–消退回0| RBM
  RBA -.->|上传与保存分离·禁在业务表存文件地址| PDFARCH
  FINNOTE[独立模块·销售采购链条任意单据均不生成报销单·报销单亦不回写任何外部单据] -.-> RBM
  FINNOTE[独立模块·销售采购链条任意单据均不生成报销单·报销单亦不回写任何外部单据·唯一例外是课题经费拨付] -.-> RBM
  subgraph S6 [独立课题管理 research]
    RGL[课题指南 research_guideline] --> RAP[课题申报书 research_application]
    RAP --> RRV[专家评审明细 research_topic_review]
@@ -1139,10 +1142,13 @@
  RRV -.->|"明细删空时主单汇总清为 null"| RAP
  RAP -->|"publishNotice ç”Ÿæˆ system_notice å…¬å‘Šå¹¶å›žå†™ noticeId·重复发布拦截"| SYS2[system_notice ç³»ç»Ÿå…¬å‘Š]
  RFR -->|"金额校验 å·²ç™»è®° + æœ¬æ¬¡ ä¸å¾—超出 budgetAmount ç”³è¯·ç»è´¹"| RAP
  RFR -.->|"凭证走附件中心 recordType=research_fund_record·表内不存文件地址"| PDFARCH
  RFR -.->|"凭证走附件中心 recordType=research_fund_record·同一批 blob åŒç»‘ finance_reimbursement·表内不存文件地址"| PDFARCH
  RAP -.->|"申报附件 recordType=research_application"| PDFARCH
  RGL -.->|"指南附件 recordType=research_guideline"| PDFARCH
  RESNOTE[独立模块·一期经费拨付只在本模块落记录·与财务模块无数据同步·二期再对接] -.-> RFR
  RFR -->|"1:1 ç”ŸæˆæŠ¥é”€å•·经 FinanceReimbursementApi æœ¬åœ° Bean å»ºå•并立即 submit·回写 reimbursement_id/reimbursement_no·台账置 15 æŠ¥é”€å®¡æ‰¹ä¸­"| RBM
  RBM -->|"报销单进入同一条 BPM æµç¨‹ finance_reimbursement_approve(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹)·businessKey=报销单 ID"| RBS
  RBS -.->|"BPM ç›‘听器 ResearchFundStatusListener å›žå†™è¯¾é¢˜å°è´¦ é€šè¿‡20 é©³å›ž30(+驳回原因/时间) æ’¤å›ž0"| RFR
  RESNOTE[一期已对接财务·课题台账为拨款台账·报销单为财务出账凭证·两边以 reimbursement_id è½¯å…³è”无外键·财务侧直接删单会留下悬空 ID ç”±ä¸šåŠ¡æŠ¥é”™å…œåº•] -.-> RFR
</pre>
<pre style="display:none" class="mmd-src" data-mmd="10-business-object">
erDiagram
@@ -1188,6 +1194,7 @@
  research_guideline ||--o{ research_application : "被申报引用"
  research_application ||--o{ research_topic_review : "专家评分明细"
  research_application ||--o{ research_fund_record : "经费拨付记录"
  research_fund_record ||--|| finance_reimbursement : "1:1 ç”Ÿæˆå‡ºè´¦æŠ¥é”€å•(软关联 reimbursement_id)"
  ums_member ||--o{ research_application : "申报人(软关联无外键)"
</pre>
<pre style="display:none" class="mmd-src" data-mmd="11-ai-business-flow">
@@ -1235,7 +1242,7 @@
</pre>
<pre style="display:none" class="mmd-src" data-mmd="12-finance-flow">
flowchart TD
  U1[报销人填报报销单·主表: æŠ¥é”€å•号(选填) æŠ¥é”€æ ‡é¢˜ æŠ¥é”€æ€»é‡‘额 ç”³è¯·æ—¥æœŸ å¤‡æ³¨] --> U2[录入报销明细 1..n è¡Œ: è´¹ç”¨ç±»åž‹ å‘票号码 å‘票金额 å‘票日期 å¼€ç¥¨æ–¹ è´¹ç”¨è¯´æ˜Ž]
  U1["报销人填报报销单·主表: æŠ¥é”€å•号(选填) æŠ¥é”€æ ‡é¢˜ æŠ¥é”€æ€»é‡‘额 ç”³è¯·æ—¥æœŸ å¤‡æ³¨"] --> U2["录入报销明细 1..n è¡Œ: è´¹ç”¨ç±»åž‹ å‘票号码 å‘票金额 å‘票日期 å¼€ç¥¨æ–¹ è´¹ç”¨è¯´æ˜Ž"]
  U2 --> U3[发票附件: å›¾ç‰‡æˆ– PDF å…ˆä¼  system storage-blob upload å– blobId]
  U3 --> S1[保存 create·主表与明细与全部 blobIds ä¸€æ¬¡æ€§æäº¤]
  S1 --> G1["报销单号:可自定义;留空(含纯空格)则由后端生成 BX + yyyyMMdd + 6位流水(Redis·每日重置)"]
@@ -1251,7 +1258,7 @@
  K3 -->|archive å½’æ¡£| K5[已归档 40·终态·写入 archiveTime]
  D1["仅草稿 0 å¯ delete·连带清理明细与发票附件"] -.-> K1
  E1["仅草稿 0 ä¸Žå·²é©³å›ž 35 å¯ update·其余状态只读"] -.-> K4
  N1["独立模块·不与其他业务模块产生单据或字段带入"] --- K1
  N1["独立模块·与销售采购链条任何单据均无关联;唯一例外是被课题经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆ(经 yudao-module-finance-api)"] --- K1
</pre>
<pre style="display:none" class="mmd-src" data-mmd="13-ums-membership-flow">
flowchart TD
@@ -1322,18 +1329,26 @@
  KA -->|驳回| K5
  K5 -->|修改后重新 submit| K2
  K4 -->|publishNotice å‘布立项通知| N1["system_notice å…¬å‘ŠÂ·æ ‡é¢˜ã€Žå…³äºŽå…¬å¸ƒç«‹é¡¹è¯¾é¢˜çš„通知』·正文含课题名 ç«‹é¡¹ç¼–号 ç±»åˆ« ç”³æŠ¥äºº å•位 èµ„助金额·回写 noticeId·重复发布被拦截"]
  K4 -->|登记拨款记录 research_fund_record| F1[填批次名称 æ‹¨ä»˜é‡‘额 æ‹¨ä»˜æ—¥æœŸ æ”¶æ¬¾å•位 æ‹¨ä»˜çŠ¶æ€ å‡­è¯é™„ä»¶]
  F1 -->|"金额校验: å·²ç™»è®°é‡‘额 + æœ¬æ¬¡é‡‘额 ä¸å¾—超出申报单申请经费·超出时报出本次 å·²ç™»è®° åˆè®¡ é¢„算四项数值"| F2[待拨付 0]
  F2 -->|"仅待拨付 0 å¯æ”¹å¯åˆ "| F2
  F2 -->|"状态改为已拨付时必须已有凭证附件"| F3[已拨付 20]
  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"] -.-> F1
  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
  RES1["一期边界: ç»è´¹æ‹¨ä»˜åªåœ¨æœ¬æ¨¡å—落拨款记录·与财务模块无数据同步(不做跨模块对接)"] -.-> F1
  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 ä¸ºè½¯å…³è”(无外键)·仅复用 BPM å®¡æ‰¹ã€system å…¬å‘Šä¸Žé™„件中心"] --- K1
  BOUND["独立模块 yudao-module-research·对 ums_member ä¸ºè½¯å…³è”(无外键)·对财务模块走 -api å­æ¨¡å—本地调用·仅复用 BPM å®¡æ‰¹ã€system å…¬å‘Šä¸Žé™„件中心"] --- K1
</pre>
<script type="module">
docs/project-business/mermaid/08-module-relation.mmd
@@ -11,7 +11,7 @@
  MES -->|退货质检入库回调| AS[售后工单维修退货]
  CRM -->|客户主数据| AS
  HRM[HRM å‘˜å·¥å…¥è½¬è°ƒç¦»äº¤æŽ¥è€ƒå‹¤è–ªé…¬] -.->|expandUserIds æ•°æ®å¯è§æ€§æ‰©å±•·接口已就绪待各模块接入| CRM
  BPM[BPM Flowable å®¡æ‰¹å¼•擎] -.->|状态事件回调·14个监听器| ERP
  BPM[BPM Flowable å®¡æ‰¹å¼•擎] -.->|状态事件回调·15个监听器| ERP
  BPM -.-> CRM
  BPM -.-> SRM
  BPM -.-> MES
@@ -30,7 +30,7 @@
  STG[附件中心 storage-blob+attachment å…¨æ¨¡å—统一挂附件] --- ERP
  FIN[财务报销 ç‹¬ç«‹æ¨¡å— yudao-module-finance æŠ¥é”€å• æ˜Žç»† å‘票附件] -.->|复用 BPM å®¡æ‰¹ä¸ŽçŠ¶æ€å›žè°ƒ| BPM
  FIN -.->|发票走附件中心 recordType=finance_reimbursement| STG
  FIN -.->|独立模块·不与其它业务模块产生单据或数据关联| ERP
  FIN -.->|"对外仅提供 yudao-module-finance-api(FinanceReimbursementApi)供他模块建单"| ERP
  UMS[会员管理 ç‹¬ç«‹æ¨¡å— yudao-module-ums å…¥ä¼šç”³è¯· ä¼šå‘˜ é€šè®¯å½•] -.->|复用 BPM ä¸¤çº§å®¡æ‰¹ä¸ŽçŠ¶æ€å›žè°ƒ| BPM
  UMS -.->|证明材料走附件中心 recordType=ums_application / ums_member| STG
  UMS -.->|审批通过经 AdminUserApi.createUser å»ºç³»ç»Ÿç™»å½•账号| SYS
@@ -39,3 +39,5 @@
  RES -.->|附件走附件中心 recordType=research_guideline / research_application / research_fund_record| STG
  RES -->|选会员取申报人姓名 å·¥ä½œå•位 èŒç§° è”系电话| UMS
  RES -->|立项通知写 system_notice å…¬å‘Š| SYS
  RES -->|经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆ finance_reimbursement æŠ¥é”€å•并提交审批·走新建的 yudao-module-finance-api| FIN
  FIN -.->|报销审批结果经 BPM äº‹ä»¶å›žè°ƒ ResearchFundStatusListener å›žå†™è¯¾é¢˜æ‹¨æ¬¾å°è´¦| RES
docs/project-business/mermaid/09-data-flow.mmd
@@ -80,7 +80,7 @@
  RBM -->|submit å¸¦ id ä¸Ž processDefinitionKey èµ° query| RBS
  RBS -.->|BPM ç›‘听器结果回写 é€šè¿‡20 é©³å›ž35 å–消退回0| RBM
  RBA -.->|上传与保存分离·禁在业务表存文件地址| PDFARCH
  FINNOTE[独立模块·销售采购链条任意单据均不生成报销单·报销单亦不回写任何外部单据] -.-> RBM
  FINNOTE[独立模块·销售采购链条任意单据均不生成报销单·报销单亦不回写任何外部单据·唯一例外是课题经费拨付] -.-> RBM
  subgraph S6 [独立课题管理 research]
    RGL[课题指南 research_guideline] --> RAP[课题申报书 research_application]
    RAP --> RRV[专家评审明细 research_topic_review]
@@ -97,7 +97,10 @@
  RRV -.->|"明细删空时主单汇总清为 null"| RAP
  RAP -->|"publishNotice ç”Ÿæˆ system_notice å…¬å‘Šå¹¶å›žå†™ noticeId·重复发布拦截"| SYS2[system_notice ç³»ç»Ÿå…¬å‘Š]
  RFR -->|"金额校验 å·²ç™»è®° + æœ¬æ¬¡ ä¸å¾—超出 budgetAmount ç”³è¯·ç»è´¹"| RAP
  RFR -.->|"凭证走附件中心 recordType=research_fund_record·表内不存文件地址"| PDFARCH
  RFR -.->|"凭证走附件中心 recordType=research_fund_record·同一批 blob åŒç»‘ finance_reimbursement·表内不存文件地址"| PDFARCH
  RAP -.->|"申报附件 recordType=research_application"| PDFARCH
  RGL -.->|"指南附件 recordType=research_guideline"| PDFARCH
  RESNOTE[独立模块·一期经费拨付只在本模块落记录·与财务模块无数据同步·二期再对接] -.-> RFR
  RFR -->|"1:1 ç”ŸæˆæŠ¥é”€å•·经 FinanceReimbursementApi æœ¬åœ° Bean å»ºå•并立即 submit·回写 reimbursement_id/reimbursement_no·台账置 15 æŠ¥é”€å®¡æ‰¹ä¸­"| RBM
  RBM -->|"报销单进入同一条 BPM æµç¨‹ finance_reimbursement_approve(财务审核 â†’ è´¢åŠ¡ä¸»ç®¡å®¡æ‰¹)·businessKey=报销单 ID"| RBS
  RBS -.->|"BPM ç›‘听器 ResearchFundStatusListener å›žå†™è¯¾é¢˜å°è´¦ é€šè¿‡20 é©³å›ž30(+驳回原因/时间) æ’¤å›ž0"| RFR
  RESNOTE[一期已对接财务·课题台账为拨款台账·报销单为财务出账凭证·两边以 reimbursement_id è½¯å…³è”无外键·财务侧直接删单会留下悬空 ID ç”±ä¸šåŠ¡æŠ¥é”™å…œåº•] -.-> RFR
docs/project-business/mermaid/10-business-object.mmd
@@ -41,4 +41,5 @@
  research_guideline ||--o{ research_application : "被申报引用"
  research_application ||--o{ research_topic_review : "专家评分明细"
  research_application ||--o{ research_fund_record : "经费拨付记录"
  research_fund_record ||--|| finance_reimbursement : "1:1 ç”Ÿæˆå‡ºè´¦æŠ¥é”€å•(软关联 reimbursement_id)"
  ums_member ||--o{ research_application : "申报人(软关联无外键)"
docs/project-business/mermaid/12-finance-flow.mmd
@@ -1,5 +1,5 @@
flowchart TD
  U1[报销人填报报销单·主表: æŠ¥é”€å•号(选填) æŠ¥é”€æ ‡é¢˜ æŠ¥é”€æ€»é‡‘额 ç”³è¯·æ—¥æœŸ å¤‡æ³¨] --> U2[录入报销明细 1..n è¡Œ: è´¹ç”¨ç±»åž‹ å‘票号码 å‘票金额 å‘票日期 å¼€ç¥¨æ–¹ è´¹ç”¨è¯´æ˜Ž]
  U1["报销人填报报销单·主表: æŠ¥é”€å•号(选填) æŠ¥é”€æ ‡é¢˜ æŠ¥é”€æ€»é‡‘额 ç”³è¯·æ—¥æœŸ å¤‡æ³¨"] --> U2["录入报销明细 1..n è¡Œ: è´¹ç”¨ç±»åž‹ å‘票号码 å‘票金额 å‘票日期 å¼€ç¥¨æ–¹ è´¹ç”¨è¯´æ˜Ž"]
  U2 --> U3[发票附件: å›¾ç‰‡æˆ– PDF å…ˆä¼  system storage-blob upload å– blobId]
  U3 --> S1[保存 create·主表与明细与全部 blobIds ä¸€æ¬¡æ€§æäº¤]
  S1 --> G1["报销单号:可自定义;留空(含纯空格)则由后端生成 BX + yyyyMMdd + 6位流水(Redis·每日重置)"]
@@ -15,4 +15,4 @@
  K3 -->|archive å½’æ¡£| K5[已归档 40·终态·写入 archiveTime]
  D1["仅草稿 0 å¯ delete·连带清理明细与发票附件"] -.-> K1
  E1["仅草稿 0 ä¸Žå·²é©³å›ž 35 å¯ update·其余状态只读"] -.-> K4
  N1["独立模块·不与其他业务模块产生单据或字段带入"] --- K1
  N1["独立模块·与销售采购链条任何单据均无关联;唯一例外是被课题经费拨付 1:1 è‡ªåŠ¨ç”Ÿæˆ(经 yudao-module-finance-api)"] --- K1
pom.xml
@@ -53,6 +53,7 @@
        <module>yudao-module-wxcp</module>
        <!-- è´¢åŠ¡ç®¡ç†æ¨¡å— -->
        <module>yudao-module-finance</module>
        <module>yudao-module-finance-api</module>
        <!-- ä¼šå‘˜ç®¡ç†æ¨¡å— -->
        <module>yudao-module-ums</module>
        <!-- è¯¾é¢˜ç®¡ç†æ¨¡å— -->
yudao-module-finance-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-finance-api</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        finance æ¨¡å—下,财务报销的 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-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.finance.api.reimbursement;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementCreateReqDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementRespDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementUpdateReqDTO;
/**
 * è´¢åŠ¡æŠ¥é”€å• API æŽ¥å£
 *
 * ä¾›å…¶ä»–模块(如课题管理经费拨付)创建并自动提交报销审批,以及修改后重新发起、删除、查询报销单。
 *
 * @author è¶…级管理员
 */
public interface FinanceReimbursementApi {
    String PREFIX = "/rpc-api/finance/reimbursement";
    /**
     * åˆ›å»ºæŠ¥é”€å•并立即提交审批(系统自动生成 1 è¡Œæ˜Žç»†ï¼‰
     *
     * @param reqDTO æŠ¥é”€å•信息
     * @return æŠ¥é”€å•编号
     */
    CommonResult<Long> createReimbursementAndSubmit(FinanceReimbursementCreateReqDTO reqDTO);
    /**
     * ä¿®æ”¹æŠ¥é”€å•并重新提交审批(用于报销被驳回后重新发起)
     *
     * @param reqDTO æŠ¥é”€å•信息(含报销单编号)
     * @return æ˜¯å¦æˆåŠŸ
     */
    CommonResult<Boolean> updateReimbursementAndResubmit(FinanceReimbursementUpdateReqDTO reqDTO);
    /**
     * åˆ é™¤æŠ¥é”€å•(只有草稿或已驳回状态才能删除)
     *
     * @param reimbursementId æŠ¥é”€å•编号
     * @return æ˜¯å¦æˆåŠŸ
     */
    CommonResult<Boolean> deleteReimbursement(Long reimbursementId);
    /**
     * èŽ·å–æŠ¥é”€å•
     *
     * @param reimbursementId æŠ¥é”€å•编号
     * @return æŠ¥é”€å•
     */
    CommonResult<FinanceReimbursementRespDTO> getReimbursement(Long reimbursementId);
}
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/dto/FinanceReimbursementCreateReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.finance.api.reimbursement.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Schema(description = "RPC æœåŠ¡ - è´¢åŠ¡æŠ¥é”€å•åˆ›å»º Request DTO")
@Data
public class FinanceReimbursementCreateReqDTO {
    @Schema(description = "报销事由/标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "课题〔XX研究〕第一期经费拨款")
    private String title;
    @Schema(description = "报销总金额(元)", requiredMode = Schema.RequiredMode.REQUIRED, example = "50000.00")
    private BigDecimal totalAmount;
    @Schema(description = "申请日期", example = "2026-12-01")
    private LocalDate applyDate;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "凭证附件 BlobId åˆ—表")
    private List<Long> blobIds;
    @Schema(description = "自动生成的唯一一行明细的费用说明", example = "课题〔XX研究〕第一期经费拨款")
    private String expenseDescription;
    @Schema(description = "申请人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long applicantId;
}
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/dto/FinanceReimbursementRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.finance.api.reimbursement.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
@Schema(description = "RPC æœåŠ¡ - è´¢åŠ¡æŠ¥é”€å• Response DTO")
@Data
public class FinanceReimbursementRespDTO {
    @Schema(description = "报销单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "报销单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "BX20261201000001")
    private String no;
    @Schema(description = "报销事由/标题")
    private String title;
    @Schema(description = "报销总金额(元)", example = "50000.00")
    private BigDecimal totalAmount;
    @Schema(description = "报销单状态:0-草稿 15-审批中 20-已通过 35-已驳回 40-已归档", example = "15")
    private Integer status;
    @Schema(description = "流程实例编号")
    private String processInstanceId;
    @Schema(description = "申请日期", example = "2026-12-01")
    private LocalDate applyDate;
    @Schema(description = "申请人用户编号", example = "1")
    private Long applicantId;
}
yudao-module-finance-api/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/dto/FinanceReimbursementUpdateReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.finance.api.reimbursement.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Schema(description = "RPC æœåŠ¡ - è´¢åŠ¡æŠ¥é”€å•ä¿®æ”¹å¹¶é‡æ–°æäº¤ Request DTO")
@Data
public class FinanceReimbursementUpdateReqDTO {
    @Schema(description = "报销单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long reimbursementId;
    @Schema(description = "报销事由/标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "课题〔XX研究〕第一期经费拨款")
    private String title;
    @Schema(description = "报销总金额(元)", requiredMode = Schema.RequiredMode.REQUIRED, example = "50000.00")
    private BigDecimal totalAmount;
    @Schema(description = "申请日期", example = "2026-12-01")
    private LocalDate applyDate;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "凭证附件 BlobId åˆ—表(全量替换)")
    private List<Long> blobIds;
    @Schema(description = "自动生成的唯一一行明细的费用说明", example = "课题〔XX研究〕第一期经费拨款")
    private String expenseDescription;
}
yudao-module-finance/pom.xml
@@ -19,6 +19,12 @@
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-finance-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-system</artifactId>
            <version>${revision}</version>
        </dependency>
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/api/reimbursement/FinanceReimbursementApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package cn.iocoder.yudao.module.finance.api.reimbursement;
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.finance.api.reimbursement.dto.FinanceReimbursementCreateReqDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementRespDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementUpdateReqDTO;
import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.vo.FinanceReimbursementSaveReqVO;
import cn.iocoder.yudao.module.finance.controller.admin.reimbursement.vo.item.FinanceReimbursementItemSaveReqVO;
import cn.iocoder.yudao.module.finance.dal.dataobject.reimbursement.FinanceReimbursementDO;
import cn.iocoder.yudao.module.finance.service.reimbursement.FinanceReimbursementService;
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 java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collections;
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.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.finance.enums.ErrorCodeConstants.*;
/**
 * è´¢åŠ¡æŠ¥é”€å• API å®žçŽ°ç±»
 *
 * ä¾›å…¶ä»–模块(课题管理经费拨付)以「自动建单 + è‡ªåŠ¨æäº¤å®¡æ‰¹ã€çš„æ–¹å¼è½ä¸€å¼ è´¢åŠ¡æŠ¥é”€å•ï¼Œ
 * æ˜Žç»†ç”±ç³»ç»Ÿå›ºå®šç”Ÿæˆ 1 è¡Œï¼ˆè´¹ç”¨ç±»åž‹=其他),发票金额取报销总金额。
 *
 * @author è¶…级管理员
 */
@RestController
@RequestMapping(FinanceReimbursementApi.PREFIX)
@Validated
public class FinanceReimbursementApiImpl implements FinanceReimbursementApi {
    /** è´¹ç”¨ç±»åž‹ï¼šå…¶ä»–(字典 finance_expense_type:1-差旅 2-办公 3-招待 4-交通 5-其他) */
    private static final Integer EXPENSE_TYPE_OTHER = 5;
    @Resource
    private FinanceReimbursementService reimbursementService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Long> createReimbursementAndSubmit(FinanceReimbursementCreateReqDTO reqDTO) {
        // 1. å…ˆè§£æžå¯ç”¨çš„æŠ¥é”€å®¡æ‰¹æµç¨‹ï¼šæ²¡æœ‰å·²å‘布流程时直接失败,避免留下无法提交的孤儿草稿单
        String processDefinitionKey = resolveApproveProcessDefinitionKey();
        // 2. åˆ›å»ºæŠ¥é”€å•(1 è¡Œæ˜Žç»†ï¼‰
        FinanceReimbursementSaveReqVO createReqVO = buildSaveReqVO(null, reqDTO.getTitle(),
                reqDTO.getTotalAmount(), reqDTO.getApplyDate(), reqDTO.getRemark(),
                reqDTO.getBlobIds(), reqDTO.getExpenseDescription());
        Long reimbursementId = reimbursementService.createReimbursement(createReqVO);
        // 3. ç«‹å³æäº¤å®¡æ‰¹
        Long applicantId = reqDTO.getApplicantId() != null ? reqDTO.getApplicantId() : getLoginUserId();
        reimbursementService.submitReimbursement(reimbursementId, processDefinitionKey, applicantId);
        return success(reimbursementId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Boolean> updateReimbursementAndResubmit(FinanceReimbursementUpdateReqDTO reqDTO) {
        // 1. å…ˆè§£æžå¯ç”¨çš„æŠ¥é”€å®¡æ‰¹æµç¨‹
        String processDefinitionKey = resolveApproveProcessDefinitionKey();
        // 2. ä¿®æ”¹æŠ¥é”€å•(明细重建为 1 è¡Œï¼‰
        FinanceReimbursementSaveReqVO updateReqVO = buildSaveReqVO(reqDTO.getReimbursementId(),
                reqDTO.getTitle(), reqDTO.getTotalAmount(), reqDTO.getApplyDate(), reqDTO.getRemark(),
                reqDTO.getBlobIds(), reqDTO.getExpenseDescription());
        reimbursementService.updateReimbursement(updateReqVO);
        // 3. é‡æ–°æäº¤å®¡æ‰¹
        reimbursementService.submitReimbursement(reqDTO.getReimbursementId(), processDefinitionKey, getLoginUserId());
        return success(true);
    }
    @Override
    public CommonResult<Boolean> deleteReimbursement(Long reimbursementId) {
        reimbursementService.deleteReimbursement(reimbursementId);
        return success(true);
    }
    @Override
    public CommonResult<FinanceReimbursementRespDTO> getReimbursement(Long reimbursementId) {
        // æŠ¥é”€å•不存在时返回 null,由调用方决定如何处理(课题侧据此容忍被直接删除的报销单)
        FinanceReimbursementDO reimbursement = reimbursementService.getReimbursement(reimbursementId);
        return success(BeanUtils.toBean(reimbursement, FinanceReimbursementRespDTO.class));
    }
    // ==================== å†…部方法 ====================
    /**
     * è§£æžæŠ¥é”€å®¡æ‰¹åˆ†ç±»ä¸‹å¯ç”¨çš„æµç¨‹å®šä¹‰ key;分类下没有已发布的流程时抛出可执行的精确提示
     */
    private String resolveApproveProcessDefinitionKey() {
        List<Map<String, Object>> definitionList = reimbursementService.getReimbursementApproveProcessDefinitionList();
        if (CollUtil.isEmpty(definitionList)) {
            throw exception(REIMBURSEMENT_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        return String.valueOf(definitionList.get(0).get("key"));
    }
    /** ç»„装报销单保存 VO:明细固定 1 è¡Œï¼Œè´¹ç”¨ç±»åž‹ã€Œå…¶ä»–」,发票金额=报销总金额 */
    private FinanceReimbursementSaveReqVO buildSaveReqVO(Long id, String title, BigDecimal totalAmount,
                                                         LocalDate applyDate, String remark, List<Long> blobIds,
                                                         String expenseDescription) {
        FinanceReimbursementItemSaveReqVO item = new FinanceReimbursementItemSaveReqVO();
        item.setExpenseType(EXPENSE_TYPE_OTHER);
        item.setInvoiceAmount(totalAmount);
        item.setInvoiceDate(applyDate);
        item.setDescription(expenseDescription);
        FinanceReimbursementSaveReqVO reqVO = new FinanceReimbursementSaveReqVO();
        reqVO.setId(id);
        reqVO.setTitle(title);
        reqVO.setTotalAmount(totalAmount);
        reqVO.setApplyDate(applyDate != null ? applyDate : LocalDate.now());
        reqVO.setRemark(remark);
        reqVO.setBlobIds(blobIds);
        reqVO.setItems(Collections.singletonList(item));
        return reqVO;
    }
}
yudao-module-finance/src/main/java/cn/iocoder/yudao/module/finance/enums/ErrorCodeConstants.java
@@ -19,7 +19,7 @@
    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, "报销审批分类下没有可用的流程模型,请先创建并发布流程");
    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, "报销单号({})中间不能包含空格,请去掉空格后重新输入,或清空该字段由系统自动生成");
yudao-module-research/pom.xml
@@ -37,6 +37,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- è´¢åŠ¡æ¨¡å— API(经费拨付自动生成并提交财务报销单) -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-finance-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/fund/vo/ResearchFundRecordRespVO.java
@@ -31,9 +31,21 @@
    @Schema(description = "收款单位/账户名", example = "XX大学")
    private String payee;
    @Schema(description = "状态:0-待拨付 20-已拨付", example = "20")
    @Schema(description = "状态:0-待拨付 15-报销审批中 20-已拨付 30-已驳回", example = "20")
    private Integer status;
    @Schema(description = "关联财务报销单 ID", example = "1")
    private Long reimbursementId;
    @Schema(description = "关联财务报销单号", example = "BX20260928000001")
    private String reimbursementNo;
    @Schema(description = "财务报销驳回原因")
    private String rejectReason;
    @Schema(description = "财务报销驳回时间")
    private LocalDateTime rejectTime;
    @Schema(description = "备注")
    private String remark;
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/controller/admin/fund/vo/ResearchFundRecordSaveReqVO.java
@@ -38,10 +38,6 @@
    @Schema(description = "收款单位/账户名", example = "XX大学")
    private String payee;
    @Schema(description = "状态:0-待拨付 20-已拨付", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
    @NotNull(message = "拨付状态不能为空")
    private Integer status;
    @Schema(description = "备注")
    private String remark;
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/dataobject/fund/ResearchFundRecordDO.java
@@ -8,6 +8,7 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * è¯¾é¢˜-经费拨付记录 DO
@@ -42,9 +43,21 @@
    /** æ”¶æ¬¾å•位/账户名 */
    private String payee;
    /** çŠ¶æ€ï¼š0-待拨付 20-已拨付 */
    /** çŠ¶æ€ï¼š0-待拨付 15-报销审批中 20-已拨付 30-已驳回 */
    private Integer status;
    /** å…³è”财务报销单 ID(finance_reimbursement.id) */
    private Long reimbursementId;
    /** å…³è”财务报销单号 */
    private String reimbursementNo;
    /** è´¢åŠ¡æŠ¥é”€é©³å›žåŽŸå› ï¼ˆBPM å›žå†™ï¼‰ */
    private String rejectReason;
    /** è´¢åŠ¡æŠ¥é”€é©³å›žæ—¶é—´ï¼ˆBPM å›žå†™ï¼‰ */
    private LocalDateTime rejectTime;
    /** å¤‡æ³¨ */
    private String remark;
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/dal/mysql/fund/ResearchFundRecordMapper.java
@@ -13,4 +13,8 @@
        return selectList(ResearchFundRecordDO::getApplicationId, applicationId);
    }
    default ResearchFundRecordDO selectByReimbursementId(Long reimbursementId) {
        return selectOne(ResearchFundRecordDO::getReimbursementId, reimbursementId);
    }
}
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/enums/ErrorCodeConstants.java
@@ -46,8 +46,10 @@
    ErrorCode FUND_NOT_EXISTS = new ErrorCode(1_030_109_300, "经费拨付记录不存在");
    ErrorCode FUND_RECORD_FAIL_STATUS = new ErrorCode(1_030_109_301, "登记失败:课题申报书({})当前状态为〔{}〕,只有已立项的课题才能登记经费拨付");
    ErrorCode FUND_AMOUNT_EXCEEDED = new ErrorCode(1_030_109_302, "登记失败:本次拨付 {} å…ƒï¼Œå·²ç™»è®° {} å…ƒï¼Œåˆè®¡ {} å…ƒï¼Œè¶…出该课题申请预算 {} å…ƒï¼Œè¯·è°ƒä½Žæœ¬æ¬¡æ‹¨ä»˜é‡‘额");
    ErrorCode FUND_VOUCHER_REQUIRED = new ErrorCode(1_030_109_303, "登记失败:状态为「已拨付」时必须上传凭证附件,请先上传凭证附件后再保存");
    ErrorCode FUND_UPDATE_FAIL_STATUS = new ErrorCode(1_030_109_304, "更新失败:经费拨付记录({})当前状态为〔{}〕,只有待拨付状态才能修改");
    ErrorCode FUND_DELETE_FAIL_STATUS = new ErrorCode(1_030_109_305, "删除失败:经费拨付记录({})当前状态为〔{}〕,只有待拨付状态才能删除");
    ErrorCode FUND_VOUCHER_REQUIRED = new ErrorCode(1_030_109_303, "登记拨款失败:本次登记的〔{}〕未上传拨付凭证附件。凭证需随自动生成的财务报销单一起送交财务审批,请先上传拨付凭证后再保存");
    ErrorCode FUND_UPDATE_FAIL_STATUS = new ErrorCode(1_030_109_304, "更新失败:经费拨付记录({})当前状态为〔{}〕,只有「待拨付」或「已驳回」状态才能修改并重新发起报销;「报销审批中」请先在财务端撤回,或等待财务审批结果");
    ErrorCode FUND_DELETE_FAIL_STATUS = new ErrorCode(1_030_109_305, "删除失败:经费拨付记录({})当前状态为〔{}〕,只有「待拨付」状态才能删除;「报销审批中」请先在财务端撤回,已驳回的记录请改为「修改并重新发起报销」");
    ErrorCode FUND_REIMBURSEMENT_NOT_EXISTS = new ErrorCode(1_030_109_306, "经费拨付记录({})关联的财务报销单(ID={})已不存在(可能已被删除),请删除本条拨款记录后重新登记");
    ErrorCode FUND_REIMBURSEMENT_STATUS_CHANGED = new ErrorCode(1_030_109_307, "重新发起报销失败:关联的财务报销单({})当前状态为〔{}〕,只有「草稿」或「已驳回」的报销单才能重新提交,请联系财务确认该单据状态");
}
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/enums/ResearchFundStatusEnum.java
@@ -14,7 +14,9 @@
public enum ResearchFundStatusEnum {
    PENDING(0, "待拨付"),
    APPROVING(15, "报销审批中"),
    PAID(20, "已拨付"),
    REJECTED(30, "已驳回"),
    ;
    private final Integer status;
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/fund/ResearchFundRecordService.java
@@ -17,4 +17,13 @@
    /** æŸè¯¾é¢˜ç”³æŠ¥ä¹¦ä¸‹çš„全部经费拨付记录(按创建时间倒序) */
    List<ResearchFundRecordDO> getFundRecordListByApplicationId(Long applicationId);
    /**
     * è´¢åŠ¡æŠ¥é”€å®¡æ‰¹ç»“æžœå›žå†™å°è´¦çŠ¶æ€
     *
     * @param reimbursementId è´¢åŠ¡æŠ¥é”€å•ç¼–å·
     * @param bpmStatus       BPM å®¡æ‰¹ç»“果(BpmTaskStatusEnum)
     * @param reason          é©³å›žåŽŸå› 
     */
    void updateFundStatusByReimbursement(Long reimbursementId, Integer bpmStatus, String reason);
}
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/fund/ResearchFundRecordServiceImpl.java
@@ -1,7 +1,13 @@
package cn.iocoder.yudao.module.research.service.fund;
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.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.finance.api.reimbursement.FinanceReimbursementApi;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementCreateReqDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementRespDTO;
import cn.iocoder.yudao.module.finance.api.reimbursement.dto.FinanceReimbursementUpdateReqDTO;
import cn.iocoder.yudao.module.research.controller.admin.fund.vo.ResearchFundRecordSaveReqVO;
import cn.iocoder.yudao.module.research.dal.dataobject.application.ResearchApplicationDO;
import cn.iocoder.yudao.module.research.dal.dataobject.fund.ResearchFundRecordDO;
@@ -10,6 +16,7 @@
import cn.iocoder.yudao.module.research.enums.ResearchApplicationStatusEnum;
import cn.iocoder.yudao.module.research.enums.ResearchFundStatusEnum;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -17,16 +24,21 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
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.research.enums.ErrorCodeConstants.*;
/**
 * ç»è´¹æ‹¨ä»˜è®°å½• Service å®žçŽ°ç±»
 *
 * ä¸€æœŸåªåœ¨æœ¬æ¨¡å—内落地拨款记录(批次/金额/日期/收款方/凭证附件),不与财务模块同步;
 * æ¯æ¡æ‹¨æ¬¾è®°å½•都会自动生成并提交一张对应的财务报销单(1:1,金额一致),
 * æŠ¥é”€å•审批结果由 {@code ResearchFundStatusListener} å›žå†™æœ¬å°è´¦çŠ¶æ€ï¼š
 * å¾…拨付(0) â†’ æŠ¥é”€å®¡æ‰¹ä¸­(15) â†’ å·²æ‹¨ä»˜(20) / å·²é©³å›ž(30);财务端撤回则退回待拨付(0)。
 * ç´¯è®¡æ‹¨ä»˜é‡‘额以申报单 budget_amount(申请预算)为上限。
 */
@Service
@@ -35,6 +47,9 @@
public class ResearchFundRecordServiceImpl implements ResearchFundRecordService {
    public static final String FUND_RECORD_TYPE = "research_fund_record";
    /** è´¢åŠ¡æŠ¥é”€å•çŠ¶æ€ï¼šè‰ç¨¿ */
    private static final Integer FINANCE_STATUS_DRAFT = 0;
    @Resource
    private ResearchFundRecordMapper fundRecordMapper;
@@ -45,22 +60,50 @@
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private FinanceReimbursementApi financeReimbursementApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createFundRecord(ResearchFundRecordSaveReqVO createReqVO) {
        ResearchApplicationDO application = validateApplicationApproved(createReqVO.getApplicationId());
        validateVoucherRequired(createReqVO);
        validateVoucherRequired(createReqVO.getBlobIds(), createReqVO.getBatchName());
        // ç´¯è®¡æ ¡éªŒï¼šæ–°ç™»è®°é‡‘额 + è¯¥ç”³æŠ¥å•已有记录金额,不得超过申请预算
        BigDecimal registered = sumAmount(fundRecordMapper.selectListByApplicationId(application.getId()), null);
        List<ResearchFundRecordDO> existList = fundRecordMapper.selectListByApplicationId(application.getId());
        BigDecimal registered = sumAmount(existList, null);
        validateAmountNotExceeded(createReqVO.getAmount(), registered, application.getBudgetAmount());
        // 1. è½è¯¾é¢˜ä¾§å°è´¦ï¼ˆçŠ¶æ€å…ˆç½®å¾…æ‹¨ä»˜ï¼ŒæŠ¥é”€æäº¤æˆåŠŸåŽæ”¹ä¸ºæŠ¥é”€å®¡æ‰¹ä¸­ï¼‰
        ResearchFundRecordDO fundRecord = BeanUtils.toBean(createReqVO, ResearchFundRecordDO.class,
                in -> in.setId(null));
        fundRecord.setStatus(ResearchFundStatusEnum.PENDING.getStatus());
        fundRecordMapper.insert(fundRecord);
        // 2. å‡­è¯é™„件同时绑定到课题台账(财务侧报销单由 finance æ¨¡å—自行绑定同一批 blob)
        storageAttachmentApi.bindAttachments("file", FUND_RECORD_TYPE,
                fundRecord.getId(), createReqVO.getBlobIds());
        // 3. è‡ªåŠ¨ç”Ÿæˆå¹¶æäº¤è´¢åŠ¡æŠ¥é”€å•
        String title = buildReimbursementTitle(application, createReqVO, existList.size() + 1);
        FinanceReimbursementCreateReqDTO reqDTO = new FinanceReimbursementCreateReqDTO();
        reqDTO.setTitle(title);
        reqDTO.setTotalAmount(createReqVO.getAmount());
        reqDTO.setApplyDate(createReqVO.getPayDate() != null ? createReqVO.getPayDate() : LocalDate.now());
        reqDTO.setRemark(createReqVO.getRemark());
        reqDTO.setBlobIds(createReqVO.getBlobIds());
        reqDTO.setExpenseDescription(title);
        reqDTO.setApplicantId(getLoginUserId());
        Long reimbursementId = financeReimbursementApi.createReimbursementAndSubmit(reqDTO).getCheckedData();
        FinanceReimbursementRespDTO reimbursement =
                financeReimbursementApi.getReimbursement(reimbursementId).getCheckedData();
        // 4. å›žå†™è´¢åŠ¡æŠ¥é”€å•ä¿¡æ¯å¹¶è¿›å…¥ã€ŒæŠ¥é”€å®¡æ‰¹ä¸­ã€
        fundRecordMapper.updateById(new ResearchFundRecordDO()
                .setId(fundRecord.getId())
                .setStatus(ResearchFundStatusEnum.APPROVING.getStatus())
                .setReimbursementId(reimbursementId)
                .setReimbursementNo(reimbursement != null ? reimbursement.getNo() : null));
        return fundRecord.getId();
    }
@@ -68,21 +111,51 @@
    @Transactional(rollbackFor = Exception.class)
    public void updateFundRecord(ResearchFundRecordSaveReqVO updateReqVO) {
        ResearchFundRecordDO exist = validateFundRecordExists(updateReqVO.getId());
        if (!ResearchFundStatusEnum.PENDING.getStatus().equals(exist.getStatus())) {
        if (!isEditable(exist.getStatus())) {
            throw exception(FUND_UPDATE_FAIL_STATUS,
                    exist.getId(), ResearchFundStatusEnum.nameOf(exist.getStatus()));
        }
        // è®°å½•归属申报单不可变更,防止把拨款挪到别的课题下
        ResearchApplicationDO application = validateApplicationApproved(exist.getApplicationId());
        validateVoucherRequired(updateReqVO);
        validateVoucherRequired(updateReqVO.getBlobIds(), updateReqVO.getBatchName());
        BigDecimal registered = sumAmount(fundRecordMapper.selectListByApplicationId(exist.getApplicationId()),
                exist.getId());
        BigDecimal registered = sumAmount(
                fundRecordMapper.selectListByApplicationId(exist.getApplicationId()), exist.getId());
        validateAmountNotExceeded(updateReqVO.getAmount(), registered, application.getBudgetAmount());
        ResearchFundRecordDO updateObj = BeanUtils.toBean(updateReqVO, ResearchFundRecordDO.class);
        updateObj.setApplicationId(exist.getApplicationId());
        fundRecordMapper.updateById(updateObj);
        // 1. è´¢åŠ¡ä¾§ä¿®æ”¹æŠ¥é”€å•å¹¶é‡æ–°æäº¤å®¡æ‰¹ï¼ˆå…ˆåšï¼Œå¤±è´¥åˆ™è¯¾é¢˜ä¾§ä¸è½ä»»ä½•å˜æ›´ï¼‰
        Long reimbursementId = exist.getReimbursementId();
        if (reimbursementId == null) {
            throw exception(FUND_REIMBURSEMENT_NOT_EXISTS, exist.getId(), "空");
        }
        FinanceReimbursementRespDTO reimbursement =
                financeReimbursementApi.getReimbursement(reimbursementId).getCheckedData();
        if (reimbursement == null) {
            throw exception(FUND_REIMBURSEMENT_NOT_EXISTS, exist.getId(), reimbursementId);
        }
        String title = buildReimbursementTitle(application, updateReqVO, null);
        FinanceReimbursementUpdateReqDTO reqDTO = new FinanceReimbursementUpdateReqDTO();
        reqDTO.setReimbursementId(reimbursementId);
        reqDTO.setTitle(title);
        reqDTO.setTotalAmount(updateReqVO.getAmount());
        reqDTO.setApplyDate(updateReqVO.getPayDate() != null ? updateReqVO.getPayDate() : LocalDate.now());
        reqDTO.setRemark(updateReqVO.getRemark());
        reqDTO.setBlobIds(updateReqVO.getBlobIds());
        reqDTO.setExpenseDescription(title);
        financeReimbursementApi.updateReimbursementAndResubmit(reqDTO).getCheckedData();
        // 2. å›žå†™è¯¾é¢˜ä¾§å°è´¦ï¼šæ¸…空驳回信息,回到「报销审批中」
        fundRecordMapper.update(null, new LambdaUpdateWrapper<ResearchFundRecordDO>()
                .eq(ResearchFundRecordDO::getId, exist.getId())
                .set(ResearchFundRecordDO::getBatchName, updateReqVO.getBatchName())
                .set(ResearchFundRecordDO::getAmount, updateReqVO.getAmount())
                .set(ResearchFundRecordDO::getPayDate, updateReqVO.getPayDate())
                .set(ResearchFundRecordDO::getPayee, updateReqVO.getPayee())
                .set(ResearchFundRecordDO::getRemark, updateReqVO.getRemark())
                .set(ResearchFundRecordDO::getStatus, ResearchFundStatusEnum.APPROVING.getStatus())
                .set(ResearchFundRecordDO::getReimbursementNo, reimbursement.getNo())
                .set(ResearchFundRecordDO::getRejectReason, "")
                .set(ResearchFundRecordDO::getRejectTime, null));
        storageAttachmentApi.updateAttachments("file", FUND_RECORD_TYPE,
                updateReqVO.getId(), updateReqVO.getBlobIds());
@@ -97,6 +170,20 @@
                    exist.getId(), ResearchFundStatusEnum.nameOf(exist.getStatus()));
        }
        // è”动清理财务侧草稿报销单(已被财务侧删除时直接跳过)
        Long reimbursementId = exist.getReimbursementId();
        if (reimbursementId != null) {
            FinanceReimbursementRespDTO reimbursement =
                    financeReimbursementApi.getReimbursement(reimbursementId).getCheckedData();
            if (reimbursement != null && reimbursement.getStatus() != null) {
                if (!FINANCE_STATUS_DRAFT.equals(reimbursement.getStatus())) {
                    throw exception(FUND_REIMBURSEMENT_STATUS_CHANGED,
                            reimbursement.getNo(), financeStatusName(reimbursement.getStatus()));
                }
                financeReimbursementApi.deleteReimbursement(reimbursementId);
            }
        }
        fundRecordMapper.deleteById(id);
        storageAttachmentApi.deleteAttachmentsByRecord(FUND_RECORD_TYPE, id);
    }
@@ -106,7 +193,39 @@
        return fundRecordMapper.selectListByApplicationId(applicationId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFundStatusByReimbursement(Long reimbursementId, Integer bpmStatus, String reason) {
        ResearchFundRecordDO fundRecord = fundRecordMapper.selectByReimbursementId(reimbursementId);
        if (fundRecord == null) {
            log.warn("[updateFundStatusByReimbursement] æœªæ‰¾åˆ°å…³è”报销单({})的经费拨付记录", reimbursementId);
            return;
        }
        boolean rejected = BpmTaskStatusEnum.REJECT.getStatus().equals(bpmStatus);
        LambdaUpdateWrapper<ResearchFundRecordDO> update = new LambdaUpdateWrapper<ResearchFundRecordDO>()
                .eq(ResearchFundRecordDO::getId, fundRecord.getId());
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmStatus)) {
            update.set(ResearchFundRecordDO::getStatus, ResearchFundStatusEnum.PAID.getStatus());
        } else if (rejected) {
            update.set(ResearchFundRecordDO::getStatus, ResearchFundStatusEnum.REJECTED.getStatus());
        } else {
            // æ’¤å›ž/取消:退回「待拨付」,供课题侧修改后重新发起报销
            update.set(ResearchFundRecordDO::getStatus, ResearchFundStatusEnum.PENDING.getStatus());
        }
        update.set(ResearchFundRecordDO::getRejectReason,
                        rejected ? StrUtil.blankToDefault(reason, "财务未填写驳回原因") : "")
                .set(ResearchFundRecordDO::getRejectTime, rejected ? LocalDateTime.now() : null);
        fundRecordMapper.update(null, update);
    }
    // =================== å†…部方法 ===================
    /** åªæœ‰ã€Œå¾…拨付」或「已驳回」的台账才能修改并重新发起报销 */
    private boolean isEditable(Integer status) {
        return ResearchFundStatusEnum.PENDING.getStatus().equals(status)
                || ResearchFundStatusEnum.REJECTED.getStatus().equals(status);
    }
    private ResearchFundRecordDO validateFundRecordExists(Long id) {
        ResearchFundRecordDO fundRecord = fundRecordMapper.selectById(id);
@@ -129,12 +248,22 @@
        return application;
    }
    /** çŠ¶æ€ä¸ºã€Œå·²æ‹¨ä»˜ã€æ—¶å¿…é¡»ä¸Šä¼ å‡­è¯é™„ä»¶ */
    private void validateVoucherRequired(ResearchFundRecordSaveReqVO reqVO) {
        if (ResearchFundStatusEnum.PAID.getStatus().equals(reqVO.getStatus())
                && CollUtil.isEmpty(reqVO.getBlobIds())) {
            throw exception(FUND_VOUCHER_REQUIRED);
    /** ç™»è®°ä¸Žä¿®æ”¹æ‹¨æ¬¾éƒ½å¿…须上传凭证:凭证会随自动生成的财务报销单送交财务审批 */
    private void validateVoucherRequired(List<Long> blobIds, String batchName) {
        if (CollUtil.isEmpty(blobIds)) {
            throw exception(FUND_VOUCHER_REQUIRED, StrUtil.isBlank(batchName) ? "本次" : batchName);
        }
    }
    /** æŠ¥é”€å•标题:课题〔课题名〕第一期经费拨款 / è¯¾é¢˜ã€”课题名〕第2次经费拨款 */
    private String buildReimbursementTitle(ResearchApplicationDO application,
                                           ResearchFundRecordSaveReqVO reqVO, Integer seq) {
        String batch = reqVO.getBatchName();
        if (StrUtil.isBlank(batch)) {
            int no = seq != null ? seq : fundRecordMapper.selectListByApplicationId(application.getId()).size() + 1;
            batch = "第" + no + "次";
        }
        return "课题〔" + application.getTitle() + "〕" + batch + "经费拨款";
    }
    /** ç´¯è®¡æµ®å‡ºæ ¡éªŒï¼šæœ¬æ¬¡ + å·²ç™»è®°ï¼ˆæŽ’除 excludeId è‡ªèº«ï¼‰ä¸å¾—超过申请预算 */
@@ -159,4 +288,19 @@
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    /** è´¢åŠ¡æŠ¥é”€å•çŠ¶æ€ä¸­æ–‡åï¼ˆfinance æ¨¡å—未对外暴露状态枚举,此处按状态值转可读文案) */
    private String financeStatusName(Integer status) {
        if (status == null) {
            return "未知";
        }
        return switch (status) {
            case 0 -> "草稿";
            case 15 -> "审批中";
            case 20 -> "已通过";
            case 35 -> "已驳回";
            case 40 -> "已归档";
            default -> String.valueOf(status);
        };
    }
}
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/fund/listener/ResearchFundStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.research.service.fund.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.research.service.fund.ResearchFundRecordService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * ç»è´¹æ‹¨ä»˜å°è´¦çŠ¶æ€ç›‘å¬å™¨
 *
 * ç›‘听财务报销单(finance_reimbursement_approve)的审批结果,把结果回写到课题侧的经费拨付台账:
 * é€šè¿‡ â†’ å·²æ‹¨ä»˜(20);驳回 â†’ å·²é©³å›ž(30) + é©³å›žåŽŸå› /时间;撤回或取消 â†’ é€€å›žå¾…拨付(0)。
 * ä¸Ž finance æ¨¡å—自己的 FinanceReimbursementStatusListener è®¢é˜…同一流程 key,两者互不干扰。
 *
 * @author è¶…级管理员
 */
@Component
@Slf4j
public class ResearchFundStatusListener extends BpmProcessInstanceStatusEventListener {
    /** è´¢åŠ¡æŠ¥é”€å•å®¡æ‰¹æµç¨‹å®šä¹‰ Key */
    private static final String PROCESS_DEFINITION_KEY = "finance_reimbursement_approve";
    @Resource
    private ResearchFundRecordService fundRecordService;
    @Override
    protected String getProcessDefinitionKey() {
        return PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long reimbursementId;
        try {
            reimbursementId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„财务报销单编号", event.getBusinessKey());
            return;
        }
        log.info("[onEvent] æ”¶åˆ°è´¢åŠ¡æŠ¥é”€å®¡æ‰¹äº‹ä»¶ï¼ŒreimbursementId={}, status={}", reimbursementId, event.getStatus());
        try {
            fundRecordService.updateFundStatusByReimbursement(reimbursementId, event.getStatus(), event.getReason());
            log.info("[onEvent] ç»è´¹æ‹¨ä»˜å°è´¦(报销单 {}) çŠ¶æ€æ›´æ–°å®Œæˆ", reimbursementId);
        } catch (Exception e) {
            log.error("[onEvent] æ›´æ–°ç»è´¹æ‹¨ä»˜å°è´¦çŠ¶æ€å¤±è´¥ï¼ŒreimbursementId={}", reimbursementId, e);
        }
    }
}
yudao-server/src/main/resources/application-local.yaml
@@ -65,9 +65,9 @@
      primary: master
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mom-jssm?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿žæŽ¥çš„示例
          url: jdbc:mysql://36.213.150.237:10000/mom-jssm?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿žæŽ¥çš„示例
          username: root
          password: 123456
          password: xd@123456..
  # Redis é…ç½®ã€‚Redisson é»˜è®¤çš„配置足够使用,一般不需要进行调优
  data: