两个模块与「校园公告」同构(发布 → 阅读情况 → 回执闭环),原先只接了「读列表 + 我提交」 一层,价值最高的 发布/盯回执/催办/统计 全空。本轮补齐。 宿舍通知(9 → 35 个工具) - 发布闭环:dorm.publish(立即发布、默认发给全部宿舍)/ dorm.editNotice / dorm.sendList(改指 /api/notice/SendList,我发布的)/ dorm.readDetail / dorm.statistics; 字典:dorm.noticeTypeDic(type=noticeType)/ dorm.deptDic(type=dept)/ dorm.formTemplates。 - 回执闭环:dorm.receipts / dorm.receiptDetail / dorm.reviewReceipt / dorm.remind / dorm.expedite / dorm.remindStudent / dorm.markRead / dorm.pull / dorm.top; 家长侧补 dorm.initializeRed(一键已读)。 - 宿舍特有的床位台账(模块自带、此前完全没接):dorm.pendingStudents → dorm.assign → dorm.removeStudent / dorm.transfer / dorm.setMonitor,以及楼栋房间增改 (dorm.addBuilding / dorm.editBuilding / dorm.addRoom / dorm.editRoom)。 家校活动(7 → 22 个工具) - 发布闭环:activity.publish / activity.edit / activity.sent / activity.readDetail / activity.statistics;字典:activity.deptDic(type=1)/ activity.categoryDic(type=2)。 - 报名闭环:activity.signups / activity.signupDetail / activity.review / activity.remind / activity.expedite / activity.pull / activity.top; 家长侧补 activity.initializeRed。 裸意图直达(IntentShortcut) - 新增 DORM_PUBLISH(仅教职工)与 ACTIVITY_PUBLISH(仅教职工),学生落回模型如实说明。 - ★ 修跨模块歧义:「发通知 / 发布通知」不带限定时校园公告与宿舍通知都成立, 已从公告短语集移除,改由模型按上下文选;只认带「公告/校园」或「宿舍」「活动」限定的说法。 角色(RolePolicy) - 发布/编辑/阅读情况/回执处理/催办/统计/字典/床位台账 → STAFF_ONLY; 家长侧(通知箱、回执提交、活动广场、报名、一键已读)保持不限。 表单下拉复用请假的 optionCall + valueKeys + dependsOn 通道(模块 /api/* 只认浏览器会话, 服务端必然 302)。发布表单的接收对象固定为「全部」(receiveType/receiveList 用 fix 写死), 避免表单里出现难用的房间/班级 id 输入框。 测试:新增 DormActivityPublishIntentTest(10);IntentDirectFormTest +4(教师得表单且不过大模型、 学生落回模型);NoticeIntentFormTest 增加「发通知不再被公告劫持」; RolePermissionTest 补新工具的可见性断言。全量 86/86 通过。 README:4.2 能力划分更新 + 新增「宿舍通知/家校活动补全了哪些能力」;5.1.0 意图对照表补三行。
This commit is contained in:
@@ -77,6 +77,8 @@ class IntentDirectFormTest {
|
||||
settings = new AgentSettings();
|
||||
settings.getModules().put("homeleave", "http://base.yunhaixingtong.com/homeleave");
|
||||
settings.getModules().put("campusnotice", "http://base.yunhaixingtong.com/campusnotice");
|
||||
settings.getModules().put("dormnotice", "http://base.yunhaixingtong.com/dormnotice");
|
||||
settings.getModules().put("familyactivity", "http://base.yunhaixingtong.com/familyactivity");
|
||||
registry = new ToolRegistry(settings);
|
||||
registry.init();
|
||||
|
||||
@@ -206,6 +208,79 @@ class IntentDirectFormTest {
|
||||
assertEquals("(模型回复)", resp.getReply());
|
||||
}
|
||||
|
||||
/* ==================== 教师:发布宿舍通知 ==================== */
|
||||
|
||||
@Test
|
||||
void teacherSayingPublishDormNoticeGetsPublishFormWithoutCallingLlm() {
|
||||
ChatResponse resp = chat(EXPLODING_LLM, "我要发布宿舍通知", caller(AgentRole.TEACHER));
|
||||
|
||||
assertNotNull(resp.getForm(), "应直接下发表单");
|
||||
assertEquals("dorm.publish", resp.getForm().getTool());
|
||||
assertTrue(resp.getForm().isWrite(), "是写操作,前端要二次确认");
|
||||
assertEquals("/list/new", resp.getForm().getLandingPath());
|
||||
|
||||
// 标题 + 正文 + 通知类型(下拉)+ 发文部门(下拉)
|
||||
assertEquals("text", field(resp, "title").getWidget());
|
||||
assertEquals("textarea", field(resp, "content").getWidget());
|
||||
|
||||
FormField noticeType = field(resp, "noticeType");
|
||||
assertEquals("select", noticeType.getWidget(), "通知类型应是下拉,不该让用户手填编号");
|
||||
assertNotNull(noticeType.getOptionCall());
|
||||
assertEquals("/dormnotice/api/dict/List?type=noticeType",
|
||||
noticeType.getOptionCall().getPathAndQuery(), "字典地址必须带 type=noticeType");
|
||||
|
||||
FormField deptId = field(resp, "deptId");
|
||||
assertEquals("select", deptId.getWidget());
|
||||
assertNotNull(deptId.getOptionCall());
|
||||
assertEquals("/dormnotice/api/dict/List?type=dept", deptId.getOptionCall().getPathAndQuery());
|
||||
assertEquals("k", deptId.getValueKey());
|
||||
assertEquals("v", deptId.getLabelKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void studentSayingPublishDormNoticeFallsThroughToModel() {
|
||||
ChatResponse resp = chat(ECHO_LLM, "我要发布宿舍通知", caller(AgentRole.STUDENT));
|
||||
assertNull(resp.getForm(), "学生不该拿到发布宿舍通知表单");
|
||||
assertEquals("(模型回复)", resp.getReply());
|
||||
}
|
||||
|
||||
/* ==================== 教师:发布家校活动 ==================== */
|
||||
|
||||
@Test
|
||||
void teacherSayingPublishActivityGetsPublishFormWithoutCallingLlm() {
|
||||
ChatResponse resp = chat(EXPLODING_LLM, "我要发布活动", caller(AgentRole.TEACHER));
|
||||
|
||||
assertNotNull(resp.getForm(), "应直接下发表单");
|
||||
assertEquals("activity.publish", resp.getForm().getTool());
|
||||
assertTrue(resp.getForm().isWrite());
|
||||
assertEquals("/list/new", resp.getForm().getLandingPath());
|
||||
|
||||
// 活动表单:标题 + 说明(多行)+ 分类(下拉)+ 主办部门(下拉)+ 地点 + 起止时间
|
||||
assertEquals("text", field(resp, "title").getWidget());
|
||||
assertEquals("textarea", field(resp, "content").getWidget());
|
||||
|
||||
FormField category = field(resp, "category");
|
||||
assertEquals("select", category.getWidget());
|
||||
assertNotNull(category.getOptionCall());
|
||||
assertEquals("/familyactivity/api/dict/List?type=2", category.getOptionCall().getPathAndQuery());
|
||||
|
||||
FormField deptId = field(resp, "deptId");
|
||||
assertEquals("select", deptId.getWidget());
|
||||
assertNotNull(deptId.getOptionCall());
|
||||
assertEquals("/familyactivity/api/dict/List?type=1", deptId.getOptionCall().getPathAndQuery());
|
||||
|
||||
assertNotNull(field(resp, "venue"));
|
||||
assertNotNull(field(resp, "startTime"));
|
||||
assertNotNull(field(resp, "endTime"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void studentSayingPublishActivityFallsThroughToModel() {
|
||||
ChatResponse resp = chat(ECHO_LLM, "我要发布活动", caller(AgentRole.STUDENT));
|
||||
assertNull(resp.getForm(), "学生不该拿到发布活动表单");
|
||||
assertEquals("(模型回复)", resp.getReply());
|
||||
}
|
||||
|
||||
/* ==================== 不该被劫持的情况 ==================== */
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user