三面拆分⑩:工作组管理落员工区 —— ADR-011 欠账清账

建组/改组:逐岗位选人,留空即回落四策略。绑定先于策略,起单时快照进工作流;
实测改绑定后新起单的 work_group 快照即含新成员表,在途不受影响。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-27 21:28:30 +08:00
co-authored by Claude Fable 5
parent a93f03f464
commit 6f36f8340a
8 changed files with 350 additions and 12 deletions
+44
View File
@@ -11,8 +11,10 @@ import {
listGrantsRoute,
listLlmInstancesRoute,
listStaffRoute,
listWorkGroupsRoute,
positionIdSchema,
probeLlmInstanceRoute,
saveWorkGroupRoute,
setGrantsRoute,
staffIdSchema,
upsertLlmInstanceRoute,
@@ -97,6 +99,48 @@ export function registerGovernanceRoutes(app: Hono<AppEnv>, container: Container
return c.json({ grants: await container.org.setGrants(positionId, parsed.data.grants) });
});
/* ── 员工管理 · 工作组(ADR-011) ──────────────────────────────────── */
/**
* 全部工作组 —— 一人至多一组,按员工遍历取。
* `ownerName` 一并带出:前端要显示的是「柴浩的组」,不是一串 id。
*/
app.get(listWorkGroupsRoute.path, async (c) => {
const denied = await guardStaff(c.get('account'));
if (denied) return c.json(denied, denied.code === 'forbidden' ? 403 : 401);
const { staff } = await container.org.listStaff();
const found = await Promise.all(
staff.map(async (s) => {
const group = await container.org.workGroupOf(s.id);
return group === undefined ? undefined : { ...group, ownerName: s.name };
}),
);
return c.json({ groups: found.filter((g) => g !== undefined) });
});
/**
* 建组 / 改组 —— 按 `ownerId` 落:已有即更新(复用 id),没有即新建。
* 与 `workGroupOf` 的取组口径一致(一人一组),不给同一个人建出两个组来。
* 改组**不影响在途**:装进工作流的是起单时的快照。
*/
app.put(saveWorkGroupRoute.path, async (c) => {
const denied = await guardStaff(c.get('account'));
if (denied) return c.json(denied, denied.code === 'forbidden' ? 403 : 401);
const parsed = saveWorkGroupRoute.body.safeParse(await c.req.json());
if (!parsed.success) {
const body: ApiError = { code: 'validation-failed', message: '工作组不合 schema', detail: parsed.error.issues };
return c.json(body, 400);
}
const existing = await container.org.workGroupOf(parsed.data.ownerId);
const group = await container.org.saveWorkGroup({
...(existing === undefined ? {} : { id: existing.id }),
name: parsed.data.name,
ownerId: parsed.data.ownerId,
members: parsed.data.members,
});
return c.json({ group });
});
/* ── 系统管理 · LLM 实例 ──────────────────────────────────────────── */
app.get(listLlmInstancesRoute.path, async (c) => {
+4
View File
@@ -45,3 +45,7 @@
影响半径: contract 增 `login` / `logout` / `resolveSession` / `setPassword` 与 `IssuedSession`;
port 增 `SessionRepository` / `PasswordHasher`,`StaffRepository` 增两个口令读写方法;
下游只有 app(装配 + 鉴权中间件),depcruise 已反查确认
近变(2026-07-27):模块面**未变**。app 层新增工作组读写路由
(`GET|PUT /api/gov/work-groups`),组装既有的 `workGroupOf` / `saveWorkGroup`
—— ADR-011 落地时欠的那个治理界面就此清账,一人一组的口径由 app 层按 ownerId 保证。