@stalfh233/omc-cli 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.
@@ -109,6 +109,22 @@ const VOLATILE_VERIFY_KEYS = new Set([
109
109
  "fixedPoint",
110
110
  "shortCode",
111
111
  ]);
112
+ /**
113
+ * Arrays whose ORDER is not part of the configuration's meaning. The platform
114
+ * reorders them on write (live 2026-10-23: a workflow.update restore read back
115
+ * `propertyPermissions` in a different order and rollback reported a phantom
116
+ * "mismatch" although every field/value matched). Canonicalize by a stable key
117
+ * before comparing.
118
+ */
119
+ const ORDER_INSENSITIVE_ARRAY_KEYS = {
120
+ propertyPermissions: (entry) => String(entry?.propertyCode ?? ""),
121
+ subPropertyPermissions: (entry) => String(entry?.propertyCode ?? ""),
122
+ activities: (entry) => String(entry?.activityCode ?? ""),
123
+ rules: (entry) => {
124
+ const rule = (entry ?? {});
125
+ return `${String(rule.preActivityCode ?? "")}->${String(rule.postActivityCode ?? "")}|${JSON.stringify(rule.ruleConditionGroup ?? rule.formula ?? null)}|${rule.utilizeElse === true ? "else" : ""}`;
126
+ },
127
+ };
112
128
  export function canonicalForVerify(value) {
113
129
  if (Array.isArray(value))
114
130
  return value.map(canonicalForVerify);
@@ -118,7 +134,22 @@ export function canonicalForVerify(value) {
118
134
  for (const key of Object.keys(record).sort()) {
119
135
  if (VOLATILE_VERIFY_KEYS.has(key))
120
136
  continue;
121
- out[key] = canonicalForVerify(record[key]);
137
+ // Workflow route conditions: 8.x stores `ruleConditionGroup: []` for a
138
+ // plain edge while the restore write drops the empty shape (semantically
139
+ // "no condition" either way). Treat both spellings as equal, else every
140
+ // workflow rollback on such a draft would report a phantom mismatch.
141
+ if (key === "ruleConditionGroup" && Array.isArray(record[key]) && record[key].length === 0)
142
+ continue;
143
+ const item = record[key];
144
+ const orderKey = ORDER_INSENSITIVE_ARRAY_KEYS[key];
145
+ if (orderKey && Array.isArray(item)) {
146
+ out[key] = item
147
+ .map((entry) => ({ key: orderKey(entry), value: canonicalForVerify(entry) }))
148
+ .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
149
+ .map((wrapped) => wrapped.value);
150
+ continue;
151
+ }
152
+ out[key] = canonicalForVerify(item);
122
153
  }
123
154
  return out;
124
155
  }
package/dist/tools.js CHANGED
@@ -12,6 +12,7 @@ import { registerBusinessRuleLane } from "./lanes/businessrule.js";
12
12
  import { registerRuleLifecycleLane } from "./lanes/rule-lifecycle.js";
13
13
  import { registerRuntimeDataLane } from "./lanes/runtime-data.js";
14
14
  import { registerWorkflowLane } from "./lanes/workflow.js";
15
+ import { registerOrganizationLane } from "./lanes/organization.js";
15
16
  import { registerOnlineJsLane } from "./lanes/online-js.js";
16
17
  import { registerB2Lane } from "./lanes/b2-registration.js";
17
18
  import { registerListViewLane } from "./lanes/list-view.js";
@@ -41,6 +42,7 @@ export function buildToolRegistry() {
41
42
  registerRuleLifecycleLane(tools);
42
43
  registerRuntimeDataLane(tools);
43
44
  registerWorkflowLane(tools);
45
+ registerOrganizationLane(tools);
44
46
  registerOnlineJsLane(tools);
45
47
  registerB2Lane(tools);
46
48
  registerListViewLane(tools);
package/dist/usage-log.js CHANGED
@@ -2,6 +2,14 @@ import { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync
2
2
  import { createHash } from "node:crypto";
3
3
  import { dirname, resolve } from "node:path";
4
4
  let pending;
5
+ /**
6
+ * The project session marker: a working directory that opted into the usage log
7
+ * (`.omc-env` present) or an explicit OMC_USAGE_LOG. Session-scoped caches key
8
+ * off this so repo tests and the golden master (no `.omc-env`) stay stable.
9
+ */
10
+ export function projectSessionActive() {
11
+ return logPath() !== undefined;
12
+ }
5
13
  function logPath() {
6
14
  const explicit = process.env.OMC_USAGE_LOG;
7
15
  if (explicit)
@@ -9,6 +17,69 @@ function logPath() {
9
17
  const projectEnv = resolve(process.cwd(), ".omc-env");
10
18
  return existsSync(projectEnv) ? resolve(process.cwd(), ".omc", "usage.jsonl") : undefined;
11
19
  }
20
+ /** The args digest of the in-flight invocation (session-scoped memo key). */
21
+ export function currentUsageArgsDigest() {
22
+ return pending?.argsDigest;
23
+ }
24
+ /** The summarized args of the in-flight invocation (safe keys only). */
25
+ export function currentUsageArgSummary() {
26
+ return pending?.argSummary;
27
+ }
28
+ /**
29
+ * Was this exact invocation (same command + same args) already SUCCESSFUL in the
30
+ * current project session, with no intervening call from `invalidators` for the
31
+ * same business key?
32
+ *
33
+ * r9 (2026-10-22) called `rule.enable` with identical args 14 times; each call
34
+ * answered `alreadyEnabled:true` and minted another rule version. The log is the
35
+ * only durable session memory a CLI has, so the idempotency memo lives there.
36
+ */
37
+ export function priorIdenticalSuccess(command, argsDigest, options = {}) {
38
+ const path = logPath();
39
+ if (!path)
40
+ return undefined;
41
+ const limit = options.limit ?? 60;
42
+ const tail = readUsageTail(path, 131072).slice(-limit);
43
+ const current = pending?.argSummary ?? {};
44
+ const keyFields = options.keyFields ?? ["schemaCode", "ruleCode", "code"];
45
+ const sameKey = (record) => {
46
+ const summary = record.argSummary && typeof record.argSummary === "object" ? record.argSummary : {};
47
+ return keyFields.every((field) => {
48
+ const wanted = current[field];
49
+ if (wanted === undefined)
50
+ return true;
51
+ return String(summary[field] ?? "") === String(wanted);
52
+ });
53
+ };
54
+ let count = 0;
55
+ const requestIds = [];
56
+ for (let index = tail.length - 1; index >= 0; index -= 1) {
57
+ const record = tail[index];
58
+ const recordCommand = String(record.command ?? "");
59
+ if (options.invalidators?.includes(recordCommand)) {
60
+ // `invalidateOnAny`: a write we cannot attribute to a business key must
61
+ // still invalidate. `rule.save --args-file` and `omc script` carry no
62
+ // schemaCode/ruleCode in the usage record, so key-scoped invalidation
63
+ // misses them — that let a stale memo answer `noop:true` AFTER a save and
64
+ // left the rule disabled (r10 F2, 0.4.74 regression).
65
+ if (options.invalidateOnAny === true)
66
+ return count > 0 ? { count, requestIds } : undefined;
67
+ if (sameKey(record))
68
+ return count > 0 ? { count, requestIds } : undefined;
69
+ }
70
+ if (recordCommand !== command)
71
+ continue;
72
+ if (String(record.argsDigest ?? "") !== argsDigest)
73
+ continue;
74
+ if (String(record.status ?? "") !== "ok")
75
+ continue;
76
+ count += 1;
77
+ const requestId = record.requestId;
78
+ if (typeof requestId === "string" && requestIds.length < 3)
79
+ requestIds.push(requestId);
80
+ }
81
+ return count > 0 ? { count, requestIds } : undefined;
82
+ }
12
83
  /** The command string of the in-flight invocation (for diagnostics). */
13
84
  export function currentUsageCommand() {
14
85
  return pending?.command;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.4.73",
3
+ "version": "0.4.75",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,9 +50,9 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@stalfh233/omc-fake-platform": "0.4.73",
53
+ "@stalfh233/omc-fake-platform": "0.4.75",
54
54
  "commander": "14.0.3",
55
- "omc-core": "0.4.73",
55
+ "omc-core": "0.4.75",
56
56
  "proper-lockfile": "4.1.2",
57
57
  "write-file-atomic": "6.0.0",
58
58
  "zod": "^3.23.8",
@@ -157,7 +157,14 @@ rules:
157
157
  - **DYNAMIC 来源不要写点号**:`dataActions` 的 `currentDataItemValue` 写 `items.sealNo` 会被引擎当成**子表列集合**(DatasetImpl),标量目标收到集合 → 运行期 `submit/update` 报 50000(`WorkSheetBizField.fillClientContent: Incorrect value type`)。单跳关联字段直接写字段 code(引擎取 `.id`);子表列只能用于公式/聚合。`rule.save` 已写前拒绝(`rule-graph-create-dynamic-source-is-child-collection`)。
158
158
  - **单选字段的 `dataCondition` 在 1.1.25 不生效**:用 FIXED 值比较 RADIO/CHECKBOX/DROPDOWN 时,运行时日志「解析选框类型数据项的比较值失败」,条件被跳过、节点**无条件执行**(r8:异常归还被当成已归还回写)。`rule.save` 会把它放进 `readback.lintHazards`(`rule-graph-radio-condition-unparseable`);改用 BRANCH 判断该字段,或拆两条规则。
159
159
  - **预置 Update 规则里的跨模型回写只在 `data.update` 触发**:`rule-graph-cross-model-update-trigger-inert` 现在是 hazard(在 `readback.lintHazards`,不混进 `lintWarnings`);create/submit 不执行它。要提交时生效,把节点放进 Create 规则或流程事件规则。
160
+ - **部门/员工写面**:`org.department.create`(`deptType` 传名字或 0..3,平台按 **int 枚举**反序列化,写 `"dept"` 会 10009)→ `org.department.update`(先读再合并)/`org.department.delete`(`strike_out`);`org.user.create` 必填 `name/username/departmentId`,**平台对 `userLabelIds`/`userManagerConfigList`/`roleIds` 的 null 直接 NPE**(50000,`UserHelper.dealUserLabel:428`),工具已补空数组;`org.user.update` 同样必须先补 `deptIds` 等数组(`UserHelper.modified` 对 null 的 `deptIds` NPE)。`org.department.list` 递归展开整棵树(`/department/tree` 每次只返回一层);`org.department.users` / `org.user.info` / `org.user.departments` 用于回读。新建用户的 `active` 平台回读为 false(账号激活另有流程),不要把 active 当作创建成功标志。
161
+ - **组织写面(角色/成员/管理范围)**:界面上的「分组」= 角色(`group:false`),容器是角色组(`group:true`)。链路:`org.roleGroup.create`(**parentId 必填**,1.1.25 传空会 50000 `parentIds cannot be empty`)→ `org.role.create`(roleId 平台生成,code 随后补 `SYS<时间戳>`)→ `org.role.addUsers`(`id` 必须传 **recId**,不是账号;工具补 `type:3`/`unitType:"3"`)→ `org.role.setUserScope`(**平台覆盖语义、无回滚**:默认读旧值合并,`mode:"replace"` 才整体替换;`unitType` 传数字 3)。读回用 `org.role.users` / `org.role.userScope`。
162
+ - **`corpId` 默认写当前企业,不要写 `"other"`**(r9 2026-10-22 更正):`RoleGroupHelper.createRoleGroupModel` 只从 body 取 corpId,缺省才按 parentId 反查企业设置、查不到就 NPE(被吞成 errcode 0);`other` 是平台**合法的「非组织分组」语义**,不是兜底值。工具现在默认 `session.enterpriseId()`,并回读校验(不一致给 warning);只有明确要建非组织分组时才显式传 `corpId:"other"`。
163
+ - **`org.role.list` 必须带 `expandAll=true`**:`/role/list` 不带该参数只展开**第一个**角色组的 children(`RoleController.list`,1.1.25),所以建在别的组下的角色读回为「不存在」——r9 因此误判「角色未创建」并重复读了 8 次。工具已默认展开,`expandAll:false` 才拿容器面。
164
+ - **`org.role.update` 的平台前置条件**:`modify_role` 的 `validateRoleParam` 要求 `roleName` 与 `roleGroupId` **都非空**(否则 202001 / 202012),所以只改备注也要回填这两项——工具已从 `role/get` 自动补齐。`corpId` 在 update 里不生效,换企业只能重建角色。
165
+ - **角色参与人可用性**:r9 对照实验证明,`corpId=other` 的角色**仍能正常解析参与人**(把非发起人加进该角色后,待办正确落到该人)。所以「审批人变成发起人」要按**解析为空**排查(角色零成员 / 成员没挂部门 / 自由文本 / 角色码不存在),不要归因于 corpId。
160
166
  - **系统持久化节点是平台自带的,不要手写**:预置 `Create/Update/Delete` 行本来就带 `SYSTEM_CREATE/SYSTEM_UPDATE/SYSTEM_DELETE`(实测基线 `Start → Create(SYSTEM_CREATE) → End`)。`rule.save` 会丢弃调用方传入的 `SYSTEM_*` 并**从平台基线原样携带**那一个节点(code/name/几何都不由我们猜)。手写的后果:预置规则里类型错配 → `data.submit` 50000;自定义规则/流程事件里带系统节点 → 结束事件无当前对象,`Object <id> is not serialized`,每步仍回 DELIVERED 但什么都没写。只写平台允许配置的业务节点(UPDATE/CREATE/DELETE/BRANCH/ASSIGN/MESSAGE/BIZ_ACTION…)。
167
+ - **QUERY 节点的三个「静默空结果」入口(1.1.25 源码,r9 §14.1)**:`QueryActivityBehavior:47-50`(输入数据为空,或 `dataCondition` 过滤后为空 → `return null`)、`:78-86`(`mainConditions` 为空 → `emptyList`;目标带 `.` 时 `sheetConditions` 为空也 `emptyList`)、`ActivityBizObjectManagerImpl:172-178`(真查询 0 命中 → `emptyList`)。**三条都不写日志**,所以「节点执行开始后什么都没有」既可能是配置错也可能是 0 命中。前两条工具已在写前拦:`rule-graph-query-conditions-empty`(无条件叶,fail closed——QUERY 的空条件**不是**「查全表」,与 UPDATE 语义相反;要全表请写恒真叶)与 `rule-graph-query-datacondition-empty`(hazard)。第三条只能在运行期验证:先 `data.list` 确认目标行存在且状态已提交。
161
168
  - **取 artifact 先投影**:`artifact get <id> --summary` 看形状 → `--jq '<path>'` 取那一段;只有确实要整份时才裸取(r6:47 次整包拉取 650KB,0 次用投影)。
162
169
  - **读回产物会自动清理,也可手动清**:每次大结果落一个 artifact(机器级目录,跨项目累积;实测一轮沙盒 504 个/3.4MB)。`createArtifact` 每 24h 顺手删 7 天未动的;要立刻回收用 `omc artifact prune --older-than 7d --max-bytes 268435456`(默认预算 256MB,超出按最旧优先删),`--json` 回 removed/freedBytes/kept。
163
170
  - **核验状态用投影,别重复整条读**:`data.load --args '{"schemaCode":..,"bizObjectId":..,"fields":["sequenceStatus","<业务状态字段>"]}'` 只回指定字段(缺失项在 `missingFields`)——r4 实测同一对象被整条读 74 次;大结果先 `artifact get <id> --summary` → `--keys/--jq`,别整包内联进推理。
@@ -212,6 +219,22 @@ rules:
212
219
  - 写完后 `field.publish` → `configure_form`(同步进控件)→ **门户打开表单改触发字段的值**,断言目标控件变 `required` / `disabled`(存储回读不算验证)。
213
220
  - 恒只读:`readonlyFormula: true`。
214
221
 
222
+ ### 日期类校验(不早于今天 / 截止日不早于开始日)
223
+
224
+ **这类需求走表单的「提交校验」,不要用规则图 BRANCH。** 平台有专门的日期值类型 `today`/`yesterday`/`tomorrow`(不是"没有今天的系统变量"):
225
+
226
+ - **字段 vs 今天**(`planDate` 不得早于今天)→ `datarule.create {dataRuleType:1, checkType:2}`:
227
+ ```json
228
+ {"schemaCode":"<m>","propertyCode":"planDate","dataRuleType":1,"checkType":2,
229
+ "name":"计划日期不早于今天",
230
+ "options":{"conditions":"{planDate} 大于等于 {\"valueType\":\"today\"}",
231
+ "prompt":"计划日期不能早于今天"}}
232
+ ```
233
+ - **字段 vs 字段**(结束日不早于开始日)→ 同一 `checkType:2`,右值必须写成值对象:
234
+ `{end_date} 大于等于 {"valueType":"currentModel","value":"start_date"}`(**裸 `{start_date}` 右值不会生效**)。
235
+ - **规则图里要按"今天"分支**(例如给逾期记录打标)→ 条件叶用 `ruleValueType:"today"` + `currentSchemaDataItem:"<offset>"`(`"0"`=今天、`"-7"`=7 天前、`"30"`=30 天后,可带单位 `"-1-week"`);**不要用 `ruleValueType:"DYNAMIC"` 去和另一个日期字段比**——那会 `data.submit` 稳定 50000(`SingletonDataset.getBizObjectProperty` NPE,r10 复现)。
236
+ - 校验规则的执行面:**门户前端提交时**执行;REST `data.submit`/`data.update` **不拦**(r9 已用越界数据实测)。要用 API 兜底就得二开。
237
+
215
238
  ### 规则图的触发条件(关键)
216
239
 
217
240
  规则支持**触发条件**,不要用「两条无条件规则」表达互斥结果:
@@ -225,6 +248,7 @@ rules:
225
248
  - **触发时机**:数据事件(`Create`/`Update`/`Delete`)**只分发到平台预置 code 行**;流程相关用预置 `Available`(流程生效)/`Cancel`(流程作废),或流程事件绑定 `workflow.bindRule` 的 `end`/`cancel`/`start`/`activate`。
226
249
  **"流程结束后执行"的两个正式入口(2026-10-16 真机反馈消歧)**:① **流程设计 → 流程结束后**(对应 `workflow.bindRule` 的 `end`,绑定自定义规则承载动作节点);② **平台自带业务规则的"流程生效"(预置 `Available`)行**。两者都在**流程实例走完归档/审批通过那一刻**触发,**不是进入流程时**——进入流程时刻是 `start`/`activate`。业务要"审批通过后执行某逻辑"时只用 ①或②,**禁止**绑 `start`/`activate`,也勿误用 `Create`(提交即触发,时序等同流程发起)。
227
250
  - **预置规则身份不可改**:`Create`/`Update`/`Delete`/`Load`/`GetList`/`Available`/`Cancel` 名称由平台维护(`Create`=数据新增);传 `name` 不生效(仅自定义规则名称可改),可改的是节点名 `nodeName`。
251
+ - **节点级事件**:`workflow.bindRule` 还可传 `activityCode`+`nodeEvent`(`endActivity`=提交离开该节点、`activityApproved`/`activityRejected`、`beforeActivate`/`afterActivate`、`cancelActivity`、`jobSubmitted`/`jobRejected`),与 `handler` 互斥。
228
252
  - **流程事件只绑自定义规则**:`workflow.bindRule` 的 `ruleCodes` 只能给自定义 code——设计器的事件规则选择器不提供预置触发行(绑 `Create` 到 `end` 非法,工具 fail closed)。要「审批通过后执行某逻辑」:新建自定义规则承载动作节点再绑 `end`。
229
253
  - **审批通过/驳回回写**:通过绑 `end`(或 `Available`)、驳回绑 `cancel`(或 `Cancel`)——**不要**把两条各自 FIXED 赋值的规则都绑到 `endEventHandler`(两条都会执行、后者覆盖前者;工具会在 `bizActions.length>1` 时回读 `advisory` 提醒)。真实生产只在 `endEventHandler` 绑**一条**多分支规则图。
230
254
 
@@ -276,14 +300,15 @@ BIZ_ACTION:
276
300
  - **常量入参用 `codeType: 0`(CONST)**(如固定请求头 `X-LowCode-Corpid`):值可放 `bizCode`,也可放 `value`(工具会自动移入 `bizCode`)。平台 CONST **只读 `bizCode`**——`value`/`expressionValue` 原样存着的话,运行时取到的是字面 `null`。
277
301
  - 参数面每参数必带 `bizPropertyType`(参数类型),否则业务规则按类型匹配字段会失败、映射不到(缺失即拒绝);`externParameterType`/`description` 工具补齐。**返回值参数默认不必填(`required:false`),入参按需必填**——返回值声明为必填会经规则 designer 的 `required` 镜像强制每条输出映射也必填,是绑定摩擦的常见来源。
278
302
  - `rule.save` 自动补 `serviceName`/`serviceMethodName`/节点 `nodeCategory:"actionNode"`/映射元素 `index`(弹窗异步加载,稍候再判空)。
279
- - 配规则后读回 methodMapping,再用按钮或流程实际触发一次。
303
+ - 配规则后读回 methodMapping,再用 `rule.run` 或流程实际触发一次。
280
304
  - 服务、方法、参数不确定时拒绝猜,先读证据。
281
305
 
282
306
  ## 按钮与在线 JS
283
307
 
284
- - 直接执行规则:`useLocation: ROW` + `operateType: WITH_OUT_VIEW`。
308
+ - 直接执行规则:`useLocation: ROW` + `operateType: WITH_OUT_VIEW`,`bindAction: BUSINESS_RULE` + `targetCode: <自定义 ruleCode>`。
285
309
  - `WITH_VIEW` 是打开表单。
286
310
  - FLOW 按钮需要 workflowCode;CLI 会补安全的 actionConfig。
311
+ - **按钮规则验收用 `rule.run`**(= 按钮点击的请求):副作用用 `data.load` 另读(返回是执行前快照);规则须自定义且 enabled,被更新行须 COMPLETED(引擎不查 DRAFT)。
287
312
  - **在线 JS 分两条**:本 CLI 只做**读写与发布**——**读现存脚本用 `onlinejs.get`**(抽 customScript innerHTML 原文+SHA-256,不必拉 form.get --full 六份 JSON;据此决定 replace 还是默认 INCREMENTAL 并入),用 `onlinejs.compose` 把已定稿的 `source` 组合进平台 skeleton 的 `customScript` 再 publish,回读 `publishedHtmlJson`+SHA-256。**确认是否落地用 `summary:true`**(只回 sha/字节数/`draftPublishedInSync`,不回原文,约 600B;默认回显也已去重,不再把同一段脚本回三次)。**脚本内容的编写/增量合并不在本 CLI**:若当前项目自带在线 JS 编写规范(例如其 `.cpdocs`/技能或项目文档),遵循它;否则向用户/设计者索取已定稿的 `source`。不要在 omc 侧手写或臆造 JS 逻辑。
288
313
 
289
314
  ## 运行验收与清理
@@ -33,7 +33,7 @@ only the section for the capability you need —
33
33
  | 字段级规则 `datarule` | 字段级控件规则 | 字段级规则、控件规则、校验规则、计算拼接、显示规则、条件必填/只读/显示 | [字段级规则](#字段级规则-datarule) |
34
34
  | 业务规则 `businessrule` | 图形规则图 + **定时任务** | 业务规则、触发器、规则图、事件、BRANCH、BIZ_ACTION、**定时任务/定时/每天/周期/调度** | [业务规则](#业务规则-businessrule) |
35
35
  | 规则生命周期 `rule-lifecycle` | 规则启停删 | 启用规则、停用规则、删除规则、规则启停 | [规则生命周期](#规则生命周期-rule-lifecycle) |
36
- | 流程模板 `workflow` | 审批流程模板 | 流程、工作流、审批、节点、待办、流转、参与人、审批人 | [流程模板](#流程模板-workflow) |
36
+ | 流程模板 `workflow` | 审批流程模板 | 流程、工作流、审批、节点、待办、流转、参与人、审批人、表单操作按钮 | [流程模板](#流程模板-workflow) |
37
37
  | 表单在线 JS `onlinejs` | 表单脚本发布/回读 | 在线js、脚本、联动、onload、onvalidate | [表单在线 JS](#表单在线-js-onlinejs) |
38
38
  | 业务集成 `bizservice` | 集成服务与方法 | 集成、接口、服务、回调、对接、外部系统、datasql、restful | [业务集成](#业务集成-bizservice) |
39
39
  | 报表 `report` | 报表页 + BI 图表(**仅显式要求时**) | 报表、图表、看板、bi、dashboard(用户点名的才做) | [报表](#报表-report) |
@@ -90,13 +90,14 @@ only the section for the capability you need —
90
90
 
91
91
  ## 默认表单 `form`
92
92
 
93
- - **能做哪些事**:表单 draft-first 布局、发布、`publishedHtmlJson` 绑定读回;把已发布字段绑进默认表单。
94
- - **工具**:`form.get`、`form.draft`、`form.publish`、`configure_form`
93
+ - **能做哪些事**:表单 draft-first 布局、发布、`publishedHtmlJson` 绑定读回;把已发布字段绑进默认表单;**表单工具栏按钮**(`options.customButtonJson` → publish 生成 FORM 按钮)与按钮执行。
94
+ - **工具**:`form.get`、`form.draft`、`form.publish`、`configure_form`、`form.runButton`
95
95
  - **recipe**:`omc describe configure_form --example publish-default-form`
96
96
  - **边界 / 陷阱**:默认表单已存在,用 `configure_form`/`form.draft(rebuild)`/`form.publish(rebuild:true)` 改它,不要新建;改字段形状后刷新表单一调用 `form.publish rebuild:true`。
97
97
  - **每个模型交付前其默认表单必须已发布**:只建草稿=用户无法录入/查看。用 `configure_form` 或 `form.draft(rebuild:true)`+`form.publish` 发布。
98
98
  - **发布态以 `form.get` 的 `published`/`existDraft` 布尔为准**,不要看 `publishedControlCodes` 是否非空(草稿字段也非空,会误判)。交付前列出每个模型的 `form.get.published` 核对。
99
99
  - **写业务数据的前提是先发布模型+表单**:正确顺序 = 建模型 → 发布字段(`field.publish`)→ 建并**发布**默认表单(`form.publish`)→ 才 `data.save`/`data.submit`。不要在建/未发布状态下写业务数据。
100
+ - **表单按钮**:`form.publish {buttons:[…]}` 写 `options.customButtonJson` 并生成 `h_biz_button(triggerType=FORM)`;表单页显示还取决于流程节点 `buttonPermission.customBtns[].enabled`(设计器「表单操作」勾选)。执行用 `form.runButton`(带整条记录,区别于列表按钮的 `rule.run`)。
100
101
 
101
102
  ## 列表视图 `listview`
102
103
 
@@ -131,14 +132,14 @@ only the section for the capability you need —
131
132
 
132
133
  ## 规则生命周期 `rule-lifecycle`
133
134
 
134
- - **能做哪些事**:业务规则的启用、停用、批量停用、删除。
135
- - **工具**:`rule.enable`、`rule.disable`、`rule.delete`、`rule.disableMany`、`configure_rule`
136
- - **边界 / 陷阱**:`enabled` 是运行时真相,`status` 可能滞后。`rule.disableMany` 默认含预置行,`customOnly:true` 只停自定义。
135
+ - **能做哪些事**:业务规则的启用、停用、批量停用、删除、手动执行(按钮规则验收)。
136
+ - **工具**:`rule.enable`、`rule.disable`、`rule.delete`、`rule.disableMany`、`rule.run`、`configure_rule`
137
+ - **边界 / 陷阱**:`enabled` 是运行时真相,`status` 可能滞后。`rule.disableMany` 默认含预置行,`customOnly:true` 只停自定义。`rule.run` 只跑自定义 code 且须 enabled(预置行被拒);返回 data 是执行前快照,副作用用 `data.load` 另读;被更新行须 COMPLETED(引擎不查 DRAFT)。
137
138
 
138
139
  ## 流程模板 `workflow`
139
140
 
140
141
  - **能做哪些事**:流程模板 get_draft/get_published、create、update、publish、bindRule(事件处理器规则绑定)、delete、workitem.list、workflow.instanceActivity(实例节点状态诊断);参与人用组织对象。
141
- - **工具**:`workflow.get_draft`、`workflow.get_published`、`workflow.validate`、`workflow.create`、`workflow.update`、`workflow.publish`、`workflow.bindRule`、`workflow.delete`、`configure_workflow`、`workitem.list`、`workflow.instanceActivity`、`org.role.list`、`org.department.list`、`org.user.search`
142
+ - **工具**:`workflow.get_draft`、`workflow.setButtons`(节点「表单操作」按钮勾选)、`workflow.get_published`、`workflow.validate`、`workflow.create`、`workflow.update`、`workflow.publish`、`workflow.bindRule`、`workflow.delete`、`configure_workflow`、`workitem.list`、`workflow.instanceActivity`、`org.role.list`、`org.department.list`、`org.user.search`、`org.role.users`、`org.role.userScope`、`org.roleGroup.create`、`org.role.create`、`org.role.update`、`org.role.addUsers`、`org.role.removeUsers`、`org.role.setUserScope`、`org.department.create`、`org.department.update`、`org.department.delete`、`org.department.users`、`org.user.create`、`org.user.update`、`org.user.delete`、`org.user.info`、`org.user.departments`、`org.role.delete`、`org.roleGroup.delete`、`org.user.transferDepartment`
142
143
  - **recipe**:`omc describe workflow.bindRule --example workflow-handler-binding`
143
144
  - **边界 / 陷阱**:greenfield 首版;`publish`/`bindRule` 不得在 `ValidateMessage[]` 拒绝时报告成功。运行时 workitem(提交/审批)走 `data.submit`;想知道实例停在哪/是否错派,用 `workflow.instanceActivity`(无 status=1=真停,参与人全是发起人=解析为空退回)。
144
145
 
@@ -69,10 +69,10 @@ omc help omc-write-hazards
69
69
 
70
70
  ## 按钮
71
71
 
72
- - 列表按钮用 `listview.configure` 的 `buttons`(传 `code`/`name`/`useLocation`/`bindAction`/`targetModel`/`targetForm`…);**不要因为没找到就叫「CLI 无列表按钮能力」**(dogfood 实测该误判会假阻塞整条需求)。配完 `listview.get --full` 回读 `customButtons`。
73
- - `BUSINESS_RULE` 按钮需要 target rule 和 actionConfig 对象。
74
- - `FLOW`/`FORM`/`ADD_DATA` 的 actionConfig 必须有 `configureList: []`;FLOW 还需要 `WorkflowListData[0].workflowCode` 与 `schemaCodeVal`。CLI 只给 FLOW 的 targetCode 时会自动展开。
75
- - `WITH_VIEW` 是打开表单;`WITH_OUT_VIEW` 才是直接执行。行级按钮要用 `useLocation: ROW` 才能拿到当前行上下文。
72
+ - **列表/行级按钮**用 `listview.configure` 的 `buttons`(`useLocation: LIST|ROW`)。
73
+ - **表单按钮是另一层**:`h_biz_button.triggerType=FORM`(非 QUERY),由 `form/publish` 读 `biz_sheet.options.customButtonJson` 生成;表单页按**流程节点 `buttonPermission.customBtns[].enabled`**(「表单操作」勾选)过滤。`useLocation:FORM` 不显示。
74
+ - `SCRIPT` 需 `actionConfig.scriptData`;`BUSINESS_RULE`/`FLOW` 需 `targetCode`;`ADD_DATA`/`FORM` 目标在 `actionConfig`(`schemaCodeVal`+`formListData`+`configureList`)。
75
+ - `WITH_VIEW` 打开表单、`WITH_OUT_VIEW` 直接执行;行级按钮用 `ROW` 才有行上下文;列表按钮绑的规则用 `rule.run` 验收。
76
76
 
77
77
  ## 子表
78
78