@stalfh233/omc-cli 0.4.100 → 0.4.101

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.
@@ -446,6 +446,100 @@ export function isModernAssignVersion(version) {
446
446
  * supported alternatives (BRANCH, or two rules).
447
447
  */
448
448
  const CHOICE_CONDITION_TYPES = new Set([12, 13, 14, 15]);
449
+ /**
450
+ * 2026-10-27 用户反馈 + r24 沙盒四格矩阵(1.1.25 真机实测):
451
+ * CREATE(新增数据)节点打到**已发布流程的模型**时的流程发起开关语义——
452
+ *
453
+ * 不传 startWorkflow → 行落库、不发起流程(API 默认,实测)
454
+ * true + originatorValue "{}" → 触发表单提交 50000(fastjson expect '[', but {)
455
+ * true + 数组但 id 无效 → 50000「业务规则发起人不存在」
456
+ * true + [{id,name}] 真实用户 + targetWorkflowCode → 行落库并自动发起流程
457
+ *
458
+ * 契约:目标有已发布流程而未声明开关 → warning 教学显式做法(合法的两种选择
459
+ * 都要写明);开了开关但 originatorValue/targetWorkflowCode 不合法 → error
460
+ * 写前拒绝——那两种形状会把**触发表单的提交**整个打挂,正是 fail-closed 该拦的。
461
+ */
462
+ async function createNodeFlowStartWarnings(session, nodes) {
463
+ const creates = nodes.filter((node) => String(node.nodeType ?? "") === "CREATE" && String(node.targetObjectCode ?? "") !== "");
464
+ if (creates.length === 0)
465
+ return [];
466
+ const flowByTarget = new Map();
467
+ for (const target of [...new Set(creates.map((node) => String(node.targetObjectCode)))]) {
468
+ try {
469
+ const isWorkflow = (await wireCall(session, { path: ENDPOINTS.workflowIsWorkflow, query: { schemaCode: target } })).data === true;
470
+ if (!isWorkflow)
471
+ continue;
472
+ const row = (await wireCall(session, { path: ENDPOINTS.workflowGetBySchema, query: { schemaCode: target } })).data;
473
+ const code = row && typeof row.workflowCode === "string" && row.workflowCode ? row.workflowCode : null;
474
+ if (!code)
475
+ continue;
476
+ // get_published 原样回模板(fake 保真:未发布也回 DRAFT 形状——Ticket 77,
477
+ // 发布证据是 templateType:PUBLISH 标记,不是有数据);流程码用已解析的 code。
478
+ const published = (await wireCall(session, { path: ENDPOINTS.workflowGetPublished, query: { workflowCode: code } })).data;
479
+ const publishMarker = published !== null && published !== undefined ? String(published.templateType ?? "").toUpperCase() : "ABSENT";
480
+ if (published && (publishMarker === "" || publishMarker === "PUBLISH"))
481
+ flowByTarget.set(target, code);
482
+ }
483
+ catch {
484
+ // 读面失败不阻塞写面(与 radioConditionWarnings 同策略:读不到就不评)
485
+ }
486
+ }
487
+ const findings = [];
488
+ for (const node of creates) {
489
+ const target = String(node.targetObjectCode);
490
+ const flowCode = flowByTarget.get(target);
491
+ if (flowCode === undefined)
492
+ continue;
493
+ const nodeCode = String(node.nodeCode ?? "");
494
+ if (node.startWorkflow === undefined || node.startWorkflow === false) {
495
+ findings.push({
496
+ code: "rule-create-target-has-flow",
497
+ nodeCode,
498
+ key: "startWorkflow",
499
+ severity: "warning",
500
+ message: `CREATE 目标模型 ${target} 已发布流程 ${flowCode}:不传 startWorkflow 时规则建行**不发起流程**(实测 2026-10-27,行落库即止)。` +
501
+ `要让规则新建的行直接进流程:显式 startWorkflow:true + targetWorkflowCode:"${flowCode}",且 originatorValue 必须是用户对象数组 [{"id":"<userId>","name":"<名>"}](id 用 org.user.search 取;传对象 {} 或无效 id 会让**触发表单的提交**直接 50000,实测)。` +
502
+ `注意:设计器里手工编辑该节点可打开此开关——API 与设计器两侧配置可能漂移,改完用 rule.get 回读核对 startWorkflow/targetWorkflowCode。`,
503
+ });
504
+ continue;
505
+ }
506
+ const raw = typeof node.originatorValue === "string" ? node.originatorValue.trim() : node.originatorValue;
507
+ let parsed = raw;
508
+ if (typeof raw === "string" && raw.length > 0) {
509
+ try {
510
+ parsed = JSON.parse(raw);
511
+ }
512
+ catch {
513
+ parsed = null;
514
+ }
515
+ }
516
+ const originatorValid = Array.isArray(parsed)
517
+ && parsed.length > 0
518
+ && parsed.every((entry) => entry && typeof entry === "object" && typeof entry.id === "string" && entry.id.length > 0);
519
+ if (!originatorValid) {
520
+ findings.push({
521
+ code: "rule-create-startworkflow-originator-shape",
522
+ nodeCode,
523
+ key: "originatorValue",
524
+ message: `CREATE "${nodeCode}" 开了 startWorkflow 但 originatorValue 不是用户对象数组:期望 [{"id":"<userId>","name":"<名>"}]。` +
525
+ `实测(2026-10-27,1.1.25):传对象 {} → 触发表单提交 50000(fastjson "expect '[', but {");数组里 id 无效 → 50000「业务规则发起人不存在」。` +
526
+ `id 用 org.user.search 取真实用户后再写。`,
527
+ });
528
+ }
529
+ // 「startWorkflow:true 必须带 targetWorkflowCode」由既有的离线同步检查
530
+ // rule-graph-create-workflow-code 负责(rule-graph.ts:2055),这里不重复——
531
+ // 但流程码要能对上:给了但不是该模型的已发布流程时补一条 error。
532
+ if (typeof node.targetWorkflowCode === "string" && node.targetWorkflowCode.length > 0 && node.targetWorkflowCode !== flowCode) {
533
+ findings.push({
534
+ code: "rule-create-startworkflow-target-mismatch",
535
+ nodeCode,
536
+ key: "targetWorkflowCode",
537
+ message: `CREATE "${nodeCode}" 的 targetWorkflowCode="${node.targetWorkflowCode}" 不是目标模型 ${target} 的已发布流程(应为 "${flowCode}")——发到不匹配的流程会运行期失败。`,
538
+ });
539
+ }
540
+ }
541
+ return findings;
542
+ }
449
543
  async function radioConditionWarnings(session, schemaCode, nodes) {
450
544
  const sources = new Set();
451
545
  const collect = (condition) => {
@@ -659,6 +753,8 @@ export async function executeRuleSaveWrite(session, args) {
659
753
  const blockers = [...prepared.warnings, ...validateRuleNodeGraph(preparedGraph, args.ruleCode, typeof args.schemaCode === "string" ? args.schemaCode : undefined, carriedNodeCodes ? { carriedNodeCodes } : {})];
660
754
  // 1.2: needs the published field types (pure lint cannot see them).
661
755
  blockers.push(...(await radioConditionWarnings(session, args.schemaCode, Array.isArray(preparedGraph.nodes) ? preparedGraph.nodes : [])));
756
+ // r24 反馈(2026-10-27 真机四格矩阵):CREATE 目标带已发布流程时的发起开关教学/拦截。
757
+ blockers.push(...(await createNodeFlowStartWarnings(session, Array.isArray(preparedGraph.nodes) ? preparedGraph.nodes : [])));
662
758
  // Ticket 113: warning-severity findings (e.g. unknown mapping keys) do not
663
759
  // gate the write; they surface in the readback as lintWarnings.
664
760
  const warningFindings = blockers.filter((blocker) => blocker.severity === "warning");
@@ -0,0 +1,41 @@
1
+ # 截图占位目录(尼西实战案例)
2
+
3
+ `omc-introduction.md` 的「15. 实战一:尼西双表单与流程搭建」章节里引用了 9 张截图。
4
+ 把截图按下面文件名放进本目录即可,文档中的占位说明可逐条删除。
5
+
6
+ ## 客户源需求(3 张)
7
+
8
+ | 文件名 | 内容 |
9
+ |---|---|
10
+ | `01-requirement-ft-sheet.png` | 客户 Excel 的 `FT` Sheet(字段清单 / 必填 / LotClass 显隐) |
11
+ | `02-requirement-fp-sheet.png` | 客户 Excel 的 `FP` Sheet |
12
+ | `03-requirement-flow.png` | 客户给的流程图(FT/FP,含 `SubConRootCause=1` 分支) |
13
+
14
+ ## 对话截图(6 张)
15
+
16
+ | 文件名 | 内容 |
17
+ |---|---|
18
+ | `04-chat-app-create.png` | 确认新建应用 + `app.create ok` |
19
+ | `05-chat-intent-apply.png` | `intent apply` 返回 `DELIVERED` + `equivalent: true`(89 个动作) |
20
+ | `06-chat-workflow-role.png` | 流程节点与角色路由(PIC→蔡依涛 / MOS→赵益 / AOSIT / AOSENG) |
21
+ | `07-chat-permission-matrix.png` | 节点字段权限矩阵(本节点 + 之前所有节点可见) |
22
+ | `08-chat-biz-integration.png` | `service.register` 注册 productType 服务 + `method.test` 返回 `PRODUCT_TYPE=MOS` |
23
+ | `09-chat-onlinejs-publish.png` | `onlinejs.compose` 返回 `published: true` |
24
+
25
+ ## 替换方式
26
+
27
+ 文档里每处占位是:
28
+
29
+ ```
30
+ > **[截图 01|客户源需求:FT 表单规格]**
31
+ > 占位文件:`docs/images/omc-nixi/01-requirement-ft-sheet.png`
32
+ > 建议内容:...
33
+ ```
34
+
35
+ 落地后改成标准图片引用即可:
36
+
37
+ ```markdown
38
+ ![客户源需求:FT 表单规格](images/omc-nixi/01-requirement-ft-sheet.png)
39
+ ```
40
+
41
+ 建议截图宽度 ≥ 1200px,避免中文文字模糊。