Commit Graph
7 Commits
Author SHA1 Message Date
lijing c084c978aa feat(通知公告): 发布表单支持模块页的富文本正文与选人控件
build / build-push (push) Successful in 2m1s
前端已把 NoticeFront 的 wangEditor 与自绘「通知对象」控件 1:1 搬进助手表单,
后端补上这两个控件所需的契约:

- ToolParam / FormField 新增:
  · pickerSources:选人弹窗的**数据源页签**(形如 {name, 模块内相对地址}),
    由 AgentOrchestrator 拼上模块前缀后下发(沿用「只写相对路径」的铁律);
  · richTextUploadPath / richTextPreviewPath:正文插图的上传与预览地址。
- AgentOrchestrator:把 resolveUploadPath 抽出通用形态 resolveModulePath(幂等、只接受
  模块内相对路径);buildForm 为 widget=userpicker 下发 pickerSources;
  optionCall 强制改写 widget 的白名单放行 userpicker(否则会被降级成 select)。
- NoticeTools:notify.publish 与 notify.editNotice 的 receiveList 改为 userpicker,
  两个数据源为「基础平台」/api/base/GetSchoolAll 与「自定义组」
  /api/contacts/GetGroupTree?type=1;content 走 richtext。

测试:NotifyIntentFormTest / IntentDirectFormTest 补 pickerSources 断言
(**只写相对路径**,不带前缀 —— 前缀拼错是本项目反复出过的坑),
ModuleFormParityTest 补 userpicker 取值口径断言(字符串原样透传,不得包成数组)。
mvn -o test:Tests run: 135, Failures: 0, Errors: 0,BUILD SUCCESS。
2026-10-14 15:19:24 +08:00
lijing 9410599008 feat(通知公告): 接入 NoticeFront(模块 notice、工具前缀 notify.*)
build / build-push (push) Successful in 1m7s
★「通知公告」与既有「校园公告」是两个应用,不是同一个:
  · 校园公告 CampusAnnouncement → key=campusnotice、前缀 /campusnotice、按班级下发、带审批流/草稿箱(既有 notice.*)
  · 通知公告 NoticeFront       → key=notice、前缀 /notice、按用户下发,无审批/草稿/推送范围(本次新增 notify.*)
  两者路由与页面名几乎同名(/list、/list/new、已收到/已发送),极易拿错源码,故工具前缀避开已占用的 notice.*。

新增 NoticeTools(9 个工具):收件箱/详情/一键已读、发布/编辑/我发出的/阅读情况、发文部门字典、组织树。
发布表单逐项对齐 NoticeFront/src/views/notice/NewNotice.vue#doAdd:
  title / content / deptId / receiveList / fileIds / timingSendTime
(smsFlag 在页面里没有对应控件、恒为 0,故不放出;receiveList 的「全部用户」哨兵是 all。)

配套:ToolRegistry 注册、RolePolicy 把发布闭环与两个字典工具列为教职工专属、
application.yml 补地址 http://base.yunhaixingtong.com/notice 与默认落地页 /
(AgentSettings 出厂默认表也要补 notice —— 契约测试用的是 new AgentSettings(),不读 yml)。

fix(ToolExecutor): 已按 joinSeparator 拼成逗号串的字段不再被包回数组
  normalizeJsonArgs 的第二个循环「数组参数给了标量 → 包成单元素数组」会作用到
  刚拼好的 receiveList/fileIds 上,把 101,102 变成 [101,102],
  而模块侧 NoticeRequest.receiveList 是 String ⇒ 直接判非法。
  此前一直没暴露:本地 mock 的提交体是原样 JSON.stringify(args)(本来就是数组),
  看不出差异,只有真实后端才走到这一步。现在 join 过的字段直接跳过该循环。

测试:ModuleFormParityTest 新增「通知公告发布字段集不与校园公告互串」+
「代取地址带 /notice 前缀」+「提交体是逗号串、哨兵互斥、提交路径带前缀」;全量 126/126 绿。
2026-10-14 12:27:31 +08:00
lijing c3477c6588 fix(专业发展): 「发布专业发展」直达表单,所属栏目不再问两遍
build / build-push (push) Successful in 1m27s
用户反馈:说「我要发布专业发展」没直接出表单,先被反问「要选哪个栏目」,
答完出来表单,「所属栏目」却还是空的要再选一次 —— 一件事问了两遍。

两处根因:
1) profession.publish 的说明只写了「调用前必须确认所属栏目 moduleId 与标题」,
   漏了公告 / 宿舍 / 活动 / 作业四个发布工具都有的那句「缺失就直接调用本工具,
   系统会弹出完整表单让用户填写,不要用文字反复追问」—— 模型读到「必须确认」
   就照字面先去口头确认了;
2) 「发布专业发展」不在裸意图直达表里,这句必然要过一轮模型。

改动:
- 工具说明按另外四个发布工具的同款句式补齐(说明词是模型唯一的施工图);
- IntentShortcut 新增「发布专业发展记录」意图(仅教职工),说「我要发布专业发展」
  直接弹表单;栏目是学校自建的、各校 id 不同,所以**不预置**,
  由用户就在表单里选 —— 这正是「直接出表单选栏目」;
- 契约测试 everyPublishToolTellsTheModelNotToKeepAskingInWords:凡是名字以
  .publish 结尾的工具,说明里必须带「不要用文字反复追问」(漏写会被测出来);
- IntentDirectFormTest 补两条:教职工直达出表单(含栏目下拉与代取地址、且不预置
  栏目)、学生落回模型。

配套(前端 86e3a10):即便真的先问了一轮再弹表单,对话里定好的值也会被对齐到
选项取值(数字 71 → "71",名称「数学」→ SUB02),表单里不再重复问。
2026-10-14 11:35:43 +08:00
lijing 42a7a8030d fix(upload): 附件上传地址统一由编排层拼模块前缀,修专业发展上传 404
build / build-push (push) Successful in 52s
用户在自己线上环境传专业发展附件时接口 404。根因:`ToolParam.upload()` 的覆盖值被当成
**完整路径**原样下发,于是 `profession.publish` 声明 `/api/file/Upload` 就打到站点根
`http://base.yunhaixingtong.com/api/file/Upload` —— 那里没有应用。
(请假那次是「前缀拿模块 key 拼错」,这次是「压根没拼」,同一个坑的两种形态。)

- `AgentOrchestrator` 新增 `resolveUploadPath(module, baseUrl, override)`:
  约定与 `ToolDef.path` 完全一致 —— **参数只写模块内相对路径**,模块前缀一律在这里拼;
  对「已经带了前缀」的写法保持幂等,调用方不必猜该写哪一种。顺带删掉只剩一处调用的
  `modulePathPrefix`,把注释合并到 `pathPrefix` 上。
- `HomeworkTools.UPLOAD_PATH` 改为模块内相对路径 `/api/file/Upload`(下发值不变:
  `/coursehomework/api/file/Upload`),与 `ProfessionTools` 统一写法。
- `ToolParam.upload()` 的文档写清约定,避免下一个人再写完整路径。

测试(`ModuleFormParityTest`,替换掉原先「测试自己重算一遍拼接逻辑」的弱断言):
- `everyPublishToolUploadPathMatchesItsModuleRealEndpoint`:逐条钉住 6 个模块的线上真实地址
  (对照各模块发布页源码:NoticeUpload.vue / AttachmentUpload.vue / RecordEdit.vue),
  其中 profession 那条正是这次线上 404 的回归用例;
- `noUploadPathIsEverEmittedWithoutItsModulePrefix`:**通用不变量** —— 遍历整个注册表,
  任何附件字段的最终地址都必须以所属模块前缀开头、不许出现裸 `/api/…` 与双斜杠,
  且 `.upload()` 里不许自带前缀。以后新增模块/附件字段会自动被覆盖。

全量 121/121 BUILD SUCCESS。
2026-10-14 09:56:21 +08:00
lijing 8cfbb949b6 feat(profession): 专业发展发布工具按模块页重写,新增「栏目动态字段」下发机制
build / build-push (push) Successful in 2m15s
对齐 ProfessionView/src/views/profession/RecordEdit.vue:

- profession.publish 字段改为 moduleId/title/grade/subject/ifOpen/remark/contents/fileIds
  (POST_FORM /api/profession/Create;附件走 /api/file/Upload;fullForm 全字段渲染)
- 新增 4 个字典工具:profession.grades / subjects / moduleFields / areaTree,
  并恢复上一轮重写时误删的 profession.myPoints / profession.pointsRank
- ToolDef:新增 extraFields* 声明(字典工具 + 参数名 + 「查询参数 → 表单字段」占位符映射 + 前置锚点)
- FormRequest:新增 ExtraFields(含 dependsOn / param / before / areaCall)
- AgentOrchestrator:抽出共用的 buildReadCall,据此下发 extraFields(栏目字段定义与区划树)
- ModuleFormParityTest:新增 4 条契约测试(字段顺序 / 提交方式 / 控件形态 / 动态字段声明)
2026-10-13 18:11:10 +08:00
lijing cb8764082d fix(form): 附件上传地址改用模块真实路由前缀,修请假佐证材料 404
build / build-push (push) Successful in 53s
用户上传请假佐证材料时请求打到 /homeleave/api/base/FileUpload,被网关直接 404。

根因:默认上传地址按 "/" + ToolDef.module + "/api/base/FileUpload" 拼 ——
把「模块 key」当成了路由前缀。而请假的 key 是 homeleave、真实前缀是
/home-leave(差一个连字符)。实测 /home-leave/... 回 302 走 CAS,
/homeleave/... 回 caddy 404。

- 新增 AgentOrchestrator.pathPrefix(module, baseUrl):从 agent.modules 的配置
  地址推出站点根下的路径前缀;未配置时退回 key,尾部斜杠会被吃掉
  (否则会拼出 //api/base/FileUpload)
- buildForm 的上传地址改用它拼接(作业仍走 uploadPathOverride,不受影响)
- 其余 4 个模块的 key 与前缀同名,行为不变(curl 实测 /campusnotice、
  /dormnotice、/familyactivity、/coursehomework/api/file/Upload 均 302 可达)
- 补 2 条契约测试锁住这个行为;并修正 ModuleFormParityTest 里被一并写错的
  homeleave 地址 —— 正是它把同一个错误复制进了测试,导致一直测不出来
2026-10-13 16:36:15 +08:00
lijing c16356db6e feat(form): 发布表单与各模块「新建发布」页逐字段对齐(含审批流与作业两步发布)
build / build-push (push) Successful in 1m2s
把 5 个模块「新建/发布」页的字段与提交体逐个核对后,补齐此前缺失或形态不符的部分:

- 部门名镜像:公告/宿舍/活动三处在模块页是「字典 id + 反查名称」一起提交。
  新增 mirrorLabelOf 机制 —— 该字段不渲染控件、不进模型 schema,提交时按源字段
  选中项的显示名填充(deptName)
- 公告审批流:新增 flow 控件(widget=flow)+ 教师组织树字典 notice.teaTree,
  提交 flow:[{name,vote,approverTeaIds}];publishType 值域修正为 direct/approve;
  存草稿改用模块自己的独立接口 notice.saveDraft(POST /api/notice/SaveDraft)
- 活动分类:category 提交的是**分类名称**(模块页 :value="String(c.v)"),
  optionsKeys 由 ("k","v") 改为 ("v","v")
- 请假附件:fileIds 在 HomeLeave 是 number[](JSON 数组),去掉逗号串拼接;
  连续请假按 continuousLeaveNum 逐天展开成 continuousLeaveTime[](服务端/浏览器同源)
- 作业发布(整块新增):homework.publish / schedule / published / drafts /
  teachingSubjects / classTree。模块没有一步发布的接口,用 ToolDef.then 声明两步提交
  (Create 落草稿 → Publish | Schedule);附件是对象数组
  attachments:[{fileId,fileUrl,fileType,fileName,sort}],上传走 file 域
  /coursehomework/api/file/Upload
- 执行器:新增 followUp 两步提交(浏览器侧同源)、数组参数给标量时包成单元素数组、
  首步响应 data 取资源 id(对象形态取 .id,避免拼出 /homeworks/{"id":1}/publish)
- 新增 ModuleFormParityTest(13 项)逐条锁住上述契约

回归:mvn clean package → Tests run: 103, Failures: 0, Errors: 0, BUILD SUCCESS
真机:headless Chrome 走「表单 → 浏览器代执行 → 模块接口」,50 项断言全绿
2026-10-13 11:29:03 +08:00