@stalfh233/omc-fake-platform 0.4.73 → 0.4.75

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/form.js CHANGED
@@ -30,6 +30,7 @@ export const formRoute = (ctx, input) => {
30
30
  publishedAttributesJson: sheet.publishedAttributesJson ?? null,
31
31
  publishedActionsJson: sheet.publishedActionsJson ?? null,
32
32
  publishedHtmlJson: sheet.publishedHtmlJson ?? null,
33
+ options: sheet.options ?? null,
33
34
  binding: { fieldCodes: sheet.fieldCodes },
34
35
  };
35
36
  }
@@ -78,6 +79,7 @@ export const formRoute = (ctx, input) => {
78
79
  // customScript element when present). Default to "[]" on first
79
80
  // draft so onlinejs.compose can parse it as an array.
80
81
  draftHtmlJson: body.draftHtmlJson ?? existing.draftHtmlJson ?? "[]",
82
+ options: typeof body.options === "string" ? body.options : existing.options ?? null,
81
83
  });
82
84
  return null;
83
85
  }
@@ -91,6 +93,68 @@ export const formRoute = (ctx, input) => {
91
93
  sheet.publishedAttributesJson = sheet.draftAttributesJson ?? null;
92
94
  sheet.publishedActionsJson = body.publishedActionsJson ?? "[]";
93
95
  sheet.publishedHtmlJson = sheet.draftHtmlJson ?? "[]";
96
+ // The platform persists the whole BizFormModel on publish, options
97
+ // included; keep the authoring column so the readback can show it.
98
+ if (typeof body.options === "string")
99
+ sheet.options = body.options;
100
+ // ModelManagementFacadeImpl.publishBizForm:1022 — publish reads
101
+ // options.customButtonJson and re-adds the FORM-trigger button rows.
102
+ // A malformed value answers 50000 (fastjson "syntax error, position at
103
+ // 0, name customButtonJson") exactly like the platform.
104
+ const rawOptions = typeof body.options === "string" ? body.options : sheet.options;
105
+ if (typeof rawOptions === "string" && rawOptions !== "") {
106
+ let optionsRecord;
107
+ try {
108
+ const parsed = JSON.parse(rawOptions);
109
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
110
+ optionsRecord = parsed;
111
+ }
112
+ catch {
113
+ return businessRefusal(50000, "options 不是合法 JSON");
114
+ }
115
+ const rawButtons = optionsRecord?.customButtonJson;
116
+ if (rawButtons !== undefined && rawButtons !== null && rawButtons !== "") {
117
+ if (typeof rawButtons !== "string")
118
+ return businessRefusal(50000, "syntax error, position at 0, name customButtonJson");
119
+ let list;
120
+ try {
121
+ list = JSON.parse(rawButtons);
122
+ }
123
+ catch {
124
+ return businessRefusal(50000, "syntax error, position at 0, name customButtonJson");
125
+ }
126
+ if (!Array.isArray(list))
127
+ return businessRefusal(50000, "syntax error, position at 0, name customButtonJson");
128
+ for (const [key, button] of [...ctx.state.buttons]) {
129
+ if (button.schemaCode === sheet.sheetCode && button.triggerType === "FORM" && button.triggerCode === sheet.sheetCode)
130
+ ctx.state.buttons.delete(key);
131
+ }
132
+ list.forEach((entry, index) => {
133
+ const item = (entry ?? {});
134
+ const code = String(item.code ?? "");
135
+ if (code === "")
136
+ return;
137
+ ctx.state.buttons.set(`${sheet.sheetCode}#FORM#${sheet.sheetCode}#${code}`, {
138
+ id: `btn-${sheet.sheetCode}-${code}`,
139
+ schemaCode: sheet.sheetCode,
140
+ code,
141
+ name: String(item.name ?? code),
142
+ name_i18n: typeof item.name_i18n === "string" ? item.name_i18n : null,
143
+ triggerType: "FORM",
144
+ triggerCode: sheet.sheetCode,
145
+ useLocation: typeof item.useLocation === "string" ? item.useLocation : "FORM",
146
+ bindAction: typeof item.bindAction === "string" ? item.bindAction : undefined,
147
+ operateType: typeof item.operateType === "string" ? item.operateType : undefined,
148
+ targetCode: typeof item.targetCode === "string" ? item.targetCode : null,
149
+ targetObjCode: typeof item.targetObjCode === "string" ? item.targetObjCode : null,
150
+ showPermCode: typeof item.showPermCode === "string" ? item.showPermCode : null,
151
+ showPermType: typeof item.showPermType === "string" ? item.showPermType : null,
152
+ actionConfig: typeof item.actionConfig === "string" ? item.actionConfig : JSON.stringify(item.actionConfig ?? {}),
153
+ sortKey: typeof item.sortKey === "number" ? item.sortKey : index,
154
+ });
155
+ });
156
+ }
157
+ }
94
158
  }
95
159
  return null;
96
160
  }
@@ -67,9 +67,72 @@ function selfCheckProblems(state, model) {
67
67
  }
68
68
  return problems;
69
69
  }
70
+ const RUNTIME_EXECUTE_PREFIX = "/api/api/launcher/domain_service_runtime/";
71
+ /**
72
+ * Manual custom-trigger rule execution — the portal button's face (live
73
+ * 2026-10-23): POST /api/api/launcher/domain_service_runtime/<schema>/<rule>
74
+ * {schemaCode, ids, ruleCode, consolidation}. The response's `data` is the
75
+ * PRE-execution snapshot (the frontend sends reacquireDataFromDb:false), so the
76
+ * fake applies the rule's UPDATE nodes but returns the rows as they were.
77
+ */
78
+ function runtimeExecuteRule(ctx, schemaCode, ruleCode, ids) {
79
+ const rule = [...ctx.state.rules.values()].find((entry) => entry.schemaCode === schemaCode && entry.code === ruleCode);
80
+ if (!rule)
81
+ return businessRefusal(302024, "业务规则不存在");
82
+ let nodes = [];
83
+ try {
84
+ const parsed = typeof rule.node === "string" ? JSON.parse(rule.node) : rule.node;
85
+ if (Array.isArray(parsed))
86
+ nodes = parsed;
87
+ }
88
+ catch {
89
+ nodes = [];
90
+ }
91
+ const rows = ids
92
+ .map((id) => ctx.state.dataRows.get(id))
93
+ .filter((row) => row !== undefined);
94
+ const before = rows.map((row) => structuredClone(row));
95
+ for (const node of nodes) {
96
+ if (String(node.nodeType ?? "").toUpperCase() !== "UPDATE")
97
+ continue;
98
+ const actions = Array.isArray(node.dataActions) ? node.dataActions : [];
99
+ const groups = (node.filterCondition?.mainConditions ?? []);
100
+ const matches = (row) => {
101
+ if (groups.length === 0)
102
+ return true;
103
+ return groups.some((group) => (Array.isArray(group) ? group : []).every((raw) => {
104
+ const condition = (raw ?? {});
105
+ const field = String(condition.targetSchemaDataItem ?? "");
106
+ if (condition.ruleValueType !== "FIXED")
107
+ return true;
108
+ return String(row[field] ?? "") === String(condition.value ?? "");
109
+ }));
110
+ };
111
+ for (const row of rows) {
112
+ if (!matches(row))
113
+ continue;
114
+ for (const action of actions) {
115
+ if (action.ruleValueType !== "FIXED")
116
+ continue;
117
+ row[String(action.targetDataItem ?? "")] = action.value;
118
+ }
119
+ }
120
+ }
121
+ // The fake's route return value IS the envelope's `data` (errcode is added by
122
+ // the transport), so hand back the pre-execution rows exactly like the live
123
+ // `{"errcode":0,"errmsg":"Execution Completed","data":[…]}` unwraps to.
124
+ return before;
125
+ }
70
126
  export const ruleLifecycleRoute = (ctx, input) => {
71
127
  const q = input.query ?? {};
72
128
  const body = (input.body ?? {});
129
+ if (input.path.startsWith(RUNTIME_EXECUTE_PREFIX)) {
130
+ const [schemaCode, ruleCode] = input.path.slice(RUNTIME_EXECUTE_PREFIX.length).split("/").map((part) => decodeURIComponent(part));
131
+ const ids = Array.isArray(body.ids) ? body.ids.map(String) : [];
132
+ if (!schemaCode || !ruleCode)
133
+ return businessRefusal(10011, "业务模型不存在");
134
+ return runtimeExecuteRule(ctx, String(body.schemaCode ?? schemaCode), String(body.ruleCode ?? ruleCode), ids);
135
+ }
73
136
  switch (input.path) {
74
137
  case "/api/api/app/businessrule/node_run_map":
75
138
  return selfCheckProblems(ctx.state, body);
@@ -170,6 +170,60 @@ export const runtimeDataRoute = (ctx, input) => {
170
170
  },
171
171
  };
172
172
  }
173
+ case "/api/api/system/getCustomButtons": {
174
+ // SystemAdminController.getCustomButtons: SchemaCustomButtonModel with one
175
+ // TriggerInfo per default form; only triggerType=FORM buttons ride here.
176
+ const schemaCode = String(q.schemaCode ?? "");
177
+ const forms = [...ctx.state.sheets.values()].filter((sheet) => sheet.sheetCode === schemaCode);
178
+ return {
179
+ formCustomButtons: forms.map((sheet) => ({
180
+ triggerCode: sheet.sheetCode,
181
+ triggerName: sheet.sheetCode,
182
+ hasChildTable: false,
183
+ buttonModels: [...ctx.state.buttons.values()].filter((button) => button.triggerType === "FORM" && button.triggerCode === sheet.sheetCode && button.schemaCode === schemaCode),
184
+ })),
185
+ };
186
+ }
187
+ case "/api/api/runtime/form/executeCustomButton": {
188
+ // FormRuntimeController.executeCustomButton (live 2026-10-23): the portal
189
+ // posts the whole bizObject for a FORM button (not ids), and the payload
190
+ // carries the button row itself. Apply the bound rule's UPDATE nodes.
191
+ const model = (body.bizButtonModel ?? {});
192
+ const list = Array.isArray(body.bizObjectList) ? body.bizObjectList : [];
193
+ const schemaCode = String(model.schemaCode ?? "");
194
+ const ruleCode = String(model.targetCode ?? "");
195
+ const rule = [...ctx.state.rules.values()].find((entry) => entry.schemaCode === schemaCode && entry.code === ruleCode);
196
+ if (!rule)
197
+ return businessRefusal(302024, "业务规则不存在");
198
+ let nodes = [];
199
+ try {
200
+ const parsed = typeof rule.node === "string" ? JSON.parse(rule.node) : rule.node;
201
+ if (Array.isArray(parsed))
202
+ nodes = parsed;
203
+ }
204
+ catch {
205
+ nodes = [];
206
+ }
207
+ for (const entry of list) {
208
+ const data = (entry?.data ?? {});
209
+ const id = String(data.id ?? entry?.id ?? "");
210
+ const row = id !== "" ? ctx.state.dataRows.get(id) : undefined;
211
+ for (const node of nodes) {
212
+ if (String(node.nodeType ?? "").toUpperCase() !== "UPDATE")
213
+ continue;
214
+ const actions = Array.isArray(node.dataActions) ? node.dataActions : [];
215
+ for (const action of actions) {
216
+ if (action.ruleValueType !== "FIXED")
217
+ continue;
218
+ if (row)
219
+ row[String(action.targetDataItem ?? "")] = action.value;
220
+ else
221
+ data[String(action.targetDataItem ?? "")] = action.value;
222
+ }
223
+ }
224
+ }
225
+ return { errcode: 0, errmsg: "Execution Completed" };
226
+ }
173
227
  case "/api/api/runtime/form/delete": {
174
228
  // Ticket 22 source evidence (FormRuntimeController:1233): @RequestParam
175
229
  // schemaCode + objectId; validateNotEmpty(schemaCode) -> PortalException
package/dist/state.d.ts CHANGED
@@ -70,8 +70,31 @@ export interface FakeSheet {
70
70
  publishedAttributesJson?: string | null;
71
71
  publishedActionsJson?: string | null;
72
72
  publishedHtmlJson?: string | null;
73
+ /** `options` (BizForm.options): form-level JSON carrying customButtonJson,
74
+ * presetButtonManage, sheetPresetButtonManage. Live column, string value. */
75
+ options?: string | null;
73
76
  fieldCodes: string[];
74
77
  }
78
+ /** h_biz_button row (BizButtonModel). triggerType=QUERY for list buttons,
79
+ * FORM for form toolbar buttons (published from bizsheet options). */
80
+ export interface FakeButton {
81
+ id: string;
82
+ schemaCode: string;
83
+ code: string;
84
+ name: string;
85
+ name_i18n?: string | null;
86
+ triggerType: "QUERY" | "FORM";
87
+ triggerCode: string;
88
+ useLocation?: string;
89
+ bindAction?: string;
90
+ operateType?: string;
91
+ targetCode?: string | null;
92
+ targetObjCode?: string | null;
93
+ showPermCode?: string | null;
94
+ showPermType?: string | null;
95
+ actionConfig?: string;
96
+ sortKey?: number;
97
+ }
75
98
  export interface FakeRule {
76
99
  id: string;
77
100
  schemaCode: string;
@@ -265,6 +288,7 @@ export interface FakeState {
265
288
  }>;
266
289
  sheets: Map<string, FakeSheet>;
267
290
  rules: Map<string, FakeRule>;
291
+ buttons: Map<string, FakeButton>;
268
292
  /**
269
293
  * Ticket 109 platform fidelity: every businessrule create/update mints a NEW
270
294
  * record id — superseded rows stay readable via businessrule/get?id=
@@ -298,6 +322,52 @@ export interface FakeState {
298
322
  reportPages: Map<string, FakeReportPage>;
299
323
  /** R1-report: BI dashboards keyed by objectId (= the report page id). */
300
324
  biDashboards: Map<string, FakeBiDashboard>;
325
+ /** Organization writes: role groups / roles / members (recId) / scopes. */
326
+ orgGroups: Map<string, {
327
+ id: string;
328
+ name: string;
329
+ code: string | null;
330
+ parentId: string | null;
331
+ }>;
332
+ orgRoles: Map<string, {
333
+ id: string;
334
+ name: string;
335
+ code: string | null;
336
+ roleGroupId: string;
337
+ roleGroupName: string | null;
338
+ roleType: string;
339
+ corpId: string;
340
+ remarks?: string | null;
341
+ }>;
342
+ orgMembers: Map<string, {
343
+ roleId: string;
344
+ userId: string;
345
+ name: string;
346
+ unitType: string;
347
+ ouScope: Record<string, unknown>[];
348
+ }>;
349
+ /** Organization departments/users (writes added 2026-10-22). */
350
+ orgDepartments: Map<string, {
351
+ id: string;
352
+ name: string;
353
+ parentId: string | null;
354
+ managerId: string | null;
355
+ enabled: boolean;
356
+ sortKey: number | null;
357
+ remarks: string | null;
358
+ }>;
359
+ orgUsers: Map<string, {
360
+ id: string;
361
+ name: string;
362
+ username: string;
363
+ departmentId: string;
364
+ mobile: string | null;
365
+ email: string | null;
366
+ status: string;
367
+ active: boolean;
368
+ roleIds: string[];
369
+ deptIds?: string[];
370
+ }>;
301
371
  workflows: Map<string, FakeWorkflow>;
302
372
  workItems: Map<string, FakeWorkItem>;
303
373
  dataRows: Map<string, Record<string, unknown>>;
package/dist/state.js CHANGED
@@ -33,6 +33,7 @@ export function createFakeState(options = {}) {
33
33
  dictionaryClasses: new Map(),
34
34
  sheets: new Map(),
35
35
  rules: new Map(),
36
+ buttons: new Map(),
36
37
  ruleHistory: [],
37
38
  ruleSeq: 0,
38
39
  dataRules: new Map(),
@@ -50,6 +51,14 @@ export function createFakeState(options = {}) {
50
51
  listViews: new Map(),
51
52
  reportPages: new Map(),
52
53
  biDashboards: new Map(),
54
+ // Seeded with the static 默认 group the role/list face advertises; a new
55
+ // group must not reuse that id (or the delete guard mistakes the seed role
56
+ // for a member of the caller's group).
57
+ orgGroups: new Map([["grp-1", { id: "grp-1", name: "默认", code: "SYS_1000000", parentId: null }]]),
58
+ orgDepartments: new Map(),
59
+ orgUsers: new Map(),
60
+ orgRoles: new Map(),
61
+ orgMembers: new Map(),
53
62
  workflows: new Map(),
54
63
  workItems: new Map(),
55
64
  dataRows: new Map(),
package/dist/workflow.js CHANGED
@@ -349,8 +349,30 @@ export const workflowRoute = (ctx, input) => {
349
349
  { id: "grp-1", name: "默认", code: "SYS_1000000", children: [{ id: "role-lead", name: "部门负责人", code: "SYS260630023816197", roleType: "SYS", corpId: "corp-1" }] },
350
350
  { id: "role-finance", name: "财务负责人", code: "SYS260515031207258", roleType: "SYS", corpId: "corp-1" },
351
351
  ];
352
- case "/api/api/organization/department/tree":
353
- return { departmentList: [{ id: "dept-1", name: "总所", code: "1", corpId: "corp-1", parentId: null }, { id: "dept-2", name: "业务部", code: "1_1", corpId: "corp-1", parentId: "dept-1" }] };
352
+ case "/api/api/organization/department/tree": {
353
+ // Platform semantics (1.1.25): no deptIds → roots; deptIds=<id> → that
354
+ // department's WHOLE SUBTREE, not one level.
355
+ const all = [
356
+ { id: "dept-1", name: "总所", code: "1", corpId: "corp-1", parentId: null },
357
+ { id: "dept-2", name: "业务部", code: "1_1", corpId: "corp-1", parentId: "dept-1" },
358
+ ...[...ctx.state.orgDepartments.values()].map((dept) => ({ id: dept.id, name: dept.name, code: null, corpId: "corp-1", parentId: dept.parentId })),
359
+ ];
360
+ const requested = String(q.deptIds ?? "");
361
+ if (requested === "")
362
+ return { departmentList: all.filter((dept) => dept.parentId === null) };
363
+ const subtree = [];
364
+ const queue = [requested];
365
+ while (queue.length > 0) {
366
+ const current = queue.shift();
367
+ for (const dept of all) {
368
+ if (dept.parentId !== current || subtree.some((seen) => seen.id === dept.id))
369
+ continue;
370
+ subtree.push(dept);
371
+ queue.push(dept.id);
372
+ }
373
+ }
374
+ return { departmentList: subtree };
375
+ }
354
376
  case "/api/api/organization/user/search": {
355
377
  // UserController.search: @RequestBody {wd,page,size} → PageVO<UserVO>.
356
378
  const wd = String(body.wd ?? "");
@@ -364,6 +386,215 @@ export const workflowRoute = (ctx, input) => {
364
386
  const size = Number(body.size ?? 100);
365
387
  return { totalElements: users.length, totalPages: size > 0 ? Math.ceil(users.length / size) : 0, content: users.slice(page * size, page * size + size) };
366
388
  }
389
+ case "/api/api/organization/department/add": {
390
+ if (!body.name)
391
+ return businessRefusal(10005, "部门名称不能为空");
392
+ if (typeof body.deptType !== "number")
393
+ return businessRefusal(10009, "类型转换异常");
394
+ const id = `dept-${ctx.state.orgDepartments.size + 10}`;
395
+ const dept = { id, name: String(body.name), parentId: body.parentId ? String(body.parentId) : null, managerId: body.managerId ? String(body.managerId) : null, enabled: body.enabled !== false, sortKey: body.sortKey === undefined ? null : Number(body.sortKey), remarks: body.remarks ? String(body.remarks) : null };
396
+ ctx.state.orgDepartments.set(id, dept);
397
+ return dept;
398
+ }
399
+ case "/api/api/organization/department/modefied": {
400
+ const dept = ctx.state.orgDepartments.get(String(body.id ?? ""));
401
+ if (!dept)
402
+ return businessRefusal(10011, "部门不存在");
403
+ Object.assign(dept, {
404
+ ...(body.name !== undefined ? { name: String(body.name) } : {}),
405
+ ...(body.parentId !== undefined ? { parentId: body.parentId === null ? null : String(body.parentId) } : {}),
406
+ ...(body.managerId !== undefined ? { managerId: body.managerId === null ? null : String(body.managerId) } : {}),
407
+ ...(body.enabled !== undefined ? { enabled: body.enabled === true } : {}),
408
+ ...(body.sortKey !== undefined ? { sortKey: body.sortKey === null ? null : Number(body.sortKey) } : {}),
409
+ ...(body.remarks !== undefined ? { remarks: body.remarks === null ? null : String(body.remarks) } : {}),
410
+ });
411
+ return dept;
412
+ }
413
+ case "/api/api/organization/department/get_dept_info": {
414
+ const dept = ctx.state.orgDepartments.get(String(q.deptId ?? ""));
415
+ if (!dept)
416
+ return businessRefusal(10011, "部门不存在");
417
+ return dept;
418
+ }
419
+ case "/api/api/organization/department/strike_out": {
420
+ const deptId = String(q.id ?? "");
421
+ // Platform guards (live 1.1.25): children → 200009, users → 200002.
422
+ // dept-1 carries the static child dept-2 in the tree face.
423
+ if (deptId === "dept-1" || [...ctx.state.orgDepartments.values()].some((dept) => dept.parentId === deptId))
424
+ return businessRefusal(200009, "部门下面存在子部门,不能删除!");
425
+ if ([...ctx.state.orgUsers.values()].some((user) => user.departmentId === deptId))
426
+ return businessRefusal(200002, "部门下面存在用户,不能删除!");
427
+ ctx.state.orgDepartments.delete(deptId);
428
+ return true;
429
+ }
430
+ case "/api/api/organization/department/list_user": {
431
+ const deptId = String(q.deptId ?? "");
432
+ const users = [...ctx.state.orgUsers.values()].filter((user) => user.departmentId === deptId);
433
+ return users.map((user) => ({ id: user.id, name: user.name, username: user.username, mobile: user.mobile }));
434
+ }
435
+ case "/api/api/organization/user/add": {
436
+ for (const key of ["userLabelIds", "userManagerConfigList"]) {
437
+ // Platform NPEs on null (UserHelper.dealUserLabel:428) — the tool fills
438
+ // them, and the fake reproduces the failure so the guard is testable.
439
+ if (body[key] === null || body[key] === undefined)
440
+ return businessRefusal(50000, "服务异常");
441
+ }
442
+ const deptId = String(body.departmentId ?? "");
443
+ if (!ctx.state.orgDepartments.has(deptId) && deptId !== "dept-1" && deptId !== "dept-2")
444
+ return businessRefusal(201011, "用户的部门不存在!");
445
+ if (!body.username)
446
+ return businessRefusal(10005, "账号不能为空");
447
+ const id = `user-${ctx.state.orgUsers.size + 10}`;
448
+ const user = { id, name: String(body.name ?? ""), username: String(body.username), departmentId: deptId, mobile: body.mobile ? String(body.mobile) : null, email: body.email ? String(body.email) : null, status: String(body.status ?? "ENABLE"), active: body.active !== false, roleIds: Array.isArray(body.roleIds) ? body.roleIds : [] };
449
+ ctx.state.orgUsers.set(id, user);
450
+ return user;
451
+ }
452
+ case "/api/api/organization/user/info": {
453
+ const user = ctx.state.orgUsers.get(String(q.id ?? ""));
454
+ if (!user)
455
+ return businessRefusal(10011, "用户不存在");
456
+ return { ...user, deptIds: [], userLabelIds: [], userManagerConfigList: [] };
457
+ }
458
+ case "/api/api/organization/user/modify": {
459
+ const user = ctx.state.orgUsers.get(String(body.id ?? ""));
460
+ if (!user)
461
+ return businessRefusal(10011, "用户不存在");
462
+ if (!Array.isArray(body.deptIds))
463
+ return businessRefusal(50000, "服务异常");
464
+ Object.assign(user, {
465
+ ...(body.name !== undefined ? { name: String(body.name) } : {}),
466
+ ...(body.departmentId !== undefined ? { departmentId: String(body.departmentId) } : {}),
467
+ ...(body.mobile !== undefined ? { mobile: body.mobile === null ? null : String(body.mobile) } : {}),
468
+ ...(body.email !== undefined ? { email: body.email === null ? null : String(body.email) } : {}),
469
+ ...(body.status !== undefined ? { status: String(body.status) } : {}),
470
+ ...(body.active !== undefined ? { active: body.active === true } : {}),
471
+ ...(Array.isArray(body.roleIds) ? { roleIds: body.roleIds } : {}),
472
+ ...(Array.isArray(body.deptIds) ? { deptIds: body.deptIds } : {}),
473
+ });
474
+ return user;
475
+ }
476
+ case "/api/api/organization/user/strike_out": {
477
+ ctx.state.orgUsers.delete(String(q.id ?? ""));
478
+ return true;
479
+ }
480
+ case "/api/api/organization/user/departments": {
481
+ const user = ctx.state.orgUsers.get(String(q.id ?? ""));
482
+ if (!user)
483
+ return [];
484
+ const dept = ctx.state.orgDepartments.get(user.departmentId);
485
+ return [{ deptId: user.departmentId, deptName: dept?.name ?? user.departmentId, isMain: true }];
486
+ }
487
+ case "/api/api/organization/role/add_role_group": {
488
+ const id = `grp-${ctx.state.orgGroups.size + 1}`;
489
+ ctx.state.orgGroups.set(id, { id, name: String(body.roleGroupName ?? ""), code: body.code ? String(body.code) : null, parentId: body.parentId ? String(body.parentId) : null });
490
+ return { roleGroupId: id, roleGroupName: body.roleGroupName, code: ctx.state.orgGroups.get(id).code, corpId: body.corpId ?? "other" };
491
+ }
492
+ case "/api/api/organization/role/add_role": {
493
+ const groupId = String(body.roleGroupId ?? "");
494
+ if (!groupId)
495
+ return businessRefusal(10005, "角色组不能为空");
496
+ if (groupId !== "grp-1" && !ctx.state.orgGroups.has(groupId))
497
+ return businessRefusal(10011, "角色组不存在");
498
+ const id = `role-${ctx.state.orgRoles.size + 1}`;
499
+ ctx.state.orgRoles.set(id, { id, name: String(body.roleName ?? ""), code: body.code ? String(body.code) : null, roleGroupId: groupId, roleGroupName: body.roleGroupName ? String(body.roleGroupName) : null, roleType: String(body.roleType ?? "SYS"), corpId: String(body.corpId ?? "other") });
500
+ return { roleId: id, roleName: body.roleName, code: ctx.state.orgRoles.get(id).code, roleGroupId: groupId, roleGroupName: body.roleGroupName ?? null, roleType: body.roleType ?? "SYS", corpId: body.corpId ?? "other" };
501
+ }
502
+ case "/api/api/organization/role/get": {
503
+ const role = ctx.state.orgRoles.get(String(q.roleId ?? ""));
504
+ if (!role)
505
+ return businessRefusal(10011, "角色不存在");
506
+ const remarks = role.remarks ?? null;
507
+ return { roleId: role.id, roleName: role.name, name: role.name, code: role.code, roleGroupId: role.roleGroupId, groupId: role.roleGroupId, roleGroupName: role.roleGroupName, roleType: role.roleType, corpId: role.corpId, remarks };
508
+ }
509
+ case "/api/api/organization/role/modify_role": {
510
+ const role = ctx.state.orgRoles.get(String(body.roleId ?? ""));
511
+ if (!role)
512
+ return businessRefusal(10011, "角色不存在");
513
+ role.name = body.roleName === undefined ? role.name : String(body.roleName);
514
+ role.code = body.code === undefined ? role.code : String(body.code);
515
+ // role/get answers RoleModel (name/groupId/remarks), not RoleVO — keep the
516
+ // stored shape aligned so a readback never invents a field.
517
+ if (body.remarks !== undefined)
518
+ role.remarks = body.remarks === null ? null : String(body.remarks);
519
+ if (body.roleGroupId !== undefined)
520
+ role.roleGroupId = String(body.roleGroupId);
521
+ return { roleId: role.id };
522
+ }
523
+ case "/api/api/organization/role/add_role_user": {
524
+ const roleId = String(body.roleId ?? "");
525
+ if (!roleId)
526
+ return businessRefusal(10005, "角色不能为空");
527
+ const rows = Array.isArray(body.userIds) ? body.userIds : [];
528
+ for (const row of rows) {
529
+ const userId = String(row.id ?? "");
530
+ if (!userId)
531
+ continue;
532
+ ctx.state.orgMembers.set(`${roleId}#${userId}`, { roleId, userId, name: String(row.name ?? userId), unitType: String(row.unitType ?? "3"), ouScope: [] });
533
+ }
534
+ return true;
535
+ }
536
+ case "/api/api/organization/role/strike_out_role_user": {
537
+ // Body {roleId,userIds}, NOT query ids (live 1.1.25 answers 10009 when the
538
+ // body is missing).
539
+ const roleId = String(body.roleId ?? "");
540
+ const userIds = Array.isArray(body.userIds) ? body.userIds : [];
541
+ if (userIds.length === 0)
542
+ return businessRefusal(10009, "Required request body is missing");
543
+ for (const id of userIds)
544
+ ctx.state.orgMembers.delete(`${roleId}#${String(id)}`);
545
+ return { undeletableLabelUsers: [], deletedUsers: userIds.map((id) => ({ id })) };
546
+ }
547
+ case "/api/api/organization/role/strike_out_role": {
548
+ const ids = String(q.ids ?? "").split(",").filter((value) => value !== "");
549
+ for (const roleId of ids) {
550
+ if ([...ctx.state.orgMembers.values()].some((member) => member.roleId === roleId))
551
+ return businessRefusal(-1, "角色下已经包含用户数据!");
552
+ ctx.state.orgRoles.delete(roleId);
553
+ }
554
+ return ids.map((roleId) => ({ roleId }));
555
+ }
556
+ case "/api/api/organization/role/strike_out_role_group": {
557
+ const ids = String(q.ids ?? "").split(",").filter((value) => value !== "");
558
+ for (const groupId of ids) {
559
+ if ([...ctx.state.orgRoles.values()].some((role) => role.roleGroupId === groupId))
560
+ return businessRefusal(-1, "角色组下已经包含角色数据!");
561
+ ctx.state.orgGroups.delete(groupId);
562
+ }
563
+ return ids.map((roleGroupId) => ({ roleGroupId }));
564
+ }
565
+ case "/api/api/organization/user/dept_transfer": {
566
+ const sourceDept = String(body.sourceDept ?? "");
567
+ const targetDept = String(body.targetDept ?? "");
568
+ if (!sourceDept || !targetDept)
569
+ return businessRefusal(50000, "服务异常");
570
+ const ids = Array.isArray(body.userIds) ? body.userIds : [];
571
+ for (const id of ids) {
572
+ const user = ctx.state.orgUsers.get(String(id));
573
+ if (!user)
574
+ continue;
575
+ if (user.departmentId === sourceDept)
576
+ user.departmentId = targetDept;
577
+ }
578
+ return JSON.stringify(ids);
579
+ }
580
+ case "/api/api/organization/role/users": {
581
+ const roleId = String(q.roleId ?? "");
582
+ const members = [...ctx.state.orgMembers.values()].filter((member) => member.roleId === roleId);
583
+ const page = Number(q.page ?? 0);
584
+ const size = Number(q.size ?? 500);
585
+ return { totalElements: members.length, totalPages: size > 0 ? Math.ceil(members.length / size) : 0, content: members.slice(page * size, page * size + size).map((member) => ({ id: member.userId, name: member.name, unitType: member.unitType, departmentId: "dept-1" })) };
586
+ }
587
+ case "/api/api/organization/role/get_ouscope": {
588
+ const member = ctx.state.orgMembers.get(`${String(q.roleId ?? "")}#${String(q.userId ?? "")}`);
589
+ return member ? member.ouScope : [];
590
+ }
591
+ case "/api/api/organization/role/update_ouscope": {
592
+ const member = ctx.state.orgMembers.get(`${String(body.roleId ?? "")}#${String(body.userId ?? "")}`);
593
+ if (!member)
594
+ return businessRefusal(10011, "角色成员不存在");
595
+ member.ouScope = Array.isArray(body.ouScopeList) ? body.ouScopeList : [];
596
+ return true;
597
+ }
367
598
  default:
368
599
  return NOT_HANDLED;
369
600
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-fake-platform",
3
- "version": "0.4.73",
3
+ "version": "0.4.75",
4
4
  "description": "OMC 的内存假 CloudPivot 平台(wire 接缝的第二个适配器)+ HTTP 假服务器——@stalfh233/omc-cli 的测试与 bench 基准基座",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "build": "tsc --project tsconfig.build.json"
19
19
  },
20
20
  "dependencies": {
21
- "omc-core": "0.4.73"
21
+ "omc-core": "0.4.75"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=22.13"