@stalfh233/omc-cli 0.0.0-stage → 0.4.10
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/LICENSE +21 -0
- package/README.md +293 -3
- package/coverage/m1-coverage-manifest-v1.json +1278 -0
- package/dist/approval-token.js +102 -0
- package/dist/args.js +25 -0
- package/dist/artifacts.js +119 -0
- package/dist/bench/call-face-eval.js +256 -0
- package/dist/bench/context-attribution.js +151 -0
- package/dist/bench/discovery-cost-eval.js +230 -0
- package/dist/bench/driver.js +81 -0
- package/dist/bench/evals.js +236 -0
- package/dist/bench/fake-http-server.js +65 -0
- package/dist/bench/instrument.js +87 -0
- package/dist/bench/intent-face-eval.js +343 -0
- package/dist/bench/run.js +166 -0
- package/dist/bench/scenario.js +343 -0
- package/dist/bench/types.js +76 -0
- package/dist/bi-wire.js +41 -0
- package/dist/bizservice-config.js +581 -0
- package/dist/call.js +153 -0
- package/dist/capability-absences.js +23 -0
- package/dist/capability-overview.js +492 -0
- package/dist/capability-shape.js +154 -0
- package/dist/cli-contract.js +70 -0
- package/dist/cli-output.js +73 -0
- package/dist/cli.js +1418 -0
- package/dist/code-rules.js +69 -0
- package/dist/command-transport.js +155 -0
- package/dist/config-store.js +195 -0
- package/dist/context.js +21 -0
- package/dist/contract-consistency.js +66 -0
- package/dist/contract-resources.js +62 -0
- package/dist/coverage-consistency.js +62 -0
- package/dist/coverage-registry.js +76 -0
- package/dist/coverage.js +130 -0
- package/dist/data-list-filter.js +114 -0
- package/dist/discovery.js +390 -0
- package/dist/endpoints.js +154 -0
- package/dist/environment-policy.js +26 -0
- package/dist/execution-metadata.js +1092 -0
- package/dist/fake/app.js +45 -0
- package/dist/fake/b2-registration.js +594 -0
- package/dist/fake/businessrule.js +242 -0
- package/dist/fake/datarule.js +72 -0
- package/dist/fake/dictionary.js +108 -0
- package/dist/fake/environment.js +30 -0
- package/dist/fake/field.js +167 -0
- package/dist/fake/form.js +101 -0
- package/dist/fake/index.js +121 -0
- package/dist/fake/list-view.js +289 -0
- package/dist/fake/model.js +136 -0
- package/dist/fake/online-js.js +18 -0
- package/dist/fake/report.js +253 -0
- package/dist/fake/routes.js +47 -0
- package/dist/fake/rule-lifecycle.js +37 -0
- package/dist/fake/runtime-data.js +367 -0
- package/dist/fake/state.js +67 -0
- package/dist/fake/workflow.js +364 -0
- package/dist/field-change.js +200 -0
- package/dist/field-families.js +896 -0
- package/dist/form-layout.js +111 -0
- package/dist/form-support.js +821 -0
- package/dist/goal-routes.js +468 -0
- package/dist/governed-execution.js +87 -0
- package/dist/human-summary.js +212 -0
- package/dist/identity.js +62 -0
- package/dist/intent/baseline.js +57 -0
- package/dist/intent/capabilities/bizservice.js +274 -0
- package/dist/intent/capabilities/businessrule.js +493 -0
- package/dist/intent/capabilities/datarule.js +187 -0
- package/dist/intent/capabilities/field.js +545 -0
- package/dist/intent/capabilities/form.js +136 -0
- package/dist/intent/capabilities/index.js +64 -0
- package/dist/intent/capabilities/listview.js +157 -0
- package/dist/intent/capabilities/model.js +100 -0
- package/dist/intent/capabilities/onlinejs.js +108 -0
- package/dist/intent/capabilities/report.js +355 -0
- package/dist/intent/capabilities/workflow.js +458 -0
- package/dist/intent/capability.js +6 -0
- package/dist/intent/cli.js +91 -0
- package/dist/intent/compare.js +56 -0
- package/dist/intent/compiler.js +79 -0
- package/dist/intent/dsl.js +129 -0
- package/dist/intent/plan-file.js +63 -0
- package/dist/intent/readback.js +65 -0
- package/dist/intent/schema.js +158 -0
- package/dist/intent/validation.js +30 -0
- package/dist/intent/yaml.js +315 -0
- package/dist/json-column.js +68 -0
- package/dist/lanes/app-contract.js +95 -0
- package/dist/lanes/app-coverage.js +16 -0
- package/dist/lanes/app.js +174 -0
- package/dist/lanes/apply-changes.js +231 -0
- package/dist/lanes/b2-registration-contract.js +292 -0
- package/dist/lanes/b2-registration-coverage.js +48 -0
- package/dist/lanes/b2-registration.js +1187 -0
- package/dist/lanes/businessrule-contract.js +232 -0
- package/dist/lanes/businessrule-coverage.js +16 -0
- package/dist/lanes/businessrule.js +221 -0
- package/dist/lanes/contract-support.js +65 -0
- package/dist/lanes/coverage-declaration.js +9 -0
- package/dist/lanes/datarule-contract.js +155 -0
- package/dist/lanes/datarule-coverage.js +19 -0
- package/dist/lanes/datarule-protocol.js +308 -0
- package/dist/lanes/datarule.js +818 -0
- package/dist/lanes/dictionary-contract.js +78 -0
- package/dist/lanes/dictionary-coverage.js +24 -0
- package/dist/lanes/dictionary.js +235 -0
- package/dist/lanes/environment-contract.js +70 -0
- package/dist/lanes/environment-coverage.js +14 -0
- package/dist/lanes/environment.js +163 -0
- package/dist/lanes/field-contract.js +223 -0
- package/dist/lanes/field-coverage.js +27 -0
- package/dist/lanes/field.js +374 -0
- package/dist/lanes/form-contract.js +97 -0
- package/dist/lanes/form-coverage.js +16 -0
- package/dist/lanes/form.js +185 -0
- package/dist/lanes/lane-ids.js +34 -0
- package/dist/lanes/list-view-contract.js +175 -0
- package/dist/lanes/list-view-coverage.js +20 -0
- package/dist/lanes/list-view-shapes.js +1207 -0
- package/dist/lanes/list-view.js +578 -0
- package/dist/lanes/meta-contract.js +85 -0
- package/dist/lanes/meta.js +255 -0
- package/dist/lanes/model-contract.js +168 -0
- package/dist/lanes/model-coverage.js +20 -0
- package/dist/lanes/model.js +986 -0
- package/dist/lanes/online-js-contract.js +99 -0
- package/dist/lanes/online-js-coverage.js +28 -0
- package/dist/lanes/online-js.js +127 -0
- package/dist/lanes/report-contract.js +144 -0
- package/dist/lanes/report-coverage.js +21 -0
- package/dist/lanes/report.js +476 -0
- package/dist/lanes/rule-graph.js +1846 -0
- package/dist/lanes/rule-lifecycle-contract.js +92 -0
- package/dist/lanes/rule-lifecycle-coverage.js +20 -0
- package/dist/lanes/rule-lifecycle.js +176 -0
- package/dist/lanes/runtime-data-contract.js +264 -0
- package/dist/lanes/runtime-data-coverage.js +25 -0
- package/dist/lanes/runtime-data.js +1054 -0
- package/dist/lanes/workflow-contract.js +223 -0
- package/dist/lanes/workflow-coverage.js +40 -0
- package/dist/lanes/workflow.js +1813 -0
- package/dist/online-js-layout.js +58 -0
- package/dist/online-js-source.js +276 -0
- package/dist/package-tool.js +51 -0
- package/dist/package.js +73 -0
- package/dist/plan.js +73 -0
- package/dist/read.js +144 -0
- package/dist/redact.js +28 -0
- package/dist/rule-support.js +587 -0
- package/dist/runtime-support.js +134 -0
- package/dist/server.js +92 -0
- package/dist/session-manager.js +30 -0
- package/dist/session.js +149 -0
- package/dist/skills.js +112 -0
- package/dist/support.js +98 -0
- package/dist/tool-types.js +127 -0
- package/dist/tools.js +59 -0
- package/dist/usage-log.js +197 -0
- package/dist/wire.js +287 -0
- package/dist/workflow-support.js +99 -0
- package/dist/write-lease.js +26 -0
- package/dist/write-lock.js +109 -0
- package/dist/write.js +176 -0
- package/dist/zip.js +156 -0
- package/docs/tool-surface-map.md +42 -0
- package/package.json +71 -6
- package/skills/omc-acceptance-criteria.md +47 -0
- package/skills/omc-business-configuration.md +257 -0
- package/skills/omc-capabilities.md +197 -0
- package/skills/omc-capability-scouting.md +55 -0
- package/skills/omc-config-draft-review.md +171 -0
- package/skills/omc-five-piece-flow.md +35 -0
- package/skills/omc-glossary.md +86 -0
- package/skills/omc-refusals.md +110 -0
- package/skills/omc-requirement-analysis.md +161 -0
- package/skills/omc-requirement-vocabulary.md +51 -0
- package/skills/omc-start-here.md +82 -0
- package/skills/omc-tool-selection.md +119 -0
- package/skills/omc-write-hazards.md +87 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ENDPOINTS } from "../endpoints.js";
|
|
3
|
+
import { capabilityArg, environmentArg } from "../args.js";
|
|
4
|
+
import { governedRead } from "../read.js";
|
|
5
|
+
import { writeTool } from "../write.js";
|
|
6
|
+
import { encodeJsonColumn } from "../json-column.js";
|
|
7
|
+
import { defineTool, ToolRefusalError } from "../tool-types.js";
|
|
8
|
+
import { wireCall, wireRows, WireError } from "../wire.js";
|
|
9
|
+
import { resolveModelTreeNode } from "./model.js";
|
|
10
|
+
import { SYSTEM_FIELD_CODES } from "../field-families.js";
|
|
11
|
+
import { LIST_VIEW_LANE_CONTRACT } from "./list-view-contract.js";
|
|
12
|
+
import { assertListViewConfigureShape, decodeActionRow, decodeButtonRow, decodeConditionRow, decodeGantt, decodePresentation, decodeSortRow, listViewActionArg, listViewButtonArg, listViewConditionArg, listViewGanttArg, listViewMultiTableHeaderArg, listViewOptionsArg, listViewPresentationArg, listViewQueryLayerArg, listViewSortArg, mergeActions, PLATFORM_DEFAULT_LIST_ACTIONS, normalizeActions, normalizeButtons, normalizeConditions, normalizeGantt, normalizePresentation, normalizeSorts, parseConditionOptions, presentationTypeArg, readArray, resolveFieldRef, buttonsRenderCompat, columnsRenderCompat, } from "./list-view-shapes.js";
|
|
13
|
+
// Ticket 53: every shape (enum/zod/encode/decode/lint) lives once in
|
|
14
|
+
// ./list-view-shapes.ts and is re-exported for existing import paths.
|
|
15
|
+
export { assertListViewConfigureShape } from "./list-view-shapes.js";
|
|
16
|
+
const LIST_VIEW_LANE = LIST_VIEW_LANE_CONTRACT.lane;
|
|
17
|
+
const LIST_VIEW_TOOLS = LIST_VIEW_LANE_CONTRACT.tools;
|
|
18
|
+
function fieldOptions(options) {
|
|
19
|
+
if (typeof options === "string") {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(options);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return options && typeof options === "object" ? options : {};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* QueryRuntimeController.parseNumber keys its formatting off displayFormat with
|
|
31
|
+
* no case 0 (valid values start at 1): 1=integer, 2=1dp, 3=2dp, 11=3dp,
|
|
32
|
+
* 12=4dp, 27..30=5..8dp; currency 7/15/16/31..34; percent 4/5/6/13/14.
|
|
33
|
+
*/
|
|
34
|
+
export function numericDisplayFormat(options) {
|
|
35
|
+
const opts = fieldOptions(options);
|
|
36
|
+
const format = String(opts.format ?? "decimal");
|
|
37
|
+
const scale = Number(opts.scale ?? 0) || 0;
|
|
38
|
+
const decimalByScale = { 0: 1, 1: 2, 2: 3, 3: 11, 4: 12, 5: 27, 6: 28, 7: 29, 8: 30 };
|
|
39
|
+
const currencyByScale = { 2: 7, 3: 15, 4: 16, 5: 31, 6: 32, 7: 33, 8: 34 };
|
|
40
|
+
const percentByScale = { 0: 4, 1: 5, 2: 6, 3: 13, 4: 14 };
|
|
41
|
+
if (format === "currency" || format === "rmb" || format === "¥")
|
|
42
|
+
return currencyByScale[scale] ?? 7;
|
|
43
|
+
if (format === "percent" || format === "percentage")
|
|
44
|
+
return percentByScale[scale] ?? 6;
|
|
45
|
+
if (format === "dollar" || format === "$")
|
|
46
|
+
return 8;
|
|
47
|
+
return decimalByScale[scale] ?? 3;
|
|
48
|
+
}
|
|
49
|
+
/** QueryRuntimeController.parseDate: 1=yyyy-MM-dd, 2=datetime, 3=minute, 4=yyyy-MM. */
|
|
50
|
+
export function dateDisplayFormat(options) {
|
|
51
|
+
const format = String(fieldOptions(options).format ?? "YYYY-MM-DD");
|
|
52
|
+
if (/HH:mm:ss/i.test(format))
|
|
53
|
+
return 2;
|
|
54
|
+
if (/HH:mm$/i.test(format))
|
|
55
|
+
return 3;
|
|
56
|
+
if (/^YYYY-MM$/i.test(format))
|
|
57
|
+
return 4;
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
function columnDisplayFormat(type, options) {
|
|
61
|
+
if (type === 2)
|
|
62
|
+
return numericDisplayFormat(options);
|
|
63
|
+
if (type === 3)
|
|
64
|
+
return dateDisplayFormat(options);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ticket 75 — expand high-level button params (targetModel/targetForm/
|
|
69
|
+
* configureList) into the authoritative actionConfig shape from readback:
|
|
70
|
+
* - schemaCodeVal: the target model object (app-tree node id, app metadata,
|
|
71
|
+
* published derived from the model having published fields — field.publish
|
|
72
|
+
* publishes the whole schema);
|
|
73
|
+
* - formListData: the target default form (bizsheet/get; only sheetType 0
|
|
74
|
+
* 默认表单 binding has live evidence);
|
|
75
|
+
* - configureList: data assignments with the inline field-object copies
|
|
76
|
+
* (array-wrapped, as captured) and the evidenced literals
|
|
77
|
+
* isConst:"false" / OptionType:"Eq".
|
|
78
|
+
* Fails closed (listview-button-target-unresolved) BEFORE the query/update
|
|
79
|
+
* write when the model node, form, or any assignment field is not found.
|
|
80
|
+
*/
|
|
81
|
+
async function expandButtonActionConfigs(session, viewSchemaCode, buttons) {
|
|
82
|
+
const appRows = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data);
|
|
83
|
+
const appNameOf = (appCode) => {
|
|
84
|
+
const row = appRows.find((candidate) => String(candidate.appCode ?? candidate.code ?? "") === appCode);
|
|
85
|
+
return typeof row?.appName === "string" ? row.appName : "";
|
|
86
|
+
};
|
|
87
|
+
const publishedFieldsOf = async (schemaCode) => wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode, isPublish: "true" } })).data);
|
|
88
|
+
const expanded = [];
|
|
89
|
+
for (const button of buttons) {
|
|
90
|
+
// FLOW: the list runtime reads actionConfig.schemaCodeVal.code and
|
|
91
|
+
// actionConfig.WorkflowListData[0].workflowCode and ITERATES
|
|
92
|
+
// actionConfig.configureList (decompiled 8.6.34 7926.js case 17). Expand
|
|
93
|
+
// the high-level targetCode (workflow code) into that shape.
|
|
94
|
+
if (button.bindAction === "FLOW" && button.actionConfig === undefined && typeof button.targetCode === "string" && button.targetCode) {
|
|
95
|
+
expanded.push({
|
|
96
|
+
...button,
|
|
97
|
+
actionConfig: {
|
|
98
|
+
schemaCodeVal: { code: viewSchemaCode, value: viewSchemaCode },
|
|
99
|
+
WorkflowListData: [{ workflowCode: button.targetCode }],
|
|
100
|
+
configureList: [],
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (button.targetModel === undefined && button.targetForm === undefined && button.configureList === undefined) {
|
|
106
|
+
expanded.push(button);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const targetModel = button.targetModel ?? viewSchemaCode;
|
|
110
|
+
const unresolved = (what) => {
|
|
111
|
+
throw new ToolRefusalError({
|
|
112
|
+
status: "refused",
|
|
113
|
+
blocker: "listview-button-target-unresolved",
|
|
114
|
+
schemaCode: viewSchemaCode,
|
|
115
|
+
targetModel,
|
|
116
|
+
message: `按钮目标解析失败(零副作用):${what}。用 discover_environment / form.get 确认目标模型与默认表单存在并已发布后再配置。`,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
// 1. Target model node (AppFunction id via the app trees — ticket 68 fact).
|
|
120
|
+
const resolvedNode = await resolveModelTreeNode(session, targetModel);
|
|
121
|
+
if (!resolvedNode.node)
|
|
122
|
+
unresolved(`模型 ${targetModel} 在任何应用树中都找不到节点`);
|
|
123
|
+
const node = resolvedNode.node;
|
|
124
|
+
const appCode = resolvedNode.applicationCode;
|
|
125
|
+
// 2. Published face of the target model (drives schemaCodeVal.published
|
|
126
|
+
// and the configureList target-field inline copies).
|
|
127
|
+
const targetFieldRows = await publishedFieldsOf(targetModel);
|
|
128
|
+
if (targetFieldRows.length === 0)
|
|
129
|
+
unresolved(`模型 ${targetModel} 没有已发布字段(schemaCodeVal.published 无法为真)`);
|
|
130
|
+
const schemaCodeVal = {
|
|
131
|
+
code: targetModel,
|
|
132
|
+
id: node.id ?? null,
|
|
133
|
+
name: typeof node.name === "string" && node.name ? node.name : targetModel,
|
|
134
|
+
type: "BizModel",
|
|
135
|
+
appCode,
|
|
136
|
+
appName: appNameOf(appCode),
|
|
137
|
+
published: true,
|
|
138
|
+
};
|
|
139
|
+
// 3. Target default form (formListData; sheetType 0 默认表单 only — the
|
|
140
|
+
// sole live-evidenced binding).
|
|
141
|
+
const formCode = button.targetForm ?? targetModel;
|
|
142
|
+
let sheet = null;
|
|
143
|
+
try {
|
|
144
|
+
sheet = ((await wireCall(session, { path: ENDPOINTS.sheetGet, query: { schemaCode: targetModel, sheetCode: formCode } })).data ?? null);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (!(error instanceof WireError && error.errcode === 302024))
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
if (!sheet || !sheet.id)
|
|
151
|
+
unresolved(`模型 ${targetModel} 的表单 ${formCode} 不存在(formListData 无从展开)`);
|
|
152
|
+
const resolvedSheet = sheet;
|
|
153
|
+
if (resolvedSheet.sheetType !== undefined && resolvedSheet.sheetType !== null && Number(resolvedSheet.sheetType) !== 0)
|
|
154
|
+
unresolved(`表单 ${formCode} 的 sheetType=${String(resolvedSheet.sheetType)} 非默认表单——仅 sheetType 0(默认表单)绑定有 live 取证`);
|
|
155
|
+
const formListData = [
|
|
156
|
+
{
|
|
157
|
+
code: resolvedSheet.code ?? formCode,
|
|
158
|
+
schemaCode: targetModel,
|
|
159
|
+
id: resolvedSheet.id,
|
|
160
|
+
name: resolvedSheet.name ?? formCode,
|
|
161
|
+
sheetType: 0,
|
|
162
|
+
sheetTypeName: "默认表单",
|
|
163
|
+
published: resolvedSheet.published === true,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
// 4. configureList — inline field copies array-wrapped (captured shape).
|
|
167
|
+
const configureList = [];
|
|
168
|
+
if (button.configureList !== undefined) {
|
|
169
|
+
const sourceFieldRows = await publishedFieldsOf(viewSchemaCode);
|
|
170
|
+
for (const assignment of button.configureList) {
|
|
171
|
+
const sourceRow = sourceFieldRows.find((row) => String(row.code ?? row.propertyCode ?? "") === assignment.currentDataItemId);
|
|
172
|
+
if (!sourceRow)
|
|
173
|
+
unresolved(`赋值源字段 ${viewSchemaCode}.${assignment.currentDataItemId} 不在发布面上`);
|
|
174
|
+
const targetRow = targetFieldRows.find((row) => String(row.code ?? row.propertyCode ?? "") === assignment.targetDataItemId);
|
|
175
|
+
if (!targetRow)
|
|
176
|
+
unresolved(`赋值目标字段 ${targetModel}.${assignment.targetDataItemId} 不在发布面上`);
|
|
177
|
+
configureList.push({
|
|
178
|
+
currentDataItemId: assignment.currentDataItemId,
|
|
179
|
+
targetDataItemId: assignment.targetDataItemId,
|
|
180
|
+
targetDataItem: [{ ...targetRow, schemaCode: targetModel }],
|
|
181
|
+
currentDataItem: [{ ...sourceRow, schemaCode: viewSchemaCode }],
|
|
182
|
+
isConst: "false",
|
|
183
|
+
OptionType: "Eq",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const { targetModel: _tm, targetForm: _tf, configureList: _cl, ...rest } = button;
|
|
188
|
+
expanded.push({ ...rest, actionConfig: { schemaCodeVal, formListData, configureList } });
|
|
189
|
+
}
|
|
190
|
+
return expanded;
|
|
191
|
+
}
|
|
192
|
+
export function filterDefaultSystemColumns(columns, hideDefaultSystemFields) {
|
|
193
|
+
if (!hideDefaultSystemFields)
|
|
194
|
+
return [...columns];
|
|
195
|
+
return columns.filter((column) => typeof column.propertyCode !== "string" || !SYSTEM_FIELD_CODES.has(column.propertyCode));
|
|
196
|
+
}
|
|
197
|
+
export function registerListViewLane(tools) {
|
|
198
|
+
tools.push(defineTool({
|
|
199
|
+
name: "listview.get",
|
|
200
|
+
layer: "low",
|
|
201
|
+
kind: "read",
|
|
202
|
+
lane: LIST_VIEW_LANE,
|
|
203
|
+
scope: LIST_VIEW_TOOLS["listview.get"].scope,
|
|
204
|
+
description: LIST_VIEW_TOOLS["listview.get"].description,
|
|
205
|
+
inputSchema: {
|
|
206
|
+
environment: environmentArg,
|
|
207
|
+
schemaCode: z.string().min(1),
|
|
208
|
+
code: z.string().optional().describe("视图 code;省略时用默认视图(=schemaCode)"),
|
|
209
|
+
full: z.boolean().optional().describe("返回 query/get 原始报文(大体积);默认返回列/排序/条件/动作/按钮的紧凑清单"),
|
|
210
|
+
capability: capabilityArg,
|
|
211
|
+
},
|
|
212
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
213
|
+
const code = args.code ?? args.schemaCode;
|
|
214
|
+
const got = await wireCall(session, { path: ENDPOINTS.listQueryGet, query: { schemaCode: args.schemaCode, code, clientType: "PC" } });
|
|
215
|
+
const model = (got.data ?? null);
|
|
216
|
+
if (model === null || !model.id)
|
|
217
|
+
return { schemaCode: args.schemaCode, code, present: false, note: "视图不存在(query/get 空或 302025);先 listview.create 或 listview.configure" };
|
|
218
|
+
if (args.full === true)
|
|
219
|
+
return { schemaCode: args.schemaCode, code, present: true, data: model };
|
|
220
|
+
const columns = readArray(model, "queryColumns").map((column) => ({
|
|
221
|
+
propertyCode: column.propertyCode,
|
|
222
|
+
propertyType: column.propertyType,
|
|
223
|
+
displayFormat: column.displayFormat,
|
|
224
|
+
visible: column.visible,
|
|
225
|
+
}));
|
|
226
|
+
// Direction is decoded to the enum NAME (ASC/DESC) — the same shape
|
|
227
|
+
// `listview.configure` reads back, so a get→configure round-trip does not
|
|
228
|
+
// surprise the caller with a raw numeric index (dogfood R2 round17b).
|
|
229
|
+
const sorts = readArray(model, "querySorts")
|
|
230
|
+
.map(decodeSortRow)
|
|
231
|
+
.sort((a, b) => Number(a.sortKey ?? 0) - Number(b.sortKey ?? 0));
|
|
232
|
+
// Round 16 (bench67 feedback): `conditionCount` alone cannot verify a
|
|
233
|
+
// declared filter (e.g. "status != 已归档") — expose the decoded rows
|
|
234
|
+
// (same projector listview.configure reads back) so acceptance can see
|
|
235
|
+
// the property/operator/default the view actually carries.
|
|
236
|
+
const conditions = readArray(model, "queryConditions")
|
|
237
|
+
.map(decodeConditionRow)
|
|
238
|
+
.sort((a, b) => String(a.propertyCode ?? "").localeCompare(String(b.propertyCode ?? "")));
|
|
239
|
+
return {
|
|
240
|
+
schemaCode: args.schemaCode,
|
|
241
|
+
code,
|
|
242
|
+
present: true,
|
|
243
|
+
name: model.name ?? null,
|
|
244
|
+
published: model.published === true,
|
|
245
|
+
presentationType: model.queryPresentationType ?? null,
|
|
246
|
+
columnCount: columns.length,
|
|
247
|
+
columns,
|
|
248
|
+
sorts,
|
|
249
|
+
conditionCount: conditions.length,
|
|
250
|
+
conditions,
|
|
251
|
+
actionCodes: readArray(model, "queryActions").map((action) => action.actionCode).filter((value) => value !== undefined),
|
|
252
|
+
buttonCount: readArray(model, "customButtons").length,
|
|
253
|
+
options: model.options !== undefined && model.options !== null ? parseConditionOptions(model.options) : null,
|
|
254
|
+
// Round 16 (bench65 feedback): the query row's `published=false` is NOT
|
|
255
|
+
// an "inactive view" signal — a list view has no draft/publish
|
|
256
|
+
// lifecycle; this column is a design-time placeholder. The view is
|
|
257
|
+
// live as soon as it is saved. Say so, so the flag is not misread.
|
|
258
|
+
hint: "published 是设计态占位(列表视图无草稿/发布生命周期),false 不代表视图未生效;列/排序/条件/动作/按钮是否落成以 columns/sorts/conditionCount/actionCodes 与 field.list 为准。完整报文加 full:true。NUMERICAL 列的 displayFormat 是平台数值格式枚举(decimal scale2→3、currency→7、percent→6…),不是字段 scale;列的小数位以字段 options.scale 为准(用 field.list 读回)",
|
|
259
|
+
};
|
|
260
|
+
}),
|
|
261
|
+
}));
|
|
262
|
+
tools.push(defineTool({
|
|
263
|
+
name: "listview.list",
|
|
264
|
+
layer: "low",
|
|
265
|
+
kind: "read",
|
|
266
|
+
lane: LIST_VIEW_LANE,
|
|
267
|
+
scope: LIST_VIEW_TOOLS["listview.list"].scope,
|
|
268
|
+
description: LIST_VIEW_TOOLS["listview.list"].description,
|
|
269
|
+
inputSchema: { environment: environmentArg, schemaCode: z.string().min(1), capability: capabilityArg },
|
|
270
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
271
|
+
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.listQueryList, query: { schemaCode: args.schemaCode, clientType: "PC" } })).data);
|
|
272
|
+
return {
|
|
273
|
+
schemaCode: args.schemaCode,
|
|
274
|
+
count: rows.length,
|
|
275
|
+
views: rows.map((row) => ({ code: row.code ?? null, name: row.name ?? null, type: row.type ?? null, published: row.published === true })),
|
|
276
|
+
hint: "默认视图 code = schemaCode;读单个视图用 listview.get",
|
|
277
|
+
};
|
|
278
|
+
}),
|
|
279
|
+
}));
|
|
280
|
+
writeTool(tools, "listview.create", LIST_VIEW_LANE, LIST_VIEW_TOOLS["listview.create"].scope, LIST_VIEW_TOOLS["listview.create"].description, { schemaCode: z.string().min(1), name: z.string().min(1), code: z.string().optional() }, async (session, args) => {
|
|
281
|
+
// Ticket 81: read the header FIRST — a same-code retry short-circuits
|
|
282
|
+
// with the existing view and sends ZERO create wires (the live
|
|
283
|
+
// post-timeout retry otherwise answers 303002 编码重复).
|
|
284
|
+
const existing = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code ?? "" } });
|
|
285
|
+
if (existing.data != null) {
|
|
286
|
+
return { performed: { skipped: "already-present" }, readback: { alreadyPresent: true, header: existing.data } };
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const performed = await wireCall(session, {
|
|
290
|
+
path: ENDPOINTS.listQueryCreate,
|
|
291
|
+
method: "POST",
|
|
292
|
+
body: { schemaCode: args.schemaCode, name: args.name, code: args.code, type: "LIST", showOnPc: false, published: false },
|
|
293
|
+
});
|
|
294
|
+
const readback = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code ?? "" } });
|
|
295
|
+
return { performed: { errcode: performed.errcode }, readback: { header: readback.data } };
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
// Race path: another writer created the view between the pre-check and
|
|
299
|
+
// the create — the platform's 303002 is absence of work, not failure.
|
|
300
|
+
if (!(error instanceof WireError && error.kind === "business" && error.errcode === 303002))
|
|
301
|
+
throw error;
|
|
302
|
+
const header = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code ?? "" } });
|
|
303
|
+
if (!header.data)
|
|
304
|
+
throw error;
|
|
305
|
+
return { performed: { skipped: "already-present" }, readback: { alreadyPresent: true, header: header.data } };
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
writeTool(tools, "listview.configure", LIST_VIEW_LANE, LIST_VIEW_TOOLS["listview.configure"].scope, LIST_VIEW_TOOLS["listview.configure"].description, {
|
|
309
|
+
schemaCode: z.string().min(1),
|
|
310
|
+
code: z.string().optional(),
|
|
311
|
+
fieldCodes: z.array(z.string()).min(1).describe("要展示的字段:可给 propertyCode 或字段显示名(唯一匹配时自动解析,歧义或不存在则拒绝,不静默跳过)"),
|
|
312
|
+
hideDefaultSystemFields: z.boolean().optional().describe("是否隐藏平台默认系统列(createdTime/creater/name/sequenceNo 等);平台 update 按提交列集合决定,不能靠 visible:false"),
|
|
313
|
+
replaceColumns: z.boolean().optional().describe("把 fieldCodes 当作列集合的权威全集:未列出的已有列(含系统列)全部移除,最终列 = fieldCodes(按给定顺序)。默认 false 为增量(保留已有列)。系统列与业务列一视同仁——想要 createdTime/sequenceNo 就把它写进 fieldCodes"),
|
|
314
|
+
sorts: z.array(listViewSortArg).optional().describe("querySorts; omitted leaves the existing sorts untouched, provided replaces them"),
|
|
315
|
+
conditions: z.array(listViewConditionArg).optional().describe("queryConditions; omitted leaves the existing conditions untouched, provided replaces them"),
|
|
316
|
+
actions: z.array(listViewActionArg).optional().describe("queryActions; omitted leaves the existing actions untouched, provided MERGES into the effective set by actionCode (existing platform defaults like 新增/删除/导入/导出 are preserved, a matching actionCode is updated). Use replaceActions:true for the authoritative full set"),
|
|
317
|
+
replaceActions: z.boolean().optional().describe("把 actions 当作权威全集:未列出的已有动作(含平台内置的 add/删除/导入/导出/修改拥有者)全部移除。默认 false 为合并(保留内置动作,按 actionCode 更新/追加)"),
|
|
318
|
+
defaultActions: z.boolean().optional().describe("先并入平台对 PC LIST 视图的默认动作集(add/delete/batch_update/batch_submit/import/export/batch_print/qr_code/editowner/import_attachment/export_attachment,常用于把被替换掉的内置按钮找回),再叠加 actions"),
|
|
319
|
+
presentation: listViewPresentationArg.optional(),
|
|
320
|
+
buttons: z
|
|
321
|
+
.array(listViewButtonArg)
|
|
322
|
+
.optional()
|
|
323
|
+
.describe("customButtons; sent as the customButtonJson JSON array string; omitted leaves the existing buttons untouched, provided replaces every button under the QUERY trigger (schema + child tables)"),
|
|
324
|
+
options: listViewOptionsArg.optional(),
|
|
325
|
+
presentationType: presentationTypeArg.optional(),
|
|
326
|
+
gantt: listViewGanttArg.optional(),
|
|
327
|
+
queryLayer: listViewQueryLayerArg.optional(),
|
|
328
|
+
multiTableHeaderConfig: listViewMultiTableHeaderArg.optional(),
|
|
329
|
+
}, async (session, args) => {
|
|
330
|
+
const code = args.code ?? args.schemaCode;
|
|
331
|
+
const got = await wireCall(session, { path: ENDPOINTS.listQueryGet, query: { schemaCode: args.schemaCode, code, clientType: "PC" } });
|
|
332
|
+
const model = (got.data ?? {});
|
|
333
|
+
if (!model.id)
|
|
334
|
+
throw new WireError("business", `list view ${code} not found on ${args.schemaCode}`, 302025);
|
|
335
|
+
const columns = Array.isArray(model.queryColumns) ? ([...model.queryColumns]) : [];
|
|
336
|
+
const fieldRows = wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } })).data);
|
|
337
|
+
let sortKey = columns.reduce((max, c) => Math.max(max, Number(c.sortKey) || 0), 0);
|
|
338
|
+
const added = [];
|
|
339
|
+
const updated = [];
|
|
340
|
+
// A `fieldCodes` entry may be a propertyCode OR a display name (cpdocs
|
|
341
|
+
// base3.0 parity); an unresolvable reference is REFUSED, never silently
|
|
342
|
+
// skipped — a silently dropped column reported as success is the
|
|
343
|
+
// "declared a column, nothing appeared" trap.
|
|
344
|
+
const unknownRefs = args.fieldCodes.filter((ref) => resolveFieldRef(fieldRows, ref) === undefined);
|
|
345
|
+
if (unknownRefs.length > 0)
|
|
346
|
+
throw new ToolRefusalError({
|
|
347
|
+
status: "refused",
|
|
348
|
+
blocker: "listview-columns-unknown-field",
|
|
349
|
+
unknown: unknownRefs,
|
|
350
|
+
message: `fieldCodes 引用了模型 ${args.schemaCode} 发布面上不存在的字段(可给 propertyCode 或字段名):${unknownRefs.join("、")};先 field.create + field.publish,或修正引用`,
|
|
351
|
+
});
|
|
352
|
+
// replaceColumns: the caller's fieldCodes ARE the view. Start from an
|
|
353
|
+
// empty set so system columns fall away unless listed — a system field
|
|
354
|
+
// can still be shown by naming its code in fieldCodes (createdTime etc.
|
|
355
|
+
// are published fields too), so the caller controls the exact set.
|
|
356
|
+
if (args.replaceColumns === true)
|
|
357
|
+
columns.length = 0;
|
|
358
|
+
for (const ref of args.fieldCodes) {
|
|
359
|
+
const fieldCode = resolveFieldRef(fieldRows, ref).code;
|
|
360
|
+
const field = fieldRows.find((row) => (row.code ?? row.propertyCode) === fieldCode);
|
|
361
|
+
const type = Number(field.type ?? field.propertyType);
|
|
362
|
+
const displayFormat = columnDisplayFormat(type, field.options);
|
|
363
|
+
const existing = columns.find((c) => c.propertyCode === fieldCode);
|
|
364
|
+
if (existing) {
|
|
365
|
+
existing.name = field.name;
|
|
366
|
+
existing.propertyType = type;
|
|
367
|
+
existing.displayFormat = displayFormat;
|
|
368
|
+
existing.visible = true;
|
|
369
|
+
updated.push(fieldCode);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
sortKey += 1;
|
|
373
|
+
columns.push({ name: field.name, propertyCode: fieldCode, propertyType: type, isDefaultProperty: false, displayFormat, width: "160", sortKey, sumType: null, relativeSchemaCode: null, childColumns: null, clientType: "PC", schemaAlias: "a_1", propertyAlias: fieldCode, syncDefaultFormat: false, displayFields: null, queryLevel: 1, options: null, visible: true, virtualField: false, enableEncrypt: false, autoFilled: false });
|
|
374
|
+
added.push(fieldCode);
|
|
375
|
+
}
|
|
376
|
+
// replaceColumns already reduced the set to fieldCodes; hideDefaultSystemFields
|
|
377
|
+
// additionally drops any system column that slipped in (an echoed system
|
|
378
|
+
// column in incremental mode, or one the caller listed only to name its
|
|
379
|
+
// display). Both act by OMITTING columns from the submitted PC collection:
|
|
380
|
+
// the platform forces every submitted column visible=true
|
|
381
|
+
// (BizQueryController.reSetQueryColumns:1000-1027), so visible:false is
|
|
382
|
+
// not an option.
|
|
383
|
+
const configuredColumns = filterDefaultSystemColumns(columns, args.hideDefaultSystemFields === true);
|
|
384
|
+
const body = { ...model, queryColumns: configuredColumns };
|
|
385
|
+
if (args.presentation !== undefined)
|
|
386
|
+
body.queryPresentation = normalizePresentation(args.presentation);
|
|
387
|
+
if (args.sorts !== undefined)
|
|
388
|
+
body.querySorts = normalizeSorts(args.sorts, fieldRows, args.schemaCode);
|
|
389
|
+
// Keep the normalized requested conditions so the post-write readback can
|
|
390
|
+
// compare what was ASKED for against what the platform stored, rather than
|
|
391
|
+
// reporting a write-side `ok` that silently lost fields (dogfood R2).
|
|
392
|
+
let requestedConditionKeys;
|
|
393
|
+
let requestedConditionSpec;
|
|
394
|
+
if (args.conditions !== undefined) {
|
|
395
|
+
const normalizedConditions = normalizeConditions(args.conditions, fieldRows, args.schemaCode);
|
|
396
|
+
body.queryConditions = normalizedConditions;
|
|
397
|
+
requestedConditionKeys = normalizedConditions.map((row) => String(row.propertyCode ?? ""));
|
|
398
|
+
requestedConditionSpec = normalizedConditions.map((row) => ({ propertyCode: String(row.propertyCode ?? ""), filterType: row.filterType }));
|
|
399
|
+
}
|
|
400
|
+
// saveBizQueryDetail removes every action whose clientType matches the
|
|
401
|
+
// update and re-adds the submitted list, so an omitted `actions` must echo
|
|
402
|
+
// the read-face effective set (stored + platform-injected) rather than
|
|
403
|
+
// sending an empty list, which would delete the view's actions.
|
|
404
|
+
//
|
|
405
|
+
// Live 2026-09-17: because it is replace-then-re-add, passing ONE action
|
|
406
|
+
// used to delete the built-in 新增/删除/导入/导出 and the list rendered
|
|
407
|
+
// with no Add button. `actions` now MERGES into the effective set by
|
|
408
|
+
// actionCode; replaceActions:true restores the authoritative-set
|
|
409
|
+
// behaviour, and defaultActions:true re-seeds the platform's own default
|
|
410
|
+
// set (the repair path for a view that already lost its buttons).
|
|
411
|
+
if (args.actions === undefined) {
|
|
412
|
+
body.queryActions = readArray(model, "queryActions");
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
const normalized = normalizeActions(args.actions, args.schemaCode);
|
|
416
|
+
if (args.replaceActions === true) {
|
|
417
|
+
body.queryActions = normalized;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
const seeded = args.defaultActions === true
|
|
421
|
+
? normalizeActions(PLATFORM_DEFAULT_LIST_ACTIONS.map((action) => ({ ...action })), args.schemaCode)
|
|
422
|
+
: [];
|
|
423
|
+
body.queryActions = mergeActions([...seeded, ...readArray(model, "queryActions")], normalized);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// BizButtonFacadeImpl.addBizButtonList runs only when the update body
|
|
427
|
+
// carries a JSON array in customButtonJson; it then deletes every button
|
|
428
|
+
// under the QUERY trigger for the schema (+ its child tables) and re-adds
|
|
429
|
+
// the submitted list. An omitted `buttons` therefore leaves the stored
|
|
430
|
+
// buttons untouched (no customButtonJson key), while `buttons: []` clears.
|
|
431
|
+
// Ticket 75: high-level targetModel/targetForm/configureList params are
|
|
432
|
+
// expanded into the authoritative actionConfig shape from readback
|
|
433
|
+
// BEFORE normalizeButtons (targetCode/targetObjCode never emitted for
|
|
434
|
+
// ADD_DATA/FORM — measured ineffective, see dry-lint).
|
|
435
|
+
if (args.buttons !== undefined) {
|
|
436
|
+
const expandedButtons = await expandButtonActionConfigs(session, args.schemaCode, args.buttons);
|
|
437
|
+
body.customButtonJson = JSON.stringify(normalizeButtons(expandedButtons, args.schemaCode, code));
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
delete body.customButtonJson;
|
|
441
|
+
}
|
|
442
|
+
// BizQueryModel.options is a single String column. BizQueryController.update
|
|
443
|
+
// sets publish=TRUE before updateBizQuery, so after the first configure the
|
|
444
|
+
// store's publish is TRUE and only the name/options-diff branch
|
|
445
|
+
// (ModelManagementFacadeImpl:1966-1973) persists options — an omitted body
|
|
446
|
+
// key (null) would overwrite a non-null stored value. `{...model}` already
|
|
447
|
+
// echoes the read-face options, so only a provided value replaces it.
|
|
448
|
+
if (args.options !== undefined)
|
|
449
|
+
body.options = encodeJsonColumn(args.options);
|
|
450
|
+
// QueryPresentationType is a header enum always assignable by
|
|
451
|
+
// updateBizQuery (:1955, not gated), so an omitted body key (null) would
|
|
452
|
+
// overwrite a stored type; `{...model}` echoes the read-face value. GANTT
|
|
453
|
+
// requires queryGanttModel (controller validateNotEmptyGantt :1121-1136).
|
|
454
|
+
if (args.presentationType !== undefined)
|
|
455
|
+
body.queryPresentationType = args.presentationType;
|
|
456
|
+
// saveBizQueryDetail only swaps queryGantt when non-null (:178-183), so an
|
|
457
|
+
// omitted gantt leaves the stored one untouched.
|
|
458
|
+
if (args.gantt !== undefined)
|
|
459
|
+
body.queryGanttModel = normalizeGantt(args.gantt, args.schemaCode);
|
|
460
|
+
// saveBizQueryDetail assigns queryLayer and multiTableHeaderConfig
|
|
461
|
+
// unconditionally (:184-186); the read face always carries them
|
|
462
|
+
// (BizQueryFacadeImpl:344-345), so an omitted arg relies on the
|
|
463
|
+
// `{...model}` echo while a provided value replaces it.
|
|
464
|
+
if (args.queryLayer !== undefined)
|
|
465
|
+
body.queryLayer = args.queryLayer;
|
|
466
|
+
if (args.multiTableHeaderConfig !== undefined) {
|
|
467
|
+
body.multiTableHeaderConfig = encodeJsonColumn(args.multiTableHeaderConfig);
|
|
468
|
+
}
|
|
469
|
+
const performed = await wireCall(session, { path: ENDPOINTS.listQueryUpdate, method: "POST", body });
|
|
470
|
+
// BUG-11 (live, docker+source verified): the controller's /query/update
|
|
471
|
+
// forces publish=TRUE and the engine's updateBizQuery SKIPS the entity
|
|
472
|
+
// update block once the STORED view is published
|
|
473
|
+
// (ModelManagementFacadeImpl:1998-2010), so a body-driven
|
|
474
|
+
// queryPresentationType is silently dropped for a published view (the
|
|
475
|
+
// default view is published). The designer switches view type through
|
|
476
|
+
// /query/update_header (a plain header update, no publish gate) — drive
|
|
477
|
+
// the same path whenever presentationType is provided.
|
|
478
|
+
if (args.presentationType !== undefined) {
|
|
479
|
+
const header = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code } });
|
|
480
|
+
const headerModel = (header.data ?? {});
|
|
481
|
+
await wireCall(session, {
|
|
482
|
+
path: ENDPOINTS.listQueryUpdateHeader,
|
|
483
|
+
method: "POST",
|
|
484
|
+
body: { ...headerModel, code, schemaCode: args.schemaCode, queryPresentationType: args.presentationType },
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const after = await wireCall(session, { path: ENDPOINTS.listQueryGet, query: { schemaCode: args.schemaCode, code, clientType: "PC" } });
|
|
488
|
+
const afterModel = (after.data ?? {});
|
|
489
|
+
const afterCols = readArray(afterModel, "queryColumns").map((c) => ({
|
|
490
|
+
propertyCode: c.propertyCode,
|
|
491
|
+
propertyType: c.propertyType,
|
|
492
|
+
displayFormat: c.displayFormat ?? null,
|
|
493
|
+
visible: c.visible ?? null,
|
|
494
|
+
sumType: c.sumType ?? null,
|
|
495
|
+
}));
|
|
496
|
+
const sorts = readArray(afterModel, "querySorts")
|
|
497
|
+
.map(decodeSortRow)
|
|
498
|
+
.sort((a, b) => Number(a.sortKey ?? 0) - Number(b.sortKey ?? 0));
|
|
499
|
+
const conditions = readArray(afterModel, "queryConditions")
|
|
500
|
+
.map(decodeConditionRow)
|
|
501
|
+
.sort((a, b) => String(a.propertyCode ?? "").localeCompare(String(b.propertyCode ?? "")));
|
|
502
|
+
const readback = { code, added, updated, columns: afterCols, sorts, conditions };
|
|
503
|
+
// Post-write semantic readback (dogfood R2): the platform MODELS a list
|
|
504
|
+
// condition as a filter CONTROL definition, so a requested condition whose
|
|
505
|
+
// propertyCode does not survive the round-trip was lost. Surface that as
|
|
506
|
+
// `conditionReadbackStatus` (not a business `ok`) so a caller never
|
|
507
|
+
// mistakes the success envelope for the filter having landed.
|
|
508
|
+
if (requestedConditionKeys !== undefined) {
|
|
509
|
+
const storedKeys = new Set(conditions.map((condition) => String(condition.propertyCode ?? "")));
|
|
510
|
+
const missingConditions = requestedConditionKeys.filter((key) => !storedKeys.has(key));
|
|
511
|
+
// Also verify the OPERATOR (filterType) survived — a propertyCode that
|
|
512
|
+
// lands with the WRONG comparison is just as wrong as one that is
|
|
513
|
+
// dropped (dogfood R2 follow-up).
|
|
514
|
+
const storedByCode = new Map(conditions.map((condition) => [String(condition.propertyCode ?? ""), condition]));
|
|
515
|
+
const filterTypeMismatches = (requestedConditionSpec ?? [])
|
|
516
|
+
.filter((spec) => storedByCode.has(spec.propertyCode))
|
|
517
|
+
.filter((spec) => String(storedByCode.get(spec.propertyCode)?.filterType ?? "") !== String(spec.filterType ?? ""))
|
|
518
|
+
.map((spec) => ({ propertyCode: spec.propertyCode, requested: spec.filterType ?? null, stored: storedByCode.get(spec.propertyCode)?.filterType ?? null }));
|
|
519
|
+
const equivalent = missingConditions.length === 0 && filterTypeMismatches.length === 0;
|
|
520
|
+
readback.conditionReadback = {
|
|
521
|
+
status: equivalent ? "equivalent" : "listview-condition-readback-mismatch",
|
|
522
|
+
requested: requestedConditionKeys,
|
|
523
|
+
stored: [...storedKeys],
|
|
524
|
+
// Operator-level summary so "operator + value" acceptance needs no
|
|
525
|
+
// `--full`/artifact fetch (dogfood R2 round17b).
|
|
526
|
+
operators: (requestedConditionSpec ?? []).map((spec) => ({
|
|
527
|
+
propertyCode: spec.propertyCode,
|
|
528
|
+
requested: spec.filterType ?? null,
|
|
529
|
+
stored: storedByCode.get(spec.propertyCode)?.filterType ?? null,
|
|
530
|
+
defaultValue: storedByCode.get(spec.propertyCode)?.defaultValue ?? null,
|
|
531
|
+
})),
|
|
532
|
+
...(missingConditions.length > 0 ? { missing: missingConditions } : {}),
|
|
533
|
+
...(filterTypeMismatches.length > 0 ? { filterTypeMismatches } : {}),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
readback.columnsRenderCompat = columnsRenderCompat(readArray(afterModel, "queryColumns"));
|
|
537
|
+
if (args.actions !== undefined) {
|
|
538
|
+
readback.actions = readArray(afterModel, "queryActions")
|
|
539
|
+
.map(decodeActionRow)
|
|
540
|
+
.sort((a, b) => Number(a.sortKey ?? 0) - Number(b.sortKey ?? 0));
|
|
541
|
+
}
|
|
542
|
+
if (afterModel.queryPresentation && typeof afterModel.queryPresentation === "object") {
|
|
543
|
+
readback.presentation = decodePresentation(afterModel.queryPresentation);
|
|
544
|
+
}
|
|
545
|
+
if (Array.isArray(afterModel.customButtons)) {
|
|
546
|
+
const buttons = readArray(afterModel, "customButtons");
|
|
547
|
+
readback.buttons = buttons
|
|
548
|
+
.map(decodeButtonRow)
|
|
549
|
+
.sort((a, b) => Number(a.sortKey ?? 0) - Number(b.sortKey ?? 0));
|
|
550
|
+
readback.buttonsRenderCompat = buttonsRenderCompat(buttons);
|
|
551
|
+
}
|
|
552
|
+
if (afterModel.options !== undefined && afterModel.options !== null) {
|
|
553
|
+
readback.options = parseConditionOptions(afterModel.options);
|
|
554
|
+
}
|
|
555
|
+
if (afterModel.queryPresentationType !== undefined && afterModel.queryPresentationType !== null) {
|
|
556
|
+
readback.presentationType = afterModel.queryPresentationType;
|
|
557
|
+
}
|
|
558
|
+
if (afterModel.queryGanttModel && typeof afterModel.queryGanttModel === "object") {
|
|
559
|
+
readback.gantt = decodeGantt(afterModel.queryGanttModel);
|
|
560
|
+
}
|
|
561
|
+
if (afterModel.queryLayer !== undefined && afterModel.queryLayer !== null) {
|
|
562
|
+
readback.queryLayer = afterModel.queryLayer;
|
|
563
|
+
}
|
|
564
|
+
if (afterModel.multiTableHeaderConfig !== undefined && afterModel.multiTableHeaderConfig !== null && afterModel.multiTableHeaderConfig !== "") {
|
|
565
|
+
readback.multiTableHeaderConfig = parseConditionOptions(afterModel.multiTableHeaderConfig);
|
|
566
|
+
}
|
|
567
|
+
return { performed: { errcode: performed.errcode }, readback };
|
|
568
|
+
}, assertListViewConfigureShape);
|
|
569
|
+
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) => {
|
|
570
|
+
const before = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code } });
|
|
571
|
+
if (before.data == null)
|
|
572
|
+
return { performed: { skipped: "already-absent" }, readback: { absent: true, header: null } };
|
|
573
|
+
const performed = await wireCall(session, { path: ENDPOINTS.listQueryDelete, method: "POST", query: { schemaCode: args.schemaCode, code: args.code } });
|
|
574
|
+
const readback = await wireCall(session, { path: ENDPOINTS.listQueryGetHeader, query: { schemaCode: args.schemaCode, code: args.code } });
|
|
575
|
+
return { performed: { errcode: performed.errcode }, readback: { absent: readback.data == null, header: readback.data ?? null } };
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
//# sourceMappingURL=list-view.js.map
|