@stalfh233/omc-cli 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +28 -2
- package/dist/lanes/organization.js +145 -25
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -749,8 +749,34 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
749
749
|
}, transport);
|
|
750
750
|
}
|
|
751
751
|
const result = { status: "ok", artifactId: id, sha256: artifact.sha256, content };
|
|
752
|
-
if (Buffer.byteLength(JSON.stringify(result), "utf8") > transport.maxOutputBytes)
|
|
753
|
-
|
|
752
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > transport.maxOutputBytes) {
|
|
753
|
+
// 2026-11-04 r70: `--full` used to fall back to the same bare `fetch`
|
|
754
|
+
// pointer it had just been asked to bypass — re-running `--full` re-hits
|
|
755
|
+
// the same ceiling, so a caller that genuinely needs the body loops
|
|
756
|
+
// forever (observed live: three consecutive `--full` calls returned the
|
|
757
|
+
// same id with bytes 90K→151K→227K of pointer metadata). Say what
|
|
758
|
+
// actually happened and give the two projections that DO work.
|
|
759
|
+
const record = content && typeof content === "object" && !Array.isArray(content) ? content : undefined;
|
|
760
|
+
const keys = record
|
|
761
|
+
? Object.entries(record)
|
|
762
|
+
.map(([key, value]) => ({ key, bytes: Buffer.byteLength(JSON.stringify(value) ?? "null", "utf8") }))
|
|
763
|
+
.sort((left, right) => right.bytes - left.bytes)
|
|
764
|
+
.slice(0, 3)
|
|
765
|
+
.map((entry) => entry.key)
|
|
766
|
+
: [];
|
|
767
|
+
return emitTransportResult({
|
|
768
|
+
status: "ok",
|
|
769
|
+
artifactId: id,
|
|
770
|
+
sha256: artifact.sha256,
|
|
771
|
+
bytes: artifact.content.length,
|
|
772
|
+
fullRejected: true,
|
|
773
|
+
reason: `整包 ${Math.round(artifact.content.length / 1024)}KB 超过本通道输出上限 ${transport.maxOutputBytes}B;--full 无法一次返回,再跑一次结果相同`,
|
|
774
|
+
...(keys.length > 0 ? { keysCommand: `omc artifact get ${id} --keys ${keys.join(",")}`, sampledKeys: keys } : {}),
|
|
775
|
+
jqCommand: `omc artifact get ${id} --jq '<path>'`,
|
|
776
|
+
raiseCeilingCommand: `omc artifact get ${id} --full --max-output-bytes ${Math.max(artifact.content.length + 1024, transport.maxOutputBytes * 2)}`,
|
|
777
|
+
hint: "二选一:用 --jq/--keys 取你真正需要的那部分(推荐),或显式抬高 --max-output-bytes",
|
|
778
|
+
}, transport);
|
|
779
|
+
}
|
|
754
780
|
return emitTransportResult(result, transport);
|
|
755
781
|
}
|
|
756
782
|
catch (error) {
|
|
@@ -74,6 +74,83 @@ function deptTypeCode(value) {
|
|
|
74
74
|
return code;
|
|
75
75
|
}
|
|
76
76
|
const orgQuery = (session) => ({ corpId: session.enterpriseId() });
|
|
77
|
+
/**
|
|
78
|
+
* Read the role tree (`/role/list?expandAll=true`) and flatten it, keeping the
|
|
79
|
+
* **id** alongside code/name.
|
|
80
|
+
*
|
|
81
|
+
* Why this exists (r70, live 1.1.25): `org.role.list`'s compact projection emits
|
|
82
|
+
* `code|name` pairs and drops the id, while `add_role` / `add_role_user` /
|
|
83
|
+
* `update_ouscope` all want the id. Worse, the platform stores a wrong
|
|
84
|
+
* `roleGroupId` verbatim (`h_org_role.groupId` is not an FK): passing the group
|
|
85
|
+
* *code* that the read face shows yields errcode 0 and an orphan role — the row
|
|
86
|
+
* exists, `role/users` finds it, `role/list` never returns it, `role/get`
|
|
87
|
+
* answers 50000. Resolving code→id here removes the trap for every caller.
|
|
88
|
+
*/
|
|
89
|
+
async function readRoleTree(session) {
|
|
90
|
+
const data = (await wireCall(session, {
|
|
91
|
+
path: ENDPOINTS.organizationRoleList,
|
|
92
|
+
query: { corpId: session.enterpriseId(), expandAll: "true" },
|
|
93
|
+
})).data;
|
|
94
|
+
const groups = [];
|
|
95
|
+
const roles = [];
|
|
96
|
+
const childKeys = ["children", "roles", "childRoles", "childList", "list"];
|
|
97
|
+
const childrenOf = (row) => {
|
|
98
|
+
for (const key of childKeys) {
|
|
99
|
+
const value = row[key];
|
|
100
|
+
if (Array.isArray(value))
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
104
|
+
};
|
|
105
|
+
const walk = (rows) => {
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
const kids = childrenOf(row);
|
|
108
|
+
// A container carries group===true, or has children and no groupId; a leaf
|
|
109
|
+
// role always carries groupId (mirrors flattenRoleTree's rule).
|
|
110
|
+
const hasGroupId = row.groupId !== undefined && row.groupId !== null;
|
|
111
|
+
const isGroup = row.group === true || (!hasGroupId && kids.length > 0);
|
|
112
|
+
if (!isGroup) {
|
|
113
|
+
const id = String(row.id ?? "");
|
|
114
|
+
if (id !== "")
|
|
115
|
+
roles.push({ id, code: String(row.code ?? ""), name: String(row.name ?? "") });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
groups.push({ id: String(row.id ?? ""), code: String(row.code ?? ""), name: String(row.name ?? "") });
|
|
119
|
+
if (kids.length > 0)
|
|
120
|
+
walk(kids);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
walk(wireRows(data));
|
|
124
|
+
return { groups, roles };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve a caller-supplied role reference (32-hex id, or the `code` / `name`
|
|
128
|
+
* that `org.role.list` actually shows) to the platform role id. A reference that
|
|
129
|
+
* matches nothing is refused with the list of real roles — never written blind.
|
|
130
|
+
*/
|
|
131
|
+
async function resolveRoleRef(session, args, tool) {
|
|
132
|
+
const requested = args.roleId ?? args.roleCode;
|
|
133
|
+
if (!requested)
|
|
134
|
+
throw new ToolInputError({ status: "error", blocker: "tool-input-invalid", tool, message: "需要 roleId 或 roleCode(org.role.list 的 roles 码表给的是 code)" });
|
|
135
|
+
// An explicit roleCode must resolve — never forward it as an id.
|
|
136
|
+
if (args.roleId !== undefined && args.roleCode === undefined && /^[0-9a-f]{32}$/i.test(requested))
|
|
137
|
+
return requested;
|
|
138
|
+
const { roles } = await readRoleTree(session);
|
|
139
|
+
const match = roles.find((role) => role.code === requested) ?? roles.find((role) => role.name === requested);
|
|
140
|
+
if (match)
|
|
141
|
+
return match.id;
|
|
142
|
+
// Not a known code/name. `roleId` may still be a valid id the tree does not
|
|
143
|
+
// surface (e.g. a role in another corp); forward it and let the platform
|
|
144
|
+
// judge, rather than breaking callers that hold an id we cannot see.
|
|
145
|
+
if (args.roleId !== undefined)
|
|
146
|
+
return requested;
|
|
147
|
+
throw new ToolInputError({
|
|
148
|
+
status: "error",
|
|
149
|
+
blocker: "tool-input-invalid",
|
|
150
|
+
tool,
|
|
151
|
+
message: `角色 ${requested} 不存在。可用角色:${roles.slice(0, 30).map((role) => `${role.code}(${role.name})`).join(";")}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
77
154
|
const roleUserId = (row) => String(row.id ?? row.userId ?? row.recId ?? "");
|
|
78
155
|
async function memberRows(session, roleId) {
|
|
79
156
|
const data = (await wireCall(session, {
|
|
@@ -226,22 +303,43 @@ export function registerOrganizationLane(tools) {
|
|
|
226
303
|
},
|
|
227
304
|
};
|
|
228
305
|
});
|
|
229
|
-
writeTool(tools, "org.role.create", LANE, SCOPES.roleCreate, "创建角色(界面「分组」;`POST organization/role/add_role`)。返回 roleId;code 创建时 null、随后补 SYS<时间戳>,要稳定编码就传 code
|
|
306
|
+
writeTool(tools, "org.role.create", LANE, SCOPES.roleCreate, "创建角色(界面「分组」;`POST organization/role/add_role`)。返回 roleId;code 创建时 null、随后补 SYS<时间戳>,要稳定编码就传 code。**所属角色组推荐用 `roleGroupCode`(如 SYS_1000000)**:`org.role.list` 的紧凑码表只给 `code|name`、不显示组 id,而平台对错的 roleGroupId **静默接受**——存进去的值不是真实组 id 时角色会变成看不见的孤儿(r70 实测:在库里、role/users 查得到,role/list 永不返回、role/get 50000)。工具会把 code 归一成真实 id,并在创建后回读可见性。", {
|
|
230
307
|
roleName: z.string().min(1),
|
|
231
|
-
|
|
308
|
+
roleGroupCode: z.string().min(1).optional().describe("所属角色组的 code(org.role.list 的 groups 码表,如 SYS_1000000)或名称。**推荐**;与 roleGroupId 二选一"),
|
|
309
|
+
roleGroupId: z.string().min(1).optional().describe("所属角色组 id(32 位 hex,只能从 artifact get <role.list 的 detailArtifactId> --jq groups 取到)。误传组 code 时工具会归一成真实 id"),
|
|
232
310
|
roleGroupName: z.string().optional(),
|
|
233
311
|
roleType: z.enum(["SYS", "CUSTOMIZE", "OTHER"]).optional().describe("默认 SYS(平台会把角色统一落成 SYS,见 RoleHelper.createRoleModel)"),
|
|
234
312
|
code: z.string().optional(),
|
|
235
313
|
remarks: z.string().optional(),
|
|
236
314
|
corpId: z.string().min(1).optional().describe("组织 corpId,默认当前登录企业;传 \"other\" 表示非组织分组角色"),
|
|
237
315
|
}, async (session, args) => {
|
|
316
|
+
if (!args.roleGroupCode && !args.roleGroupId) {
|
|
317
|
+
throw new ToolInputError({ message: "必须给 roleGroupCode(推荐)或 roleGroupId;先 org.role.list 取 groups 码表" });
|
|
318
|
+
}
|
|
319
|
+
const { groups } = await readRoleTree(session);
|
|
320
|
+
if (groups.length === 0) {
|
|
321
|
+
throw new ToolRefusalError({ status: "refused", blocker: "wire-shape", message: "org.role.list 未解析出任何角色组;先确认组织树可读,不要盲建角色" });
|
|
322
|
+
}
|
|
323
|
+
const requested = args.roleGroupId ?? args.roleGroupCode;
|
|
324
|
+
const match = groups.find((group) => group.id === requested) ??
|
|
325
|
+
groups.find((group) => group.code === requested) ??
|
|
326
|
+
groups.find((group) => group.name === requested);
|
|
327
|
+
// A code/name that resolves to nothing is a definite typo → refuse.
|
|
328
|
+
// A 32-hex id absent from the tree may be a group created moments ago in
|
|
329
|
+
// this same session; let it through and rely on the visibility backstop.
|
|
330
|
+
if (!match && args.roleGroupCode) {
|
|
331
|
+
throw new ToolInputError({
|
|
332
|
+
message: `角色组 ${requested} 不存在。可用组:${groups.map((group) => `${group.code}(id=${group.id},${group.name})`).join(";")}`,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const roleGroupId = match?.id ?? String(requested);
|
|
238
336
|
const corpId = args.corpId ?? session.enterpriseId();
|
|
239
337
|
const data = (await wireCall(session, {
|
|
240
338
|
path: ENDPOINTS.organizationRoleAdd, method: "POST",
|
|
241
339
|
query: orgQuery(session),
|
|
242
340
|
body: {
|
|
243
341
|
roleName: args.roleName,
|
|
244
|
-
roleGroupId
|
|
342
|
+
roleGroupId,
|
|
245
343
|
...(args.roleGroupName ? { roleGroupName: args.roleGroupName } : {}),
|
|
246
344
|
roleType: args.roleType ?? "SYS",
|
|
247
345
|
corpId,
|
|
@@ -252,15 +350,29 @@ export function registerOrganizationLane(tools) {
|
|
|
252
350
|
const roleId = String(data?.roleId ?? data?.id ?? "");
|
|
253
351
|
if (!roleId)
|
|
254
352
|
throw new ToolRefusalError({ status: "refused", blocker: "wire-shape", message: "平台未返回 roleId;先 org.role.list 回读确认" });
|
|
353
|
+
// Backstop against the silent-orphan trap: re-read the tree and report the
|
|
354
|
+
// truth instead of reporting success for a role nobody can see.
|
|
355
|
+
const visible = (await readRoleTree(session)).roles.some((role) => role.id === roleId);
|
|
255
356
|
const readbackCorpId = data?.corpId ?? null;
|
|
357
|
+
const warnings = [];
|
|
358
|
+
if (!visible) {
|
|
359
|
+
warnings.push(`角色 ${roleId} 创建后不在 org.role.list 任何组下(孤儿角色):groupId 很可能没落到真实组 id。请 org.role.delete 后按 roleGroupCode 重建`);
|
|
360
|
+
}
|
|
361
|
+
if (readbackCorpId !== null && String(readbackCorpId) !== corpId) {
|
|
362
|
+
warnings.push(`平台回读 corpId=${String(readbackCorpId)},与请求的 ${corpId} 不一致`);
|
|
363
|
+
}
|
|
256
364
|
return {
|
|
257
365
|
performed: { errcode: 0 },
|
|
258
366
|
readback: {
|
|
259
367
|
roleId,
|
|
260
368
|
roleName: data?.roleName ?? args.roleName,
|
|
261
369
|
code: data?.code ?? null,
|
|
370
|
+
roleGroupId,
|
|
371
|
+
roleGroupCode: match?.code ?? null,
|
|
372
|
+
roleGroupName: match?.name ?? null,
|
|
373
|
+
visibleInRoleList: visible,
|
|
262
374
|
corpId: readbackCorpId,
|
|
263
|
-
...(
|
|
375
|
+
...(warnings.length > 0 ? { warning: warnings.join(";") } : {}),
|
|
264
376
|
},
|
|
265
377
|
};
|
|
266
378
|
});
|
|
@@ -634,21 +746,23 @@ export function registerOrganizationLane(tools) {
|
|
|
634
746
|
return { userId: String(args.userId), count: departments.length, departments };
|
|
635
747
|
}),
|
|
636
748
|
}));
|
|
637
|
-
writeTool(tools, "org.role.delete", LANE, SCOPES.roleDelete, "删除角色(`GET organization/role/strike_out_role?ids=`)。平台**拒绝删除仍有成员的角色**(`-1 角色下已经包含用户数据!`,实测 1.1.25):默认先读成员数、非空即拒绝并给出人数;`force:true`
|
|
638
|
-
roleId: z.string().min(1),
|
|
749
|
+
writeTool(tools, "org.role.delete", LANE, SCOPES.roleDelete, "删除角色(`GET organization/role/strike_out_role?ids=`)。平台**拒绝删除仍有成员的角色**(`-1 角色下已经包含用户数据!`,实测 1.1.25):默认先读成员数、非空即拒绝并给出人数;`force:true` 才会先逐人移出再删。**可用 roleCode 代替 roleId**(org.role.list 的码表只有 code)。", {
|
|
750
|
+
roleId: z.string().min(1).optional().describe("角色 id(32 位 hex);与 roleCode 二选一"),
|
|
751
|
+
roleCode: z.string().min(1).optional().describe("角色 code(org.role.list 的 roles 码表,如 qc_manager)或名称"),
|
|
639
752
|
force: z.boolean().optional().describe("true = 先移出全部成员再删角色"),
|
|
640
753
|
}, async (session, args) => {
|
|
641
|
-
const
|
|
754
|
+
const roleId = await resolveRoleRef(session, { roleId: args.roleId, roleCode: args.roleCode }, "org.role.delete");
|
|
755
|
+
const rows = await memberRows(session, roleId);
|
|
642
756
|
if (rows.length > 0 && args.force !== true)
|
|
643
757
|
throw new ToolRefusalError({
|
|
644
|
-
status: "refused", blocker: "role-has-members", roleId
|
|
645
|
-
nextCommand: `omc call org.role.delete --args '${JSON.stringify({ roleId
|
|
758
|
+
status: "refused", blocker: "role-has-members", roleId, members: rows.length,
|
|
759
|
+
nextCommand: `omc call org.role.delete --args '${JSON.stringify({ roleId, force: true })}'`,
|
|
646
760
|
message: `角色下还有 ${rows.length} 名成员,平台拒绝删除(errmsg:角色下已经包含用户数据)。先 org.role.removeUsers,或传 force:true 让工具移出后再删。`,
|
|
647
761
|
});
|
|
648
762
|
if (rows.length > 0)
|
|
649
|
-
await removeMembers(session,
|
|
650
|
-
await wireCall(session, { path: ENDPOINTS.organizationRoleDelete, query: { ...orgQuery(session), ids:
|
|
651
|
-
return { performed: { errcode: 0 }, readback: { roleId
|
|
763
|
+
await removeMembers(session, roleId, rows.map(roleUserId));
|
|
764
|
+
await wireCall(session, { path: ENDPOINTS.organizationRoleDelete, query: { ...orgQuery(session), ids: roleId } });
|
|
765
|
+
return { performed: { errcode: 0 }, readback: { roleId, deleted: true, removedMembers: rows.length } };
|
|
652
766
|
});
|
|
653
767
|
writeTool(tools, "org.roleGroup.delete", LANE, SCOPES.roleGroupDelete, "删除角色组(`GET organization/role/strike_out_role_group?ids=`)。平台**拒绝删除仍含角色的组**(`-1 角色组下已经包含角色数据!`):默认先数角色、非空即拒绝;`force:true` 会逐个删角色(含其成员)再删组。", {
|
|
654
768
|
roleGroupId: z.string().min(1),
|
|
@@ -730,7 +844,8 @@ export function registerOrganizationLane(tools) {
|
|
|
730
844
|
};
|
|
731
845
|
});
|
|
732
846
|
writeTool(tools, "org.role.addUsers", LANE, SCOPES.addUsers, "加成员并(可选)一次设好管理范围(`add_role_user` + 逐人 `update_ouscope`,一个工具调用)。users[].id 必须是 recId(org.user.search 的 id);传 `scopes` 会应用到本次新增的每个人。**平台的 add_role_user 虽然收 `ouScope` 但会忽略它**(实测 1.1.25,回读仍为空),所以范围必须单独写——工具内部已按顺序完成。加完回读成员与范围。", {
|
|
733
|
-
roleId: z.string().min(1),
|
|
847
|
+
roleId: z.string().min(1).optional().describe("角色 id(32 位 hex);与 roleCode 二选一"),
|
|
848
|
+
roleCode: z.string().min(1).optional().describe("角色 code(org.role.list 的 roles 码表)或名称——省掉取 artifact 找 id"),
|
|
734
849
|
users: z.array(z.object({ id: z.string().min(1).describe("用户记录 id(recId)"), name: z.string().min(1) })).min(1),
|
|
735
850
|
scopes: z.array(z.object({
|
|
736
851
|
id: z.string().min(1).describe("部门 id"),
|
|
@@ -744,15 +859,16 @@ export function registerOrganizationLane(tools) {
|
|
|
744
859
|
const users = args.users ?? args.userIds ?? [];
|
|
745
860
|
if (users.length === 0)
|
|
746
861
|
throw new ToolInputError({ status: "error", blocker: "tool-input-invalid", tool: "org.role.addUsers", message: "需要 users(或 userIds)至少一人" });
|
|
862
|
+
const roleId = await resolveRoleRef(session, { roleId: args.roleId, roleCode: args.roleCode }, "org.role.addUsers");
|
|
747
863
|
await wireCall(session, {
|
|
748
864
|
path: ENDPOINTS.organizationRoleAddUser, method: "POST",
|
|
749
865
|
query: orgQuery(session),
|
|
750
|
-
body: { roleId
|
|
866
|
+
body: { roleId, sortKey: args.sortKey ?? 999, userIds: users.map((user) => ({ id: user.id, name: user.name, type: 3, unitType: "3" })) },
|
|
751
867
|
});
|
|
752
868
|
const readback = async () => {
|
|
753
869
|
const data = (await wireCall(session, {
|
|
754
870
|
path: ENDPOINTS.organizationRoleUsers,
|
|
755
|
-
query: { ...orgQuery(session), roleId:
|
|
871
|
+
query: { ...orgQuery(session), roleId: roleId, page: "0", size: "500" },
|
|
756
872
|
})).data;
|
|
757
873
|
return Array.isArray(data?.content) ? data.content : wireRows(data);
|
|
758
874
|
};
|
|
@@ -766,11 +882,11 @@ export function registerOrganizationLane(tools) {
|
|
|
766
882
|
for (const id of added) {
|
|
767
883
|
await wireCall(session, {
|
|
768
884
|
path: ENDPOINTS.organizationRoleUpdateOuScope, method: "POST", query: orgQuery(session),
|
|
769
|
-
body: { roleId:
|
|
885
|
+
body: { roleId: roleId, userId: id, unitType: 3, sortKey: args.sortKey ?? 999, ouScopeList },
|
|
770
886
|
});
|
|
771
887
|
const scope = wireRows((await wireCall(session, {
|
|
772
888
|
path: ENDPOINTS.organizationRoleGetOuScope,
|
|
773
|
-
query: { ...orgQuery(session), roleId:
|
|
889
|
+
query: { ...orgQuery(session), roleId: roleId, userId: id },
|
|
774
890
|
})).data);
|
|
775
891
|
scopeResults.push({ userId: id, scopes: scope.length });
|
|
776
892
|
}
|
|
@@ -778,7 +894,7 @@ export function registerOrganizationLane(tools) {
|
|
|
778
894
|
return {
|
|
779
895
|
performed: { errcode: 0 },
|
|
780
896
|
readback: {
|
|
781
|
-
roleId:
|
|
897
|
+
roleId: roleId, added,
|
|
782
898
|
...(missing.length > 0 ? { missing, hint: "这些 recId 未出现在成员列表:确认 id 是 recId、用户未离职/禁用" } : {}),
|
|
783
899
|
...(scopeResults.length > 0 ? { scopeResults } : {}),
|
|
784
900
|
// ouScope 是流程角色参与人解析的过滤维度:平台
|
|
@@ -802,12 +918,14 @@ export function registerOrganizationLane(tools) {
|
|
|
802
918
|
};
|
|
803
919
|
});
|
|
804
920
|
writeTool(tools, "org.role.removeUsers", LANE, SCOPES.removeUsers, "把成员移出角色(`POST organization/role/strike_out_role_user`)。**平台要求 JSON body `{roleId,userIds:[...]}`**——用 query 或 `ids` 会报 10009 `Required request body is missing`(实测 1.1.25)。只移出角色,不删用户。", {
|
|
805
|
-
roleId: z.string().min(1),
|
|
921
|
+
roleId: z.string().min(1).optional().describe("角色 id(32 位 hex);与 roleCode 二选一"),
|
|
922
|
+
roleCode: z.string().min(1).optional().describe("角色 code(org.role.list 的 roles 码表)或名称"),
|
|
806
923
|
ids: z.array(z.string().min(1)).min(1).describe("要移出的成员 recId 列表"),
|
|
807
924
|
}, async (session, args) => {
|
|
925
|
+
const roleId = await resolveRoleRef(session, { roleId: args.roleId, roleCode: args.roleCode }, "org.role.removeUsers");
|
|
808
926
|
await wireCall(session, {
|
|
809
927
|
path: ENDPOINTS.organizationRoleRemoveUser, method: "POST", query: orgQuery(session),
|
|
810
|
-
body: { roleId
|
|
928
|
+
body: { roleId, userIds: args.ids },
|
|
811
929
|
});
|
|
812
930
|
const data = (await wireCall(session, {
|
|
813
931
|
path: ENDPOINTS.organizationRoleUsers,
|
|
@@ -818,7 +936,8 @@ export function registerOrganizationLane(tools) {
|
|
|
818
936
|
return { performed: { errcode: 0 }, readback: { roleId: String(args.roleId), removed: args.ids.filter((id) => !present.has(id)), stillPresent: args.ids.filter((id) => present.has(id)), total: data?.totalElements ?? rows.length } };
|
|
819
937
|
});
|
|
820
938
|
writeTool(tools, "org.role.setUserScope", LANE, SCOPES.setScope, "设置成员管理范围(`POST organization/role/update_ouscope`)。平台覆盖语义、无回滚:默认读旧值合并(mode:\"merge\"),mode:\"replace\" 才整体替换。unitType 传数字 3(addUsers 里是字符串 \"3\")。节点 {id,name,type:\"1\",deptPath}。", {
|
|
821
|
-
roleId: z.string().min(1),
|
|
939
|
+
roleId: z.string().min(1).optional().describe("角色 id(32 位 hex);与 roleCode 二选一"),
|
|
940
|
+
roleCode: z.string().min(1).optional().describe("角色 code(org.role.list 的 roles 码表)或名称"),
|
|
822
941
|
userId: z.string().min(1).describe("成员 recId"),
|
|
823
942
|
scopes: z.array(z.object({
|
|
824
943
|
id: z.string().min(1).describe("部门 id"),
|
|
@@ -832,9 +951,10 @@ export function registerOrganizationLane(tools) {
|
|
|
832
951
|
mode: z.enum(["merge", "replace"]).optional().describe("默认 merge(读旧值合并);replace = 覆盖整个范围"),
|
|
833
952
|
dryRun: z.boolean().optional(),
|
|
834
953
|
}, async (session, args) => {
|
|
954
|
+
const roleId = await resolveRoleRef(session, { roleId: args.roleId, roleCode: args.roleCode }, "org.role.setUserScope");
|
|
835
955
|
const before = wireRows((await wireCall(session, {
|
|
836
956
|
path: ENDPOINTS.organizationRoleGetOuScope,
|
|
837
|
-
query: { ...orgQuery(session), roleId
|
|
957
|
+
query: { ...orgQuery(session), roleId, userId: args.userId },
|
|
838
958
|
})).data);
|
|
839
959
|
const wanted = args.scopes.map((scope) => ({ id: scope.id, name: scope.name, type: scope.type === undefined ? "1" : String(scope.type), ...(scope.deptPath ? { deptPath: scope.deptPath } : {}) }));
|
|
840
960
|
const merged = args.mode === "replace"
|
|
@@ -850,15 +970,15 @@ export function registerOrganizationLane(tools) {
|
|
|
850
970
|
await wireCall(session, {
|
|
851
971
|
path: ENDPOINTS.organizationRoleUpdateOuScope, method: "POST",
|
|
852
972
|
query: orgQuery(session),
|
|
853
|
-
body: { roleId
|
|
973
|
+
body: { roleId, userId: args.userId, unitType: 3, sortKey: 999, ouScopeList: merged },
|
|
854
974
|
});
|
|
855
975
|
const after = wireRows((await wireCall(session, {
|
|
856
976
|
path: ENDPOINTS.organizationRoleGetOuScope,
|
|
857
|
-
query: { ...orgQuery(session), roleId
|
|
977
|
+
query: { ...orgQuery(session), roleId, userId: args.userId },
|
|
858
978
|
})).data).map((row) => ({ id: row.id ?? null, name: row.name ?? null, type: row.type ?? null, deptPath: row.deptPath ?? null }));
|
|
859
979
|
return {
|
|
860
980
|
performed: { errcode: 0 },
|
|
861
|
-
readback: { roleId
|
|
981
|
+
readback: { roleId, userId: args.userId, mode: args.mode ?? "merge", before: before.length, after: after.length, scopes: after, ...(removed.length > 0 ? { removedScopeIds: removed } : {}) },
|
|
862
982
|
};
|
|
863
983
|
});
|
|
864
984
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@stalfh233/omc-fake-platform": "0.6.
|
|
55
|
+
"@stalfh233/omc-fake-platform": "0.6.2",
|
|
56
56
|
"commander": "14.0.3",
|
|
57
|
-
"omc-core": "0.6.
|
|
57
|
+
"omc-core": "0.6.2",
|
|
58
58
|
"proper-lockfile": "4.1.2",
|
|
59
59
|
"write-file-atomic": "6.0.0",
|
|
60
60
|
"zod": "^3.23.8",
|