@stalfh233/omc-cli 0.4.61 → 0.4.63
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.
|
@@ -300,10 +300,10 @@ async function stampChildRowStatuses(session, schemaCode, formData) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
catch {
|
|
303
|
-
return formData;
|
|
303
|
+
return { formData, childCodes: [] };
|
|
304
304
|
}
|
|
305
305
|
if (childCodes.size === 0)
|
|
306
|
-
return formData;
|
|
306
|
+
return { formData, childCodes: [] };
|
|
307
307
|
let changed = false;
|
|
308
308
|
const next = { ...formData };
|
|
309
309
|
for (const code of childCodes) {
|
|
@@ -321,7 +321,7 @@ async function stampChildRowStatuses(session, schemaCode, formData) {
|
|
|
321
321
|
return { ...row, rowStatus: hasId ? "Modified" : "Added" };
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
|
-
return changed ? next : formData;
|
|
324
|
+
return { formData: changed ? next : formData, childCodes: [...childCodes] };
|
|
325
325
|
}
|
|
326
326
|
/** Runtime business-object data keyed by field code. */
|
|
327
327
|
export function registerRuntimeDataLane(tools) {
|
|
@@ -570,6 +570,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
570
570
|
schemaCode: z.string(),
|
|
571
571
|
bizObjectId: z.string({ required_error: "bizObjectId 必填(参数名就是 bizObjectId——不是 id 也不是 objectId,r44 两个 agent 都猜了 objectId;取值来自 data.submit 读回的 objectId/sequenceNo 或 data.list 的 row.bizObjectId)" }),
|
|
572
572
|
full: z.boolean().optional().describe("返回未裁剪的完整业务对象(含 owner/creater 等系统字段;默认返回业务字段+身份的精简记录)"),
|
|
573
|
+
fields: z.array(z.string().min(1)).optional().describe("只保留列出的字段——核验状态时传 [\"sequenceStatus\",\"<业务状态字段>\"],别整条 data.load(r4-training 2026-10-20:同一对象被读 74 次);缺失字段回在 missingFields"),
|
|
573
574
|
capability: capabilityArg,
|
|
574
575
|
},
|
|
575
576
|
handler: async (ctx, args, self) => {
|
|
@@ -603,7 +604,27 @@ export function registerRuntimeDataLane(tools) {
|
|
|
603
604
|
}
|
|
604
605
|
if (includeSchema)
|
|
605
606
|
ctx.schemaCache.add(args.schemaCode);
|
|
606
|
-
|
|
607
|
+
// r4-training 2026-10-20: most repeat loads only ask "did it move?".
|
|
608
|
+
// Trim on the CLI side so the caller pays for the state bits, not the
|
|
609
|
+
// whole record (74 reads of one object in the r4 session).
|
|
610
|
+
let missingFields;
|
|
611
|
+
if (Array.isArray(args.fields) && args.fields.length > 0) {
|
|
612
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
613
|
+
const source = data;
|
|
614
|
+
const projected = {};
|
|
615
|
+
const missing = [];
|
|
616
|
+
for (const field of args.fields) {
|
|
617
|
+
if (field in source)
|
|
618
|
+
projected[field] = source[field];
|
|
619
|
+
else
|
|
620
|
+
missing.push(field);
|
|
621
|
+
}
|
|
622
|
+
data = projected;
|
|
623
|
+
if (missing.length > 0)
|
|
624
|
+
missingFields = missing;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return { schemaCode: args.schemaCode, bizObjectId: args.bizObjectId, exists: data != null, ...(missingFields ? { missingFields, availableFields: data && typeof data === "object" ? Object.keys(data) : [] } : {}), ...(includeSchema ? {} : { schemaCached: true }), data };
|
|
607
628
|
}, { full: args.full === true, target: `data.load:${args.schemaCode}:${args.bizObjectId}` });
|
|
608
629
|
},
|
|
609
630
|
}));
|
|
@@ -625,7 +646,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
625
646
|
message: "submit:true 与 workflowCode 互斥——workflowCode 是流程草稿两段式的第一段(返回 workItemId 后用 data.submit 提交);无流程模型的一调完成态用 submit:true 且不带 workflowCode",
|
|
626
647
|
});
|
|
627
648
|
const { sheetCode, resolvedFrom } = await resolveFormSheetCode(session, { schemaCode: args.schemaCode, sheetCode: args.sheetCode });
|
|
628
|
-
const formData = await stampChildRowStatuses(session, args.schemaCode, args.formData);
|
|
649
|
+
const formData = (await stampChildRowStatuses(session, args.schemaCode, args.formData)).formData;
|
|
629
650
|
if (typeof args.workflowCode === "string" && args.workflowCode.trim()) {
|
|
630
651
|
// Portal draft-save shape. The row id travels in data.id (the portal
|
|
631
652
|
// form SDK receives a pre-generated id from form/load and echoes it);
|
|
@@ -787,7 +808,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
787
808
|
message: "no live business object with this id; refusing to update (the platform would otherwise insert a new row)",
|
|
788
809
|
});
|
|
789
810
|
}
|
|
790
|
-
const formData = await stampChildRowStatuses(session, args.schemaCode, args.formData);
|
|
811
|
+
const formData = (await stampChildRowStatuses(session, args.schemaCode, args.formData)).formData;
|
|
791
812
|
const performed = await wireCall(session, {
|
|
792
813
|
path: ENDPOINTS.runtimeFormSave,
|
|
793
814
|
method: "POST",
|
|
@@ -886,6 +907,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
886
907
|
workItemId: z.string().optional(),
|
|
887
908
|
bizObjectId: z.string().optional().describe("existing record id; REQUIRED for workItem approval submits (formType=1 + workItemId) — the submit's bizObject must reference the in-flight record or the engine NPEs (DataChangeHandlerImpl.changeBizObject)"),
|
|
888
909
|
agree: z.boolean().optional(),
|
|
910
|
+
allowChildRowAppend: z.boolean().optional().describe("默认 false:审批提交(带 workItemId/workflowInstanceId)时 formData 含子表数组会被拒——平台按追加插行,每次提交重复一份。确实要追加子行时才置 true。"),
|
|
889
911
|
approval: z.record(z.string(), z.unknown()).optional(),
|
|
890
912
|
runMode: z.string().optional(),
|
|
891
913
|
isSimulative: z.boolean().optional(),
|
|
@@ -905,7 +927,27 @@ export function registerRuntimeDataLane(tools) {
|
|
|
905
927
|
});
|
|
906
928
|
}
|
|
907
929
|
const { sheetCode, resolvedFrom } = await resolveFormSheetCode(session, { schemaCode: args.schemaCode, sheetCode: args.sheetCode });
|
|
908
|
-
const
|
|
930
|
+
const stampedSubmit = await stampChildRowStatuses(session, args.schemaCode, args.formData);
|
|
931
|
+
const formData = stampedSubmit.formData;
|
|
932
|
+
// r5-asset 2026-10-20: the platform APPENDS child rows carried in an
|
|
933
|
+
// approval submit's formData (no upsert by row id, no error). Live: an
|
|
934
|
+
// approval that echoed the whole form twice turned 2 detail rows into 6
|
|
935
|
+
// and 30000 into 90000 while every call answered errcode 0 — node-level
|
|
936
|
+
// `editable:false` does not stop it server-side. Refuse before the wire.
|
|
937
|
+
if ((hasWorkItem || hasInstance) && args.allowChildRowAppend !== true) {
|
|
938
|
+
const carried = stampedSubmit.childCodes.filter((code) => {
|
|
939
|
+
const value = args.formData[code];
|
|
940
|
+
return Array.isArray(value) && value.length > 0;
|
|
941
|
+
});
|
|
942
|
+
if (carried.length > 0)
|
|
943
|
+
throw new ToolRefusalError({
|
|
944
|
+
status: "refused",
|
|
945
|
+
blocker: "submit-approval-child-rows",
|
|
946
|
+
schemaCode: args.schemaCode,
|
|
947
|
+
childTables: carried,
|
|
948
|
+
message: `审批提交(workItemId/workflowInstanceId)的 formData 不要带子表数组:${carried.join("、")}。平台按「追加行」处理——每提交一次就再插一份(真机:2 行 → 6 行,30000 → 90000),且不报错、审批节点的 editable:false 也拦不住。只传主表字段;确实要在审批中追加子行时显式传 allowChildRowAppend:true。`,
|
|
949
|
+
});
|
|
950
|
+
}
|
|
909
951
|
// Portal required-field semantics: the REST submit does NOT enforce them,
|
|
910
952
|
// so a missing required value must be REPORTED, not silently accepted as a
|
|
911
953
|
// portal-valid submit (dogfood R3). Best-effort read; a failure never
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -691,7 +691,7 @@ function validateWorkflowRender(template, tool) {
|
|
|
691
691
|
* chain order" — reusing any edge whose endpoints survived. A draft whose
|
|
692
692
|
* (possibly branching) graph is untouched passes the chain check verbatim.
|
|
693
693
|
*/
|
|
694
|
-
function completeWorkflowChain(rules, activities) {
|
|
694
|
+
export function completeWorkflowChain(rules, activities) {
|
|
695
695
|
const codes = activities.map((activity) => String(activity.activityCode ?? "")).filter((code) => code.length > 0);
|
|
696
696
|
if (codes.length < 2)
|
|
697
697
|
return rules;
|
|
@@ -712,13 +712,24 @@ function completeWorkflowChain(rules, activities) {
|
|
|
712
712
|
}
|
|
713
713
|
for (const code of codes) {
|
|
714
714
|
const type = typeOf(code);
|
|
715
|
-
if (type !== "START" && (inbound.get(code) ?? 0)
|
|
715
|
+
if (type !== "START" && (inbound.get(code) ?? 0) < 1)
|
|
716
716
|
valid = false;
|
|
717
|
-
if (type !== "END" && (outbound.get(code) ?? 0)
|
|
717
|
+
if (type !== "END" && (outbound.get(code) ?? 0) < 1)
|
|
718
718
|
valid = false;
|
|
719
719
|
}
|
|
720
720
|
if (valid)
|
|
721
721
|
return rules;
|
|
722
|
+
// r5-asset 2026-10-20: a node with TWO outgoing edges is a conditional
|
|
723
|
+
// branch (condition edge + utilizeElse fallback), NOT an invalid chain. The
|
|
724
|
+
// old rule demanded outbound === 1, so every branched graph fell through to
|
|
725
|
+
// the linear rebuild below — which maps consecutive activity codes and
|
|
726
|
+
// silently DROPS the else edge. Live symptom: a workflow.update that sent
|
|
727
|
+
// activities without routes erased A3→A5, every low-amount instance stalled
|
|
728
|
+
// in PROCESSING with errcode 0, and the only trace was a missing edge in
|
|
729
|
+
// get_published. Never linearize a graph that already branches.
|
|
730
|
+
const branched = [...outbound.values()].some((count) => count > 1);
|
|
731
|
+
if (branched)
|
|
732
|
+
return rules;
|
|
722
733
|
const byEdge = new Map(rules.map((rule) => [`${rule.preActivityCode}->${rule.postActivityCode}`, rule]));
|
|
723
734
|
return codes.slice(0, -1).map((code, index) => byEdge.get(`${code}->${codes[index + 1]}`) ?? { preActivityCode: code, postActivityCode: codes[index + 1] });
|
|
724
735
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.63",
|
|
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.
|
|
53
|
+
"@stalfh233/omc-fake-platform": "0.4.63",
|
|
54
54
|
"commander": "14.0.3",
|
|
55
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.63",
|
|
56
56
|
"proper-lockfile": "4.1.2",
|
|
57
57
|
"write-file-atomic": "6.0.0",
|
|
58
58
|
"zod": "^3.23.8",
|
|
@@ -150,6 +150,7 @@ rules:
|
|
|
150
150
|
|
|
151
151
|
- **两条路**:模型+表单+流程一揽子交付走**意图**(`intent` 的 `workflow` 能力,声明式一次 apply);只动流程或想逐步控制走**低层链** `workflow.create → workflow.update(权限/节点)→ workflow.publish`,标准链配方见 `omc describe workflow.update`(含多级审批/participant 三形态/自动回填说明)。流程权限细节见下节。
|
|
152
152
|
- 一个模型只能有一个流程;已有流程的模型直接 `workflow.update`,不要另建。
|
|
153
|
+
- **核验状态用投影,别重复整条读**:`data.load --args '{"schemaCode":..,"bizObjectId":..,"fields":["sequenceStatus","<业务状态字段>"]}'` 只回指定字段(缺失项在 `missingFields`)——r4 实测同一对象被整条读 74 次;大结果先 `artifact get <id> --summary` → `--keys/--jq`,别整包内联进推理。
|
|
153
154
|
- **参与人解析失败分两种,别当成同一种(1.1.25 实测)**:① **解析为空**(角色零成员、成员未挂部门、自由文本、角色码不存在)→ 引擎把任务**退回发起人**,流程继续,症状是「审批人显示成我自己」,`data.submit` 与 `workitem.list` 都正常;② **参与人函数未注册**(`FindUsersByRoleFunction` / `FindUsersSpecByRoleFunction`,1.1.25 未内置)→ `FunctionNotFoundException`、不生成待办、实例卡 PROCESSING 而接口仍回 `errCode 0`。区分用 `omc call workflow.instanceActivity --args '{"workflowInstanceId":"<id>"}'`:无 `status:1` 节点=卡住;`status:1` 且参与人全是发起人=`diagnosis.warnings` 给 `participant-fallback-to-originator`。不要从 `workflow.get_published` 的摘要回抄函数式参与人;内置写法只有 `FindUserByRole(org, roleCode[, order])`,跨部门/全局角色优先用参与者对象数组(角色 id)。
|
|
154
155
|
|
|
155
156
|
## 流程字段权限
|
|
@@ -125,3 +125,18 @@ omc call field.publish --env local --args '{"schemaCode":"expense"}'
|
|
|
125
125
|
## 固定验收顺序
|
|
126
126
|
|
|
127
127
|
执行阶段以 `omc-five-piece-flow` 为准;写面安全只额外强调两点:写前先 `commands/describe/contract` + dry-run 静态校验,写后权威 readback,必要时再用 CLI 触发运行验证。
|
|
128
|
+
|
|
129
|
+
## 审批提交回传子表 = 追加行(真机 2026-10-20)
|
|
130
|
+
|
|
131
|
+
带 `workItemId` / `workflowInstanceId` 的 `data.submit` 里放子表数组,平台按**追加**处理,不按行 `id` upsert、也不报错:
|
|
132
|
+
2 行明细回传两次 → 6 行,`totalValue` 30000 → 90000,审批节点的 `editable:false` 拦不住服务端写入。
|
|
133
|
+
**只传主表字段**;确需追加行时显式 `allowChildRowAppend:true`(0.4.63 起默认拒绝,blocker `submit-approval-child-rows`)。
|
|
134
|
+
收尾时前后各读一次子表行数确认未变。
|
|
135
|
+
|
|
136
|
+
## 跨表回写只能取主表字段(真机 2026-10-20)
|
|
137
|
+
|
|
138
|
+
- `id = {子表code}.asset` / `...asset.id`:保存发布全绿,**运行不命中**,无日志。
|
|
139
|
+
- 请求体直接引用子表关联(CREATE 的 dataActions 等):提交报 `550010 业务规则使用的数据项不存在`。
|
|
140
|
+
- BRANCH 后接两个跨表 UPDATE:`50000`。
|
|
141
|
+
- 固定 id / 主表字段条件:可运行。
|
|
142
|
+
逐件联动(一张单多件资产)纯配置做不到,走二开 BIZ_ACTION 或人工。
|