@stalfh233/omc-cli 0.4.41 → 0.4.43
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/coverage/m1-coverage-manifest-v1.json +5 -3
- package/dist/call.js +11 -5
- package/dist/cli-contract.js +27 -2
- package/dist/cli.js +11 -0
- package/dist/form-support.js +21 -1
- package/dist/journal.js +73 -0
- package/dist/lanes/app.js +23 -3
- package/dist/lanes/businessrule.js +25 -2
- package/dist/lanes/datarule.js +24 -3
- package/dist/lanes/dictionary.js +34 -3
- package/dist/lanes/field-contract.js +1 -1
- package/dist/lanes/field.js +29 -3
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +19 -2
- package/dist/lanes/list-view.js +25 -3
- package/dist/lanes/model.js +32 -3
- package/dist/lanes/online-js-contract.js +6 -1
- package/dist/lanes/online-js-coverage.js +1 -1
- package/dist/lanes/online-js.js +79 -2
- package/dist/lanes/report.js +30 -4
- package/dist/lanes/rule-lifecycle.js +28 -3
- package/dist/lanes/workflow-contract.js +1 -1
- package/dist/lanes/workflow.js +260 -17
- package/dist/online-js-source.js +38 -1
- package/dist/recipes/online-js.js +3 -3
- package/dist/rollback-spec.js +143 -0
- package/dist/rollback.js +210 -0
- package/dist/write.js +49 -2
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +7 -4
- package/skills/omc-capabilities.md +3 -3
- package/skills/omc-write-hazards.md +1 -0
package/dist/lanes/list-view.js
CHANGED
|
@@ -263,6 +263,28 @@ export function registerListViewLane(tools) {
|
|
|
263
263
|
};
|
|
264
264
|
}),
|
|
265
265
|
}));
|
|
266
|
+
const listviewRollbackSpec = (toolName) => ({
|
|
267
|
+
target: (args) => `listview:${String(args.schemaCode ?? "")}/${String(args.code ?? args.schemaCode ?? "")}`,
|
|
268
|
+
read: async (session, args) => {
|
|
269
|
+
const code = String(args.code ?? args.schemaCode);
|
|
270
|
+
const got = await wireCall(session, { path: ENDPOINTS.listQueryGet, query: { schemaCode: String(args.schemaCode), code, clientType: "PC" } });
|
|
271
|
+
const model = (got.data ?? null);
|
|
272
|
+
return model && model.id ? model : null;
|
|
273
|
+
},
|
|
274
|
+
inverse: ({ before, args }) => {
|
|
275
|
+
const schemaCode = String(args.schemaCode);
|
|
276
|
+
const code = String(args.code ?? schemaCode);
|
|
277
|
+
if (toolName === "listview.create")
|
|
278
|
+
return before == null ? { kind: "calls", calls: [{ tool: "listview.delete", args: { schemaCode, code } }] } : { kind: "calls", calls: [], notes: ["create 前视图已存在,未写入"] };
|
|
279
|
+
if (before == null)
|
|
280
|
+
return { kind: "calls", calls: [], notes: ["目标在写入前不存在,无法恢复"] };
|
|
281
|
+
const row = before;
|
|
282
|
+
const configure = { schemaCode, code, fieldCodes: readArray(row, "queryColumns").map((column) => String(column.propertyCode ?? "")).filter(Boolean), ...(row.querySorts !== undefined ? { sorts: row.querySorts } : {}), ...(row.queryConditions !== undefined ? { conditions: row.queryConditions } : {}), ...(row.queryActions !== undefined ? { actions: row.queryActions, replaceActions: true } : {}), ...(row.queryPresentation !== undefined ? { presentation: row.queryPresentation } : {}), ...(row.options !== undefined ? { options: parseConditionOptions(row.options) } : {}), ...(row.queryPresentationType !== undefined ? { presentationType: row.queryPresentationType } : {}), ...(row.queryGanttModel !== undefined ? { gantt: row.queryGanttModel } : {}), ...(row.queryLayer !== undefined ? { queryLayer: row.queryLayer } : {}), ...(row.multiTableHeaderConfig !== undefined ? { multiTableHeaderConfig: parseConditionOptions(row.multiTableHeaderConfig) } : {}) };
|
|
283
|
+
if (toolName === "listview.configure")
|
|
284
|
+
return { kind: "calls", calls: [{ tool: "listview.configure", args: configure }] };
|
|
285
|
+
return { kind: "calls", calls: [{ tool: "listview.create", args: { schemaCode, code, name: String(row.name ?? code) } }, { tool: "listview.configure", args: configure }], notes: ["删除后重建视图,平台 id 不保证一致"] };
|
|
286
|
+
},
|
|
287
|
+
});
|
|
266
288
|
tools.push(defineTool({
|
|
267
289
|
name: "listview.list",
|
|
268
290
|
layer: "low",
|
|
@@ -308,7 +330,7 @@ export function registerListViewLane(tools) {
|
|
|
308
330
|
throw error;
|
|
309
331
|
return { performed: { skipped: "already-present" }, readback: { alreadyPresent: true, header: header.data } };
|
|
310
332
|
}
|
|
311
|
-
});
|
|
333
|
+
}, undefined, undefined, listviewRollbackSpec("listview.create"));
|
|
312
334
|
writeTool(tools, "listview.configure", LIST_VIEW_LANE, LIST_VIEW_TOOLS["listview.configure"].scope, LIST_VIEW_TOOLS["listview.configure"].description, {
|
|
313
335
|
schemaCode: z.string().min(1),
|
|
314
336
|
code: z.string().optional(),
|
|
@@ -569,7 +591,7 @@ export function registerListViewLane(tools) {
|
|
|
569
591
|
readback.multiTableHeaderConfig = parseConditionOptions(afterModel.multiTableHeaderConfig);
|
|
570
592
|
}
|
|
571
593
|
return { performed: { errcode: performed.errcode }, readback };
|
|
572
|
-
}, assertListViewConfigureShape);
|
|
594
|
+
}, assertListViewConfigureShape, undefined, listviewRollbackSpec("listview.configure"));
|
|
573
595
|
writeTool(tools, "listview.delete", LIST_VIEW_LANE, LIST_VIEW_TOOLS["listview.delete"].scope, LIST_VIEW_TOOLS["listview.delete"].description, { schemaCode: z.string().min(1), code: z.string().min(1) }, async (session, args) => {
|
|
574
596
|
const before = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code } });
|
|
575
597
|
if (before.data == null)
|
|
@@ -577,6 +599,6 @@ export function registerListViewLane(tools) {
|
|
|
577
599
|
const performed = await wireCall(session, { path: ENDPOINTS.listQueryDelete, method: "POST", query: { schemaCode: args.schemaCode, code: args.code } });
|
|
578
600
|
const readback = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code } });
|
|
579
601
|
return { performed: { errcode: performed.errcode }, readback: { absent: readback.data == null, header: readback.data ?? null } };
|
|
580
|
-
});
|
|
602
|
+
}, undefined, undefined, listviewRollbackSpec("listview.delete"));
|
|
581
603
|
}
|
|
582
604
|
//# sourceMappingURL=list-view.js.map
|
package/dist/lanes/model.js
CHANGED
|
@@ -17,6 +17,35 @@ import { wireCall, wireRows, WireError } from "../wire.js";
|
|
|
17
17
|
import { MODEL_CREATE_BODY_DEFAULTS, MODEL_LANE_CONTRACT } from "./model-contract.js";
|
|
18
18
|
const MODEL_LANE = MODEL_LANE_CONTRACT.lane;
|
|
19
19
|
const MODEL_TOOLS = MODEL_LANE_CONTRACT.tools;
|
|
20
|
+
function modelRollbackSpec(toolName) {
|
|
21
|
+
return {
|
|
22
|
+
target: (args) => `model:${String(args.schemaCode ?? args.code ?? "")}`,
|
|
23
|
+
read: async (session, args) => {
|
|
24
|
+
const resolved = await resolveModelTreeNode(session, String(args.schemaCode ?? args.code));
|
|
25
|
+
const record = (await wireCall(session, { path: ENDPOINTS.modelGet, query: { schemaCode: String(args.schemaCode ?? args.code) } })).data;
|
|
26
|
+
if (!resolved.node && !record)
|
|
27
|
+
return null;
|
|
28
|
+
return { node: resolved.node ?? null, record: record ?? null };
|
|
29
|
+
},
|
|
30
|
+
inverse: ({ before, args }) => {
|
|
31
|
+
const code = String(args.schemaCode ?? args.code);
|
|
32
|
+
if (toolName === "model.create") {
|
|
33
|
+
// before null = the model did not exist → the rollback DELETES what
|
|
34
|
+
// this write created; before present = idempotent already-present
|
|
35
|
+
// skip → nothing to undo.
|
|
36
|
+
if (before == null)
|
|
37
|
+
return { kind: "calls", calls: [{ tool: "model.delete", args: { schemaCode: code } }] };
|
|
38
|
+
return { kind: "calls", calls: [], notes: ["create 时已存在(幂等跳过),未写入,无需回滚"] };
|
|
39
|
+
}
|
|
40
|
+
if (before == null)
|
|
41
|
+
return { kind: "calls", calls: [] };
|
|
42
|
+
const snapshot = before;
|
|
43
|
+
if (toolName === "model.update")
|
|
44
|
+
return { kind: "calls", calls: snapshot.node?.name === undefined ? [] : [{ tool: "model.update", args: { schemaCode: code, name: String(snapshot.node.name) } }] };
|
|
45
|
+
return { kind: "calls", calls: [{ tool: "model.create", args: { code, name: String(snapshot.node?.name ?? snapshot.record?.name ?? code) } }], notes: ["字段/表单/视图不随行恢复,需重建"] };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
20
49
|
/**
|
|
21
50
|
* Ticket 68 — locate a model's app-tree node across applications. The app
|
|
22
51
|
* tree row id is the AppFunction id that `apppackage/physically_delete`
|
|
@@ -344,7 +373,7 @@ export function registerModelLane(tools) {
|
|
|
344
373
|
});
|
|
345
374
|
const present = flattenTreeRows(wireRows((await wireCall(session, { path: ENDPOINTS.applicationTree, query: { appCode: applicationCode } })).data)).some((row) => row.type === "BizModel" && row.code === args.code);
|
|
346
375
|
return { performed: { errcode: performed.errcode, folderId, resolvedFrom, applicationCode }, readback: { present } };
|
|
347
|
-
});
|
|
376
|
+
}, undefined, undefined, modelRollbackSpec("model.create"));
|
|
348
377
|
/** model.delete writeTool extra arg: a session-aware dry-run that scans inbound references (no write). */
|
|
349
378
|
async function modelDeleteDryRun(session, args) {
|
|
350
379
|
const record = await (async () => {
|
|
@@ -493,7 +522,7 @@ export function registerModelLane(tools) {
|
|
|
493
522
|
}),
|
|
494
523
|
},
|
|
495
524
|
};
|
|
496
|
-
}, undefined, modelDeleteDryRun);
|
|
525
|
+
}, undefined, modelDeleteDryRun, modelRollbackSpec("model.delete"));
|
|
497
526
|
writeTool(tools, "model.deleteMany", MODEL_LANE, MODEL_TOOLS["model.deleteMany"].scope, MODEL_TOOLS["model.deleteMany"].description, { schemaCodes: z.array(z.string().min(1)).min(1), applicationCode: z.string().optional() }, async (session, args) => {
|
|
498
527
|
// Platform evidence: physically_delete takes the WHOLE list in ONE
|
|
499
528
|
// request (AppPackageController.java:1424 @RequestBody
|
|
@@ -734,7 +763,7 @@ export function registerModelLane(tools) {
|
|
|
734
763
|
schemaCode: raw.schemaCode,
|
|
735
764
|
message: "模型类型在创建时固化——bizmodels/update 只更新应用菜单节点显示,不能把底层 schema 从 LIST 迁移为 TREE(get_all 仍返回原类型与树系统字段,live 取证)。要树模型请建新模型",
|
|
736
765
|
});
|
|
737
|
-
});
|
|
766
|
+
}, undefined, modelRollbackSpec("model.update"));
|
|
738
767
|
registerPackageTool(tools, {
|
|
739
768
|
name: "configure_model",
|
|
740
769
|
lane: MODEL_LANE,
|
|
@@ -72,9 +72,13 @@ export const ONLINE_JS_LANE_CONTRACT = Object.freeze({
|
|
|
72
72
|
"packages/omc-cli/test/issue-11-online-js.test.ts",
|
|
73
73
|
]),
|
|
74
74
|
tools: Object.freeze({
|
|
75
|
+
"onlinejs.get": Object.freeze({
|
|
76
|
+
scope: "draft published script readback",
|
|
77
|
+
description: "Read the form's Online JS script WITHOUT pulling the whole six-JSON full body: extracts the customScript element from draftHtmlJson and publishedHtmlJson and returns each face's innerHTML verbatim (the platform lifecycle wrapper included — that IS the stored script), plus SHA-256 for change detection and a present marker when the sheet/script does not exist yet. Use this to inspect existing script before onlinejs.compose (replace vs INCREMENTAL decision), or to verify a compose landed; the compose tool's own readback fingerprints the same bytes.",
|
|
78
|
+
}),
|
|
75
79
|
"onlinejs.compose": Object.freeze({
|
|
76
80
|
scope: ONLINE_JS_SCOPE,
|
|
77
|
-
description: `Compose the Online JS source as the customScript element into the form's draftHtmlJson layout array (then publish, promoting it verbatim to publishedHtmlJson). Core contract: preserve the platform template and only extend customScript.innerHTML — the six-element skeleton includes the ${PLATFORM_LIFECYCLE_WRAPPER_MARKER} lifecycle wrapper; the style, toolbar, template, beforeCustomScript, customScript, and afterCustomScript elements are kept or synthesized with platform defaults, while every layout/control element is preserved. customScript is rebuilt canonically as {tagName:'script', id:'customScript', innerHTML:<platform lifecycle wrapper containing source>, attrs:[]} because the designer requires an ARRAY attrs (e.attrs.some) and tagName, else the Online JS editor fails with t.some is not a function. Drives bizsheet/update + bizsheet/publish; publishedHtmlJson is read back verbatim and SHA-256-fingerprinted so the caller can verify byte equality. Never overwrites the layout with a standalone script object or a partial [customScript]-only array (regression for issue 11's root cause).`,
|
|
81
|
+
description: `Compose the Online JS source as the customScript element into the form's draftHtmlJson layout array (then publish, promoting it verbatim to publishedHtmlJson). Core contract: preserve the platform template and only extend customScript.innerHTML — the six-element skeleton includes the ${PLATFORM_LIFECYCLE_WRAPPER_MARKER} lifecycle wrapper; the style, toolbar, template, beforeCustomScript, customScript, and afterCustomScript elements are kept or synthesized with platform defaults, while every layout/control element is preserved. customScript is rebuilt canonically as {tagName:'script', id:'customScript', innerHTML:<platform lifecycle wrapper containing source>, attrs:[]} because the designer requires an ARRAY attrs (e.attrs.some) and tagName, else the Online JS editor fails with t.some is not a function. Drives bizsheet/update + bizsheet/publish; publishedHtmlJson is read back verbatim and SHA-256-fingerprinted so the caller can verify byte equality. Read the EXISTING script first with onlinejs.get (decide replace vs INCREMENTAL from what is actually stored). Never overwrites the layout with a standalone script object or a partial [customScript]-only array (regression for issue 11's root cause).`,
|
|
78
82
|
}),
|
|
79
83
|
}),
|
|
80
84
|
});
|
|
@@ -93,6 +97,7 @@ export function assertOnlineJsContract() {
|
|
|
93
97
|
if (!ONLINE_JS_WIRE.composeElement.includes(PLATFORM_LIFECYCLE_WRAPPER_MARKER))
|
|
94
98
|
throw new Error("online-js contract must pin the platform lifecycle wrapper");
|
|
95
99
|
assertDescriptionCarries(ONLINE_JS_LANE_CONTRACT, "onlinejs.compose", ["customScript", "draftHtmlJson", "publishedHtmlJson", "SHA-256", "issue 11", PLATFORM_LIFECYCLE_WRAPPER_MARKER]);
|
|
100
|
+
assertDescriptionCarries(ONLINE_JS_LANE_CONTRACT, "onlinejs.get", ["customScript", "draftHtmlJson", "publishedHtmlJson", "SHA-256"]);
|
|
96
101
|
if ("onlinejs.draft" in ONLINE_JS_LANE_CONTRACT.tools || "onlinejs.publish" in ONLINE_JS_LANE_CONTRACT.tools)
|
|
97
102
|
throw new Error("online-js contract must not carry the removed programming tools");
|
|
98
103
|
}
|
|
@@ -22,7 +22,7 @@ export const ONLINE_JS_COVERAGE = declareLaneCoverage(ONLINE_JS_LANE_CONTRACT.la
|
|
|
22
22
|
".scratch/omc-route-wayfinder/leg4-closeout.md",
|
|
23
23
|
"packages/omc-cli/test/ticket-73-onlinejs-template-fidelity.test.ts",
|
|
24
24
|
],
|
|
25
|
-
tools: [claim("onlinejs.compose", T["onlinejs.compose"].scope)],
|
|
25
|
+
tools: [claim("onlinejs.compose", T["onlinejs.compose"].scope), claim("onlinejs.get", T["onlinejs.get"].scope)],
|
|
26
26
|
},
|
|
27
27
|
]);
|
|
28
28
|
//# sourceMappingURL=online-js-coverage.js.map
|
package/dist/lanes/online-js.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { ENDPOINTS } from "../endpoints.js";
|
|
4
|
-
import { assertHtmlRender, executeFormDraftWrite, executeFormPublishWrite, validateHtmlRender } from "../form-support.js";
|
|
4
|
+
import { assertHtmlRender, executeFormDraftWrite, executeFormPublishWrite, readFormOrNull, validateHtmlRender } from "../form-support.js";
|
|
5
|
+
import { capabilityArg, environmentArg } from "../args.js";
|
|
5
6
|
import { writeTool } from "../write.js";
|
|
7
|
+
import { governedRead } from "../read.js";
|
|
8
|
+
import { defineTool } from "../tool-types.js";
|
|
6
9
|
import { wireCall } from "../wire.js";
|
|
7
10
|
import { composeCustomScript, lintOnlineJsSource } from "../online-js-source.js";
|
|
8
11
|
import { composeLayoutElements } from "../online-js-layout.js";
|
|
@@ -40,7 +43,58 @@ function parseLayoutElements(rawHtml) {
|
|
|
40
43
|
* section is what convertElement keys on (id === "template") to render the
|
|
41
44
|
* template area — omitting toolbar/template leaves the panel regions empty.
|
|
42
45
|
*/
|
|
46
|
+
/** Extract the customScript element's innerHTML from a layout-JSON face; null when absent. */
|
|
47
|
+
function readCustomScriptSource(rawHtml) {
|
|
48
|
+
const elements = parseLayoutElements(rawHtml);
|
|
49
|
+
// Match by id ONLY: beforeCustomScript/afterCustomScript are also
|
|
50
|
+
// tagName:'script' elements with an empty innerHTML and would shadow the
|
|
51
|
+
// real script when the find falls back to the tag name.
|
|
52
|
+
const element = elements.find((candidate) => candidate.id === "customScript");
|
|
53
|
+
const script = typeof element?.innerHTML === "string" && element.innerHTML.trim() !== "" ? element.innerHTML : null;
|
|
54
|
+
return { present: script !== null, script, sha256: script !== null ? createHash("sha256").update(script, "utf8").digest("hex") : null, elementCount: elements.length };
|
|
55
|
+
}
|
|
43
56
|
export function registerOnlineJsLane(tools) {
|
|
57
|
+
tools.push(defineTool({
|
|
58
|
+
name: "onlinejs.get",
|
|
59
|
+
layer: "low",
|
|
60
|
+
kind: "read",
|
|
61
|
+
lane: ONLINE_JS_LANE,
|
|
62
|
+
scope: ONLINE_JS_TOOLS["onlinejs.get"].scope,
|
|
63
|
+
description: ONLINE_JS_TOOLS["onlinejs.get"].description,
|
|
64
|
+
inputSchema: {
|
|
65
|
+
environment: environmentArg,
|
|
66
|
+
schemaCode: z.string().min(1),
|
|
67
|
+
sheetCode: z.string().min(1).optional().describe("表单槽位码;省略时默认 = schemaCode(模型码即默认表单槽位);多表单模型显式传"),
|
|
68
|
+
capability: capabilityArg,
|
|
69
|
+
},
|
|
70
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
71
|
+
const sheetCode = args.sheetCode ?? args.schemaCode;
|
|
72
|
+
const sheet = await readFormOrNull(session, args.schemaCode, sheetCode);
|
|
73
|
+
if (sheet === null)
|
|
74
|
+
return { schemaCode: args.schemaCode, sheetCode, present: false, hint: "表单不存在——先 form.draft/表单创建" };
|
|
75
|
+
const draft = readCustomScriptSource(typeof sheet.draftHtmlJson === "string" ? sheet.draftHtmlJson : null);
|
|
76
|
+
const published = readCustomScriptSource(typeof sheet.publishedHtmlJson === "string" ? sheet.publishedHtmlJson : null);
|
|
77
|
+
return {
|
|
78
|
+
schemaCode: args.schemaCode,
|
|
79
|
+
sheetCode,
|
|
80
|
+
present: true,
|
|
81
|
+
sheetPublished: sheet.published === true,
|
|
82
|
+
draft: { ...draft, ...(draft.script === null ? { note: "草稿无 customScript 脚本(尚未 compose 或被清空)" } : {}) },
|
|
83
|
+
published: { ...published, ...(published.script === null ? { note: "发布面无 customScript 脚本" } : {}) },
|
|
84
|
+
...(draft.sha256 !== null && published.sha256 !== null
|
|
85
|
+
? { draftPublishedInSync: draft.sha256 === published.sha256 }
|
|
86
|
+
: {}),
|
|
87
|
+
hint: "脚本是 innerHTML 原文(含平台 (function(form){…}) 生命周期包装——那就是平台存储形态);改脚本用 onlinejs.compose(默认 INCREMENTAL 并入,replace:true 整段替换);同文件修订直接 replace:true 传完整脚本",
|
|
88
|
+
};
|
|
89
|
+
}, {
|
|
90
|
+
target: `onlinejs.get:${args.schemaCode}/${args.sheetCode ?? args.schemaCode}`,
|
|
91
|
+
// The script IS the payload of this read: keep it inline past the
|
|
92
|
+
// generic 4 KB diet (compactReadback would replace the faces with key
|
|
93
|
+
// counts and force an artifact round-trip — the exact friction this
|
|
94
|
+
// tool exists to remove).
|
|
95
|
+
compact: (result) => result,
|
|
96
|
+
}),
|
|
97
|
+
}));
|
|
44
98
|
writeTool(tools, "onlinejs.compose", ONLINE_JS_LANE, ONLINE_JS_TOOLS["onlinejs.compose"].scope, ONLINE_JS_TOOLS["onlinejs.compose"].description, {
|
|
45
99
|
schemaCode: z.string().min(1),
|
|
46
100
|
sheetCode: z.string().min(1),
|
|
@@ -122,6 +176,29 @@ export function registerOnlineJsLane(tools) {
|
|
|
122
176
|
// Refuse a source that cannot run in the script slot (export/import, unknown
|
|
123
177
|
// form.on event) BEFORE the write permission gate. This is the exact
|
|
124
178
|
// class of mistake that blanked a live form page with no harness signal.
|
|
125
|
-
(args) => lintOnlineJsSource(args.source)
|
|
179
|
+
(args) => lintOnlineJsSource(args.source), undefined, {
|
|
180
|
+
target: (args) => `onlinejs:${String(args.schemaCode ?? "")}/${String(args.sheetCode ?? args.schemaCode ?? "")}`,
|
|
181
|
+
read: async (session, args) => {
|
|
182
|
+
const sheet = await readFormOrNull(session, String(args.schemaCode), String(args.sheetCode ?? args.schemaCode));
|
|
183
|
+
if (!sheet)
|
|
184
|
+
return null;
|
|
185
|
+
const raw = typeof sheet.draftHtmlJson === "string" ? sheet.draftHtmlJson : "";
|
|
186
|
+
let elements = [];
|
|
187
|
+
try {
|
|
188
|
+
const parsed = JSON.parse(raw);
|
|
189
|
+
if (Array.isArray(parsed))
|
|
190
|
+
elements = parsed;
|
|
191
|
+
}
|
|
192
|
+
catch { }
|
|
193
|
+
const script = elements.find((element) => element && typeof element === "object" && element.id === "customScript");
|
|
194
|
+
return { customScript: typeof script?.innerHTML === "string" ? script.innerHTML : null };
|
|
195
|
+
},
|
|
196
|
+
inverse: ({ before, args }) => {
|
|
197
|
+
const source = before?.customScript;
|
|
198
|
+
if (typeof source !== "string")
|
|
199
|
+
return { kind: "irreversible", reason: "before 表单没有可识别的 customScript element,无法构造清空脚本的等价 compose 调用" };
|
|
200
|
+
return { kind: "calls", calls: [{ tool: "onlinejs.compose", args: { schemaCode: String(args.schemaCode), sheetCode: String(args.sheetCode ?? args.schemaCode), source, replace: true, publish: args.publish } }] };
|
|
201
|
+
},
|
|
202
|
+
});
|
|
126
203
|
}
|
|
127
204
|
//# sourceMappingURL=online-js.js.map
|
package/dist/lanes/report.js
CHANGED
|
@@ -9,6 +9,32 @@ import { biCall, DEFAULT_BI_CORP_ID } from "../bi-wire.js";
|
|
|
9
9
|
import { REPORT_LANE_CONTRACT } from "./report-contract.js";
|
|
10
10
|
const LANE = REPORT_LANE_CONTRACT.lane;
|
|
11
11
|
const TOOLS = REPORT_LANE_CONTRACT.tools;
|
|
12
|
+
function reportRollbackSpec(toolName) {
|
|
13
|
+
return {
|
|
14
|
+
target: (args) => `report:${String(args.code ?? args.reportCode ?? "")}`,
|
|
15
|
+
read: async (session, args) => {
|
|
16
|
+
const code = String(args.code ?? args.reportCode);
|
|
17
|
+
const page = (await wireCall(session, { path: ENDPOINTS.reportGet, query: { code } })).data;
|
|
18
|
+
if (!page || !page.id)
|
|
19
|
+
return null;
|
|
20
|
+
const auth = authOf(args.corpId, code, String(args.appCode ?? page.appCode ?? ""));
|
|
21
|
+
const bi = await readBiConfig(session, auth, String(page.id));
|
|
22
|
+
return { page, bi };
|
|
23
|
+
},
|
|
24
|
+
inverse: ({ before, args }) => {
|
|
25
|
+
if (toolName === "report.create")
|
|
26
|
+
return before == null ? { kind: "calls", calls: [{ tool: "report.delete", args: { code: String(args.code) } }] } : { kind: "calls", calls: [], notes: ["报表已存在,create 未写入"] };
|
|
27
|
+
if (before == null)
|
|
28
|
+
return { kind: "calls", calls: [] };
|
|
29
|
+
const snapshot = before;
|
|
30
|
+
if (toolName === "report.update")
|
|
31
|
+
return { kind: "calls", calls: [{ tool: "report.update", args: { code: String(args.code), ...(snapshot.page.name !== undefined ? { name: snapshot.page.name } : {}), ...(snapshot.page.icon !== undefined ? { icon: snapshot.page.icon } : {}), ...(snapshot.page.parentId !== undefined ? { parentId: snapshot.page.parentId } : {}), ...(snapshot.page.pcAble !== undefined ? { pcAble: snapshot.page.pcAble } : {}), ...(snapshot.page.mobileAble !== undefined ? { mobileAble: snapshot.page.mobileAble } : {}) } }] };
|
|
32
|
+
if (toolName === "report.configure")
|
|
33
|
+
return { kind: "calls", calls: [{ tool: "report.configure", args: { reportCode: String(args.reportCode), appCode: String(args.appCode), charts: snapshot.bi.charts, global: snapshot.bi.global, attributes: snapshot.bi.attributes, removeUnlistedCharts: true, ...(args.corpId ? { corpId: args.corpId } : {}) } }] };
|
|
34
|
+
return { kind: "calls", calls: [{ tool: "report.create", args: { code: String(snapshot.page.code), name: String(snapshot.page.name), appCode: String(snapshot.page.appCode), parentId: String(snapshot.page.parentId) } }], notes: ["删除后重建报表页,平台 id/dashboard id 不保证一致"] };
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
12
38
|
/**
|
|
13
39
|
* The default BI dashboard `global` (theme21). A brand-new report page has no
|
|
14
40
|
* BI dashboard record until it is saved, and the BI save requires a `global`
|
|
@@ -360,7 +386,7 @@ export function registerReportLane(tools) {
|
|
|
360
386
|
performed: { errcode: 0, code, appCode: args.appCode, objectId },
|
|
361
387
|
readback: { present: page.code === code, report: reportPageView(page), biInitialized: objectId !== "" },
|
|
362
388
|
};
|
|
363
|
-
}, (args) => requireCode(args.code));
|
|
389
|
+
}, (args) => requireCode(args.code), undefined, reportRollbackSpec("report.create"));
|
|
364
390
|
writeTool(tools, "report.update", LANE, TOOLS["report.update"].scope, TOOLS["report.update"].description, {
|
|
365
391
|
code: z.string().min(1),
|
|
366
392
|
name: z.string().min(1).max(60).optional(),
|
|
@@ -391,7 +417,7 @@ export function registerReportLane(tools) {
|
|
|
391
417
|
});
|
|
392
418
|
const after = (await wireCall(session, { path: ENDPOINTS.reportGet, query: { code: args.code } })).data;
|
|
393
419
|
return { performed: { errcode: performed.errcode }, readback: { present: Boolean(after && after.code), report: reportPageView(after) } };
|
|
394
|
-
});
|
|
420
|
+
}, undefined, undefined, reportRollbackSpec("report.update"));
|
|
395
421
|
writeTool(tools, "report.delete", LANE, TOOLS["report.delete"].scope, TOOLS["report.delete"].description, {
|
|
396
422
|
code: z.string().min(1),
|
|
397
423
|
corpId: z.string().optional(),
|
|
@@ -418,7 +444,7 @@ export function registerReportLane(tools) {
|
|
|
418
444
|
collectReports(tree, remaining);
|
|
419
445
|
const stillListed = remaining.some((report) => report.code === args.code);
|
|
420
446
|
return { performed: { errcode: performed.errcode, code: args.code, dashboardRemoved }, readback: { absent: !stillListed, remainingInTree: stillListed } };
|
|
421
|
-
});
|
|
447
|
+
}, undefined, undefined, reportRollbackSpec("report.delete"));
|
|
422
448
|
writeTool(tools, "report.configure", LANE, TOOLS["report.configure"].scope, TOOLS["report.configure"].description, {
|
|
423
449
|
reportCode: z.string().min(1),
|
|
424
450
|
appCode: z.string().min(1),
|
|
@@ -473,6 +499,6 @@ export function registerReportLane(tools) {
|
|
|
473
499
|
attributesPresent: after.attributes !== undefined && JSON.stringify(after.attributes) !== "{}",
|
|
474
500
|
},
|
|
475
501
|
};
|
|
476
|
-
});
|
|
502
|
+
}, undefined, undefined, reportRollbackSpec("report.configure"));
|
|
477
503
|
}
|
|
478
504
|
//# sourceMappingURL=report.js.map
|
|
@@ -10,6 +10,31 @@ import { wireCall, wireRows, WireError } from "../wire.js";
|
|
|
10
10
|
import { RULE_ENABLE_CHAIN, RULE_LIFECYCLE_LANE_CONTRACT } from "./rule-lifecycle-contract.js";
|
|
11
11
|
const RULE_LIFECYCLE_LANE = RULE_LIFECYCLE_LANE_CONTRACT.lane;
|
|
12
12
|
const RULE_LIFECYCLE_TOOLS = RULE_LIFECYCLE_LANE_CONTRACT.tools;
|
|
13
|
+
function lifecycleRollbackSpec(toolName) {
|
|
14
|
+
return {
|
|
15
|
+
target: (args) => `rule:${String(args.schemaCode ?? "")}/${String(args.ruleCode ?? "")}`,
|
|
16
|
+
read: async (session, args) => {
|
|
17
|
+
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: String(args.schemaCode) } })).data);
|
|
18
|
+
const row = rows.find((item) => String(item.code) === String(args.ruleCode));
|
|
19
|
+
if (!row)
|
|
20
|
+
return null;
|
|
21
|
+
if (toolName !== "rule.delete")
|
|
22
|
+
return { enabled: row.enabled, status: row.status };
|
|
23
|
+
return row.id ? (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: String(row.id) } })).data : row;
|
|
24
|
+
},
|
|
25
|
+
inverse: ({ before, args }) => {
|
|
26
|
+
const base = { schemaCode: String(args.schemaCode), ruleCode: String(args.ruleCode) };
|
|
27
|
+
if (before == null)
|
|
28
|
+
return { kind: "calls", calls: [] };
|
|
29
|
+
if (toolName === "rule.enable")
|
|
30
|
+
return { kind: "calls", calls: [{ tool: "rule.disable", args: base }] };
|
|
31
|
+
if (toolName === "rule.disable")
|
|
32
|
+
return { kind: "calls", calls: [{ tool: "rule.enable", args: base }] };
|
|
33
|
+
const row = before;
|
|
34
|
+
return { kind: "calls", calls: [{ tool: "rule.save", args: { ...base, name: String(row.name ?? args.ruleCode), nodeGraph: { nodes: row.node, routes: row.route, schedulerSetting: row.schedulerSetting } } }], notes: ["删除后重建的规则 id/版本不保证一致"] };
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
13
38
|
export async function executeRuleEnableWrite(session, args) {
|
|
14
39
|
await assertRuleSchemaExists(session, args.schemaCode);
|
|
15
40
|
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
@@ -47,7 +72,7 @@ export async function executeRuleEnableWrite(session, args) {
|
|
|
47
72
|
return { performed: { errcode: published.errcode, enableErrcode, ...(alreadyEnabled ? { alreadyEnabled: true } : {}) }, readback: { enabled: true } };
|
|
48
73
|
}
|
|
49
74
|
export function registerRuleLifecycleLane(tools) {
|
|
50
|
-
writeTool(tools, "rule.enable", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.enable"].scope, RULE_LIFECYCLE_TOOLS["rule.enable"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => executeRuleEnableWrite(session, args));
|
|
75
|
+
writeTool(tools, "rule.enable", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.enable"].scope, RULE_LIFECYCLE_TOOLS["rule.enable"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => executeRuleEnableWrite(session, args), undefined, undefined, lifecycleRollbackSpec("rule.enable"));
|
|
51
76
|
writeTool(tools, "rule.disable", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.disable"].scope, RULE_LIFECYCLE_TOOLS["rule.disable"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => {
|
|
52
77
|
await assertRuleSchemaExists(session, args.schemaCode);
|
|
53
78
|
const list = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
@@ -58,7 +83,7 @@ export function registerRuleLifecycleLane(tools) {
|
|
|
58
83
|
const readback = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
59
84
|
const after = wireRows(readback.data).find((item) => item.code === args.ruleCode);
|
|
60
85
|
return { performed: { errcode: performed.errcode }, readback: { enabled: after?.enabled === true } };
|
|
61
|
-
});
|
|
86
|
+
}, undefined, undefined, lifecycleRollbackSpec("rule.disable"));
|
|
62
87
|
writeTool(tools, "rule.delete", RULE_LIFECYCLE_LANE, RULE_LIFECYCLE_TOOLS["rule.delete"].scope, RULE_LIFECYCLE_TOOLS["rule.delete"].description, { schemaCode: z.string().min(1), ruleCode: z.string().min(1) }, async (session, args) => {
|
|
63
88
|
// Ticket 96 finding: the preset rule codes are platform-managed —
|
|
64
89
|
// listViewsBySchemaCode re-fills any missing preset for the schema, so
|
|
@@ -78,7 +103,7 @@ export function registerRuleLifecycleLane(tools) {
|
|
|
78
103
|
const performed = await wireCall(session, { path: ENDPOINTS.businessRuleDelete, method: "POST", query: { id: String(row.id) } });
|
|
79
104
|
const readback = await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } });
|
|
80
105
|
return { performed: { errcode: performed.errcode }, readback: { absent: !wireRows(readback.data).some((item) => item.code === args.ruleCode) } };
|
|
81
|
-
});
|
|
106
|
+
}, undefined, undefined, lifecycleRollbackSpec("rule.delete"));
|
|
82
107
|
// Round 10 issue 05 — lifecycle batch. Disabling a model's rules one by one was
|
|
83
108
|
// N calls; this flips `enabled:false` on every matching rule in one tool. Only
|
|
84
109
|
// CUSTOM rules by default (platform preset trigger rows must not be disabled —
|
|
@@ -120,7 +120,7 @@ export const WORKFLOW_LANE_CONTRACT = Object.freeze({
|
|
|
120
120
|
}),
|
|
121
121
|
"workflow.update": Object.freeze({
|
|
122
122
|
scope: WORKFLOW_SCOPE,
|
|
123
|
-
description: "写入工作流模板草稿(`/api/api/workflow/update`,raw template JSON body)。cli 以当前草稿为基线做 merge,未列出的既有节点保留:**模板缺 START 或 END 时自动按增量 merge(部分模板),给全 START..END 才整模板替换**;也可显式 `merge:true/false`。**节点字段权限也走这里**:不必读 `get_draft --full`——直接发 `activities:[{activityCode,
|
|
123
|
+
description: "写入工作流模板草稿(`/api/api/workflow/update`,raw template JSON body)。cli 以当前草稿为基线做 merge,未列出的既有节点保留:**模板缺 START 或 END 时自动按增量 merge(部分模板),给全 START..END 才整模板替换**;也可显式 `merge:true/false`。**节点字段权限也走这里**:不必读 `get_draft --full`——直接发 `activities:[{activityCode, propertyPermissions:[{propertyCode, visible, editable, required, summary, subPropertyPermissions}]}]`(**type 可省**:activityCode 命中草稿节点时类型/名称继承草稿,不会铸 `_2` 新码;改名才产生新码——平台不能原地改名),工具按 propertyCode 合并到草稿对应节点(未列的字段权限保留;`merge:true` 也可同用)——没有单独的权限工具,别把「找不到入口」当成不支持(dogfood 实测该误判会假阻塞)。`summary` **无需配置**:工具合并时自动补 `summary:true`(平台怪癖——缺省进不了运行时 `activitySummaries`、`form/load` 读不到你的 `editable:false`;显式传 `false` 才保留);子表字段用 `subPropertyPermissions`(同样自动补)。**子表属性不可设 `required:true`**(接口形状接受但设计器节点字段配置不支持,存入后无法渲染——工具直接拒 `workflow-child-table-not-required`);子表整行只配 visible/editable,字段级必填配到子表内部字段所在节点。**新增节点用语义 code**(如 `approval_finance`),不要模仿草稿的 `ActivityN` 再加 `_1/_2` 后缀。**设计器默认自动补齐**(调用方显式传值优先):填写节点({Originator})自动生成子表操作按钮 `buttonPermission.childTableBtns`(每个子表 ADD/DELETE/IMPORT/EXPORT 启用、BATCH_UPDATE 禁用——API 建的流程缺这份模板,设计器里子表按钮会全部不勾);`permittedAction` 归一为设计器默认(填写节点 showReject:false 不能驳回自己,审批节点 rejectToStart:true 驳回回发起节点)。用 `npm run test:live-permission`(非管理员参与者)验收,管理员会被后端强制 `e:true` 掩盖。",
|
|
124
124
|
}),
|
|
125
125
|
"workflow.publish": Object.freeze({
|
|
126
126
|
scope: WORKFLOW_SCOPE,
|