@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,101 @@
|
|
|
1
|
+
import { capabilityShapeError } from "../capability-shape.js";
|
|
2
|
+
import { CAPABILITY_CONTROL_TYPES } from "../capability-shape.js";
|
|
3
|
+
import { businessRefusal, NOT_HANDLED } from "./state.js";
|
|
4
|
+
/** form-publish-chain lane: default-form draft/publish. Twin of src/lanes/form.ts. */
|
|
5
|
+
export const formRoute = (ctx, input) => {
|
|
6
|
+
const q = input.query ?? {};
|
|
7
|
+
const body = (input.body ?? {});
|
|
8
|
+
switch (input.path) {
|
|
9
|
+
case "/api/api/app/bizsheet/get": {
|
|
10
|
+
const sheet = ctx.state.sheets.get(q.sheetCode);
|
|
11
|
+
if (!sheet)
|
|
12
|
+
return null;
|
|
13
|
+
const name = q.sheetCode;
|
|
14
|
+
return {
|
|
15
|
+
id: `sheet-${q.sheetCode}`,
|
|
16
|
+
sheetCode: sheet.sheetCode,
|
|
17
|
+
code: sheet.sheetCode,
|
|
18
|
+
name,
|
|
19
|
+
sheetType: 0,
|
|
20
|
+
existDraft: sheet.existDraft,
|
|
21
|
+
published: sheet.published,
|
|
22
|
+
draftViewJson: sheet.draftViewJson ?? null,
|
|
23
|
+
draftAttributesJson: sheet.draftAttributesJson ?? null,
|
|
24
|
+
draftHtmlJson: sheet.draftHtmlJson ?? null,
|
|
25
|
+
// Ticket 100: real bizsheet columns (BizForm.java:95-100); the
|
|
26
|
+
// designer writes draftActionsJson on save and both faces on publish
|
|
27
|
+
// (frontend-dist/admin/js/4280.cfa75764.js offsets 193538/185347).
|
|
28
|
+
draftActionsJson: sheet.draftActionsJson ?? null,
|
|
29
|
+
publishedViewJson: sheet.publishedViewJson ?? null,
|
|
30
|
+
publishedAttributesJson: sheet.publishedAttributesJson ?? null,
|
|
31
|
+
publishedActionsJson: sheet.publishedActionsJson ?? null,
|
|
32
|
+
publishedHtmlJson: sheet.publishedHtmlJson ?? null,
|
|
33
|
+
binding: { fieldCodes: sheet.fieldCodes },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
case "/api/api/app/bizsheet/create": {
|
|
37
|
+
const code = String(body.code ?? body.sheetCode);
|
|
38
|
+
if (!ctx.state.sheets.has(code))
|
|
39
|
+
ctx.state.sheets.set(code, { sheetCode: code, existDraft: false, published: false, draftHtmlJson: "[]", fieldCodes: [] });
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
case "/api/api/app/bizsheet/update": {
|
|
43
|
+
let attributes = {};
|
|
44
|
+
if (typeof body.draftAttributesJson === "string") {
|
|
45
|
+
try {
|
|
46
|
+
attributes = JSON.parse(body.draftAttributesJson);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return businessRefusal(50000, "draftAttributesJson 不是合法 JSON");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const [fieldCode, control] of Object.entries(attributes)) {
|
|
53
|
+
const controlType = Number(control.type);
|
|
54
|
+
const propertyType = Object.entries(CAPABILITY_CONTROL_TYPES).find(([, value]) => value === controlType)?.[0];
|
|
55
|
+
if (propertyType === undefined)
|
|
56
|
+
continue;
|
|
57
|
+
const options = control.options && typeof control.options === "object" ? control.options : {};
|
|
58
|
+
const shapeError = capabilityShapeError(Number(propertyType), "control", options);
|
|
59
|
+
if (shapeError)
|
|
60
|
+
return businessRefusal(50000, `${fieldCode}: ${shapeError}`);
|
|
61
|
+
}
|
|
62
|
+
const code = String(body.code ?? body.sheetCode);
|
|
63
|
+
const existing = ctx.state.sheets.get(code) ?? {
|
|
64
|
+
sheetCode: code,
|
|
65
|
+
existDraft: false,
|
|
66
|
+
published: false,
|
|
67
|
+
draftHtmlJson: "[]",
|
|
68
|
+
fieldCodes: [],
|
|
69
|
+
};
|
|
70
|
+
ctx.state.sheets.set(code, {
|
|
71
|
+
...existing,
|
|
72
|
+
existDraft: true,
|
|
73
|
+
draftViewJson: body.draftViewJson ?? existing.draftViewJson ?? null,
|
|
74
|
+
draftAttributesJson: body.draftAttributesJson ?? existing.draftAttributesJson ?? null,
|
|
75
|
+
fieldCodes: typeof body.draftAttributesJson === "string" ? Object.keys(attributes) : existing.fieldCodes,
|
|
76
|
+
draftActionsJson: body.draftActionsJson ?? existing.draftActionsJson ?? null,
|
|
77
|
+
// draftHtmlJson holds the COMPLETE layout JSON array (including the
|
|
78
|
+
// customScript element when present). Default to "[]" on first
|
|
79
|
+
// draft so onlinejs.compose can parse it as an array.
|
|
80
|
+
draftHtmlJson: body.draftHtmlJson ?? existing.draftHtmlJson ?? "[]",
|
|
81
|
+
});
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
case "/api/api/app/bizsheet/publish": {
|
|
85
|
+
const code = String(body.code ?? body.sheetCode);
|
|
86
|
+
const sheet = ctx.state.sheets.get(code);
|
|
87
|
+
if (sheet) {
|
|
88
|
+
sheet.published = true;
|
|
89
|
+
sheet.existDraft = false;
|
|
90
|
+
sheet.publishedViewJson = sheet.draftViewJson ?? null;
|
|
91
|
+
sheet.publishedAttributesJson = sheet.draftAttributesJson ?? null;
|
|
92
|
+
sheet.publishedActionsJson = body.publishedActionsJson ?? "[]";
|
|
93
|
+
sheet.publishedHtmlJson = sheet.draftHtmlJson ?? "[]";
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
return NOT_HANDLED;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=form.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { dispatchFakeRoute } from "./routes.js";
|
|
2
|
+
import { createFakeState, isBusinessRefusal, isMalformedEnvelope, isRawResponse } from "./state.js";
|
|
3
|
+
export { FAKE_RSA_PUBLIC_KEY } from "./state.js";
|
|
4
|
+
/**
|
|
5
|
+
* In-memory fake CloudPivot 8.6 for offline CLI tests: implements the auth
|
|
6
|
+
* flow and every live-proven endpoint the tool surface drives. Supports
|
|
7
|
+
* failure injection (transport outage, non-8.6 version, business errcode).
|
|
8
|
+
*
|
|
9
|
+
* The wire behavior is split into one route module per configuration lane
|
|
10
|
+
* under ./fake/, mirroring src/lanes/*; this class owns the shared store,
|
|
11
|
+
* the failure-injection flags, and the seed/readback test accessors.
|
|
12
|
+
*/
|
|
13
|
+
export class FakeCloudPivot {
|
|
14
|
+
options;
|
|
15
|
+
state;
|
|
16
|
+
transportDown = false;
|
|
17
|
+
businessFailure = null;
|
|
18
|
+
/** Round-2 T-06: inject a RAW (non-errcode) response body to exercise malformed-envelope diagnostics. */
|
|
19
|
+
malformedResponse = null;
|
|
20
|
+
/** When true, /api/api/runtime/form/submit returns no objectId for formType=2. */
|
|
21
|
+
forceSubmitMissingObjectId = false;
|
|
22
|
+
forceSubmitMismatchedObjectId = false;
|
|
23
|
+
omitDraftSaveWorkItem = false;
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.state = createFakeState(options);
|
|
27
|
+
}
|
|
28
|
+
/** Test accessor: the stored default-form draft for a schema (undefined if none). */
|
|
29
|
+
sheetDraft(schemaCode) {
|
|
30
|
+
return this.state.sheets.get(schemaCode);
|
|
31
|
+
}
|
|
32
|
+
/** Test accessor: the last request body for the given path (or undefined). */
|
|
33
|
+
lastBody(path) {
|
|
34
|
+
for (let i = this.state.callLog.length - 1; i >= 0; i -= 1) {
|
|
35
|
+
if (this.state.callLog[i].path === path)
|
|
36
|
+
return this.state.callLog[i].body;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Test accessor: every recorded request body for the given path (in order). */
|
|
41
|
+
bodiesFor(path) {
|
|
42
|
+
return this.state.callLog.filter((entry) => entry.path === path).map((entry) => entry.body);
|
|
43
|
+
}
|
|
44
|
+
queriesFor(path) {
|
|
45
|
+
return this.state.callLog.filter((entry) => entry.path === path).map((entry) => entry.query);
|
|
46
|
+
}
|
|
47
|
+
seedRule(rule) {
|
|
48
|
+
const id = rule.id ?? `rule-${this.state.rules.size + 1}`;
|
|
49
|
+
this.state.rules.set(`${rule.schemaCode}#${rule.code}`, {
|
|
50
|
+
...rule,
|
|
51
|
+
id,
|
|
52
|
+
schemaCode: rule.schemaCode,
|
|
53
|
+
code: rule.code,
|
|
54
|
+
name: rule.name ?? rule.code,
|
|
55
|
+
enabled: rule.enabled ?? false,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
seedDataRow(schemaCode, data, id = `row-${this.state.dataRows.size + 1}`) {
|
|
59
|
+
this.state.dataRows.set(id, { bizObjectId: id, schemaCode, ...data });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reproduce ticket 07: mark a model's default list view as having no
|
|
63
|
+
* configured columns. Subsequent /runtime/query/list responses drop business
|
|
64
|
+
* columns from `data` (they read back null) though seedDataRow stored them.
|
|
65
|
+
*/
|
|
66
|
+
markListViewUnconfigured(schemaCode) {
|
|
67
|
+
this.state.unconfiguredListViews.add(schemaCode);
|
|
68
|
+
}
|
|
69
|
+
seedApp(app) {
|
|
70
|
+
this.state.apps.set(app.code, {
|
|
71
|
+
id: app.id ?? `app-${this.state.apps.size + 1}`,
|
|
72
|
+
code: app.code,
|
|
73
|
+
name: app.name ?? app.code,
|
|
74
|
+
groupId: app.groupId ?? [...this.state.appGroups.keys()][0] ?? "group-1",
|
|
75
|
+
...(app.published !== undefined ? { published: app.published } : {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
seedField(schemaCode, code, propertyType, published = true) {
|
|
79
|
+
this.state.fields.set(`${schemaCode}#${code}`, { code, name: code, type: String(propertyType), published });
|
|
80
|
+
}
|
|
81
|
+
async request(input) {
|
|
82
|
+
this.state.callLog.push({
|
|
83
|
+
path: input.path,
|
|
84
|
+
method: input.method,
|
|
85
|
+
body: input.body,
|
|
86
|
+
query: input.query,
|
|
87
|
+
...(input.multipart !== undefined ? { multipart: { filename: input.multipart.filename, contentType: input.multipart.contentType, bytes: input.multipart.content.length } } : {}),
|
|
88
|
+
});
|
|
89
|
+
if (this.transportDown)
|
|
90
|
+
throw new Error("simulated transport outage");
|
|
91
|
+
if (this.businessFailure && input.path.includes(this.businessFailure.pathFragment))
|
|
92
|
+
return { status: 200, body: { errcode: this.businessFailure.errcode, errmsg: "simulated business refusal", data: null } };
|
|
93
|
+
if (this.malformedResponse && input.path.includes(this.malformedResponse.pathFragment))
|
|
94
|
+
return { status: 200, body: this.malformedResponse.body };
|
|
95
|
+
const handler = this.route(input);
|
|
96
|
+
if (isBusinessRefusal(handler))
|
|
97
|
+
return { status: 200, body: { errcode: handler.errcode, errmsg: handler.errmsg, data: null } };
|
|
98
|
+
// Ticket 76: raw-envelope passthrough for platforms answers that carry no
|
|
99
|
+
// numeric errcode (workflow/create duplicate-schema live shape).
|
|
100
|
+
if (isMalformedEnvelope(handler))
|
|
101
|
+
return { status: 200, body: handler.payload };
|
|
102
|
+
// Ticket 106: non-envelope bodies (upload refId plain string, export ZIP
|
|
103
|
+
// bytes) pass through verbatim.
|
|
104
|
+
if (isRawResponse(handler))
|
|
105
|
+
return { status: 200, body: handler.payload };
|
|
106
|
+
if (handler === undefined)
|
|
107
|
+
return { status: 404, body: { errcode: 404, errmsg: "not found", data: null } };
|
|
108
|
+
return { status: 200, body: { errcode: 0, errmsg: "ok", data: handler } };
|
|
109
|
+
}
|
|
110
|
+
route(input) {
|
|
111
|
+
const ctx = {
|
|
112
|
+
state: this.state,
|
|
113
|
+
options: this.options,
|
|
114
|
+
forceSubmitMissingObjectId: this.forceSubmitMissingObjectId,
|
|
115
|
+
forceSubmitMismatchedObjectId: this.forceSubmitMismatchedObjectId,
|
|
116
|
+
omitDraftSaveWorkItem: this.omitDraftSaveWorkItem,
|
|
117
|
+
};
|
|
118
|
+
return dispatchFakeRoute(ctx, input);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { businessRefusal, NOT_HANDLED } from "./state.js";
|
|
2
|
+
import { QUERY_ACTION_RELATIVE, QUERY_ACTION_TYPE, validateButtonActionConfig } from "../lanes/list-view-shapes.js";
|
|
3
|
+
/**
|
|
4
|
+
* BizQueryController.dealHistoryDataOfButton (8.6.26 :535-631) injects these
|
|
5
|
+
* default query actions on the query/get read face when the stored view lacks
|
|
6
|
+
* them, so readback from a view that was never configured with actions still
|
|
7
|
+
* carries the platform buttons. The tool filters/decodes them; the fake mirrors
|
|
8
|
+
* the injection so unit tests see the same shape as live. Enum numbers come
|
|
9
|
+
* from the shared list-view shape descriptors (ticket 53).
|
|
10
|
+
*/
|
|
11
|
+
const PLATFORM_DEFAULT_ACTIONS = [
|
|
12
|
+
{ actionCode: "qr_code", name: "打印二维码", queryActionType: QUERY_ACTION_TYPE.index.QR_CODE, sortKey: 7, icon: "plus", associationType: QUERY_ACTION_RELATIVE.index.BIZSHEET, associationCode: "__schema__", clientType: null },
|
|
13
|
+
{ actionCode: "batch_print", name: "批量打印", queryActionType: QUERY_ACTION_TYPE.index.BATCH_PRINT, sortKey: 6, icon: "plus", associationType: QUERY_ACTION_RELATIVE.index.BIZSHEET, associationCode: "__schema__", clientType: null },
|
|
14
|
+
{ actionCode: "batch_update", name: "批量修改", queryActionType: QUERY_ACTION_TYPE.index.BATCH_UPDATE, sortKey: 3, icon: "plus", associationType: QUERY_ACTION_RELATIVE.index.BIZSHEET },
|
|
15
|
+
{ actionCode: "import_attachment", name: "导入附件", queryActionType: QUERY_ACTION_TYPE.index.IMPORT_ATTACHMENT, sortKey: 10, icon: "upload" },
|
|
16
|
+
{ actionCode: "export_attachment", name: "导出附件", queryActionType: QUERY_ACTION_TYPE.index.EXPORT_ATTACHMENT, sortKey: 11, icon: "download" },
|
|
17
|
+
{ actionCode: "batch_submit", name: "批量提交", queryActionType: QUERY_ACTION_TYPE.index.BATCH_SUBMIT, sortKey: 3, icon: "plus" },
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Live 2026-09-17: a freshly created PC LIST view is seeded with STORED
|
|
21
|
+
* actions (not merely injected on read): add/delete/import/export/editowner.
|
|
22
|
+
* Measured on two un-configured views (eq_repair_order, a fresh model's
|
|
23
|
+
* default view) whose read face carries the full 11 (these 5 stored + the 6
|
|
24
|
+
* injected below). Because `query/update` REPLACES actions, configuring one
|
|
25
|
+
* action used to delete these five — the list then rendered with NO Add
|
|
26
|
+
* button. The fake must seed them or a regression cannot be caught.
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_STORED_ACTIONS = [
|
|
29
|
+
{ actionCode: "add", name: "新增", queryActionType: QUERY_ACTION_TYPE.index.ADD, icon: "plus" },
|
|
30
|
+
{ actionCode: "delete", name: "删除", queryActionType: QUERY_ACTION_TYPE.index.DELETE, icon: "delete" },
|
|
31
|
+
{ actionCode: "import", name: "导入", queryActionType: QUERY_ACTION_TYPE.index.IMPORT, icon: "upload" },
|
|
32
|
+
{ actionCode: "export", name: "导出", queryActionType: QUERY_ACTION_TYPE.index.EXPORT, icon: "download" },
|
|
33
|
+
{ actionCode: "editowner", name: "修改拥有者", queryActionType: QUERY_ACTION_TYPE.index.EDITOWNER, icon: "plus" },
|
|
34
|
+
];
|
|
35
|
+
function seedStoredActions(view) {
|
|
36
|
+
if (view.queryActions !== undefined)
|
|
37
|
+
return;
|
|
38
|
+
view.queryActions = DEFAULT_STORED_ACTIONS.map((action) => ({ ...action, schemaCode: view.schemaCode, clientType: "PC", associationType: action.actionCode === "add" ? QUERY_ACTION_RELATIVE.index.BIZSHEET : undefined, associationCode: action.actionCode === "add" ? view.schemaCode : undefined }));
|
|
39
|
+
}
|
|
40
|
+
function injectDefaultActions(schemaCode, stored) {
|
|
41
|
+
const actions = stored.map((action) => ({ ...action }));
|
|
42
|
+
const hasType = (type) => actions.some((action) => action.queryActionType === type);
|
|
43
|
+
const hasImportExport = hasType(21) || hasType(22);
|
|
44
|
+
for (const template of PLATFORM_DEFAULT_ACTIONS) {
|
|
45
|
+
const type = Number(template.queryActionType);
|
|
46
|
+
if (type === 21 || type === 22) {
|
|
47
|
+
if (hasImportExport)
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
else if (hasType(type))
|
|
51
|
+
continue;
|
|
52
|
+
const row = { ...template, schemaCode };
|
|
53
|
+
if (row.associationCode === "__schema__")
|
|
54
|
+
row.associationCode = schemaCode;
|
|
55
|
+
if (row.clientType === undefined)
|
|
56
|
+
row.clientType = "PC";
|
|
57
|
+
actions.push(row);
|
|
58
|
+
}
|
|
59
|
+
return actions;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* BizQueryController.executeReSetQueryActionsSortKey (:962-998) resets sortKey
|
|
63
|
+
* to i+1 and backfills queryId/schemaCode/clientType; ModelManagementFacadeImpl
|
|
64
|
+
* .updateBizQuery (:1977-1987) copies a PC `add` action with a relativeCode to
|
|
65
|
+
* an APP `add`. The fake mirrors both so readback matches live.
|
|
66
|
+
*/
|
|
67
|
+
function normalizeStoredActions(bodyActions, clientType, schemaCode) {
|
|
68
|
+
const actions = bodyActions.map((action, index) => ({
|
|
69
|
+
...action,
|
|
70
|
+
sortKey: index + 1,
|
|
71
|
+
clientType,
|
|
72
|
+
schemaCode,
|
|
73
|
+
// Persisted BizQueryAction boolean columns default to false (observed live);
|
|
74
|
+
// platform-injected read-face actions are not persisted, so they stay null.
|
|
75
|
+
systemAction: action.systemAction ?? false,
|
|
76
|
+
customService: action.customService ?? false,
|
|
77
|
+
}));
|
|
78
|
+
for (const action of actions) {
|
|
79
|
+
if (action.actionCode === "add" && (action.associationCode === undefined || action.associationCode === null || action.associationCode === "")) {
|
|
80
|
+
action.associationType = QUERY_ACTION_RELATIVE.index.BIZSHEET;
|
|
81
|
+
action.associationCode = schemaCode;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (clientType === "PC") {
|
|
85
|
+
const pcAdd = actions.find((action) => action.actionCode === "add" && action.clientType === "PC" && action.associationCode != null);
|
|
86
|
+
if (pcAdd) {
|
|
87
|
+
const existing = actions.findIndex((action) => action.clientType === "APP" && action.actionCode === "add");
|
|
88
|
+
if (existing >= 0)
|
|
89
|
+
actions.splice(existing, 1);
|
|
90
|
+
actions.push({ ...pcAdd, clientType: "APP", sortKey: actions.length + 1 });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return actions;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* BizButtonFacadeImpl.addBizButtonList(String,String,ButtonTriggerType,String)
|
|
97
|
+
* (8.6.26 :112-137) returns early on a blank `customButtonJson`, so an omitted
|
|
98
|
+
* key leaves the stored buttons untouched; a JSON array fully replaces them
|
|
99
|
+
* (batchDeleteByTrigger then re-add), forcing schemaCode/triggerType QUERY/
|
|
100
|
+
* triggerCode view code and sortKey = array index. The fake mirrors that so the
|
|
101
|
+
* query/update round trip matches live.
|
|
102
|
+
*/
|
|
103
|
+
function replaceStoredButtons(customButtonJson, schemaCode, triggerCode) {
|
|
104
|
+
if (typeof customButtonJson !== "string" || customButtonJson.trim() === "")
|
|
105
|
+
return undefined;
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(customButtonJson);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
if (!Array.isArray(parsed))
|
|
114
|
+
return undefined;
|
|
115
|
+
for (const button of parsed) {
|
|
116
|
+
if (!button || typeof button !== "object" || Array.isArray(button))
|
|
117
|
+
return undefined;
|
|
118
|
+
try {
|
|
119
|
+
validateButtonActionConfig(button);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return parsed.map((button, index) => ({
|
|
126
|
+
...button,
|
|
127
|
+
schemaCode: button.schemaCode ?? schemaCode,
|
|
128
|
+
triggerType: "QUERY",
|
|
129
|
+
triggerCode,
|
|
130
|
+
sortKey: index,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
/** W1-list-view lane: design-time query views. Twin of src/lanes/list-view.ts. */
|
|
134
|
+
export const listViewRoute = (ctx, input) => {
|
|
135
|
+
const q = input.query ?? {};
|
|
136
|
+
const body = (input.body ?? {});
|
|
137
|
+
switch (input.path) {
|
|
138
|
+
case "/api/api/app/query/create": {
|
|
139
|
+
const code = String(body.code ?? `view-${ctx.state.listViews.size + 1}`);
|
|
140
|
+
// Live fidelity (ticket 81): creating a view whose code already exists
|
|
141
|
+
// answers 303002 编码重复 — a post-timeout retry hits exactly this.
|
|
142
|
+
const existing = ctx.state.listViews.get(code);
|
|
143
|
+
if (existing && existing.schemaCode === String(body.schemaCode ?? ""))
|
|
144
|
+
return businessRefusal(303002, `编码重复:${code}`);
|
|
145
|
+
const created = { code, name: String(body.name), schemaCode: String(body.schemaCode ?? ""), queryColumns: [] };
|
|
146
|
+
seedStoredActions(created);
|
|
147
|
+
ctx.state.listViews.set(code, created);
|
|
148
|
+
return { code };
|
|
149
|
+
}
|
|
150
|
+
case "/api/api/app/query/get_header": {
|
|
151
|
+
const view = ctx.state.listViews.get(q.code);
|
|
152
|
+
return view
|
|
153
|
+
? { id: `view-${view.code}`, code: view.code, name: view.name, schemaCode: view.schemaCode, type: "LIST", showOnPc: false, publish: view.publish === true, ...(view.queryPresentationType !== undefined ? { queryPresentationType: view.queryPresentationType } : {}) }
|
|
154
|
+
: null;
|
|
155
|
+
}
|
|
156
|
+
case "/api/api/app/query/update_header": {
|
|
157
|
+
// BizQueryController.updateHeader → updateBizQuery(BizQueryHeaderModel):
|
|
158
|
+
// a PLAIN header update with NO publish gate (unlike /query/update, whose
|
|
159
|
+
// body-driven queryPresentationType assignment is skipped once the view
|
|
160
|
+
// is published — the BUG-11 live root cause).
|
|
161
|
+
const code = String(body.code ?? "");
|
|
162
|
+
const view = ctx.state.listViews.get(code);
|
|
163
|
+
if (!view)
|
|
164
|
+
return null;
|
|
165
|
+
if (body.queryPresentationType !== undefined)
|
|
166
|
+
view.queryPresentationType = body.queryPresentationType;
|
|
167
|
+
if (typeof body.name === "string")
|
|
168
|
+
view.name = body.name;
|
|
169
|
+
if (typeof body.publish === "boolean")
|
|
170
|
+
view.publish = body.publish;
|
|
171
|
+
return { code, queryPresentationType: view.queryPresentationType ?? null };
|
|
172
|
+
}
|
|
173
|
+
case "/api/api/app/query/get": {
|
|
174
|
+
// Live shape: every model owns a default list view (code = schemaCode)
|
|
175
|
+
// that starts without business columns. Lazy-create on first fetch.
|
|
176
|
+
// BizQueryFacadeImpl.getBizQuery sets model.clientType from the request
|
|
177
|
+
// (query/get requires it; query/update refuses a null clientType), so the
|
|
178
|
+
// fake echoes it too.
|
|
179
|
+
const schemaCode = String(q.schemaCode ?? "");
|
|
180
|
+
const code = String(q.code ?? schemaCode);
|
|
181
|
+
let view = ctx.state.listViews.get(code);
|
|
182
|
+
if (!view && code === schemaCode) {
|
|
183
|
+
// Live: a model's default view is created PUBLISHED (h_biz_query.publish=1).
|
|
184
|
+
view = { code, name: code, schemaCode, queryColumns: [], publish: true };
|
|
185
|
+
seedStoredActions(view);
|
|
186
|
+
ctx.state.listViews.set(code, view);
|
|
187
|
+
}
|
|
188
|
+
if (!view)
|
|
189
|
+
return null;
|
|
190
|
+
// BizQueryFacadeImpl.getBizQuery overwrites a condition's stored options
|
|
191
|
+
// with the field's own options before readback, so the read face is
|
|
192
|
+
// field-derived rather than the persisted condition value.
|
|
193
|
+
const queryConditions = view.queryConditions?.map((condition) => {
|
|
194
|
+
const propertyCode = String(condition.propertyCode ?? "");
|
|
195
|
+
const field = propertyCode.includes(".") ? undefined : ctx.state.fields.get(`${view.schemaCode || schemaCode}#${propertyCode}`);
|
|
196
|
+
return { ...condition, options: field?.options ?? null };
|
|
197
|
+
});
|
|
198
|
+
const clientType = String(q.clientType ?? "PC");
|
|
199
|
+
const queryActions = clientType === "PC" ? injectDefaultActions(view.schemaCode || schemaCode, view.queryActions ?? []) : view.queryActions;
|
|
200
|
+
return {
|
|
201
|
+
id: `view-${code}`,
|
|
202
|
+
code: view.code,
|
|
203
|
+
name: view.name,
|
|
204
|
+
schemaCode: view.schemaCode || schemaCode,
|
|
205
|
+
clientType,
|
|
206
|
+
queryColumns: view.queryColumns,
|
|
207
|
+
...(view.options !== undefined ? { options: view.options } : {}),
|
|
208
|
+
...(view.queryPresentationType !== undefined ? { queryPresentationType: view.queryPresentationType } : {}),
|
|
209
|
+
...(view.queryGanttModel !== undefined ? { queryGanttModel: view.queryGanttModel } : {}),
|
|
210
|
+
...(view.queryLayer !== undefined ? { queryLayer: view.queryLayer } : {}),
|
|
211
|
+
...(view.multiTableHeaderConfig !== undefined ? { multiTableHeaderConfig: view.multiTableHeaderConfig } : {}),
|
|
212
|
+
...(view.querySorts ? { querySorts: view.querySorts } : {}),
|
|
213
|
+
...(queryConditions ? { queryConditions } : {}),
|
|
214
|
+
...(queryActions && queryActions.length > 0 ? { queryActions } : {}),
|
|
215
|
+
...(view.queryPresentation ? { queryPresentation: view.queryPresentation } : {}),
|
|
216
|
+
...(view.customButtons ? { customButtons: view.customButtons } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
case "/api/api/app/query/update": {
|
|
220
|
+
const code = String(body.code ?? "");
|
|
221
|
+
const view = ctx.state.listViews.get(code);
|
|
222
|
+
if (!view)
|
|
223
|
+
return null;
|
|
224
|
+
// BizQueryController.update calls validateNotEmptyGantt (:1121-1136) before
|
|
225
|
+
// updateBizQuery: a GANTT view requires queryGanttModel with non-null
|
|
226
|
+
// startTime/endTime/title codes.
|
|
227
|
+
if (body.queryPresentationType === "GANTT") {
|
|
228
|
+
const gantt = body.queryGanttModel;
|
|
229
|
+
if (!gantt || typeof gantt !== "object")
|
|
230
|
+
return businessRefusal(10005, "甘特图参数不能为空");
|
|
231
|
+
if (gantt.startTimePropertyCode == null)
|
|
232
|
+
return businessRefusal(10005, "甘特图开始时间映射参数不能为空!");
|
|
233
|
+
if (gantt.endTimePropertyCode == null)
|
|
234
|
+
return businessRefusal(10005, "甘特图结束时间映射参数不能为空!");
|
|
235
|
+
if (gantt.titlePropertyCode == null)
|
|
236
|
+
return businessRefusal(10005, "甘特图标题映射参数不能为空!");
|
|
237
|
+
}
|
|
238
|
+
if (Array.isArray(body.queryColumns))
|
|
239
|
+
view.queryColumns = body.queryColumns;
|
|
240
|
+
// BizQueryModel.options (String) is replaced whenever the update body
|
|
241
|
+
// carries the key; an omitted key leaves it untouched (updateBizQuery's
|
|
242
|
+
// name/options diff branch, ModelManagementFacadeImpl:1966-1973).
|
|
243
|
+
if (body.options !== undefined)
|
|
244
|
+
view.options = body.options;
|
|
245
|
+
// BizQueryHeaderModel.queryPresentationType is assigned by updateBizQuery
|
|
246
|
+
// (:1955) but the entity update block is SKIPPED when the stored view is
|
|
247
|
+
// published (ModelManagementFacadeImpl:1998-2010) — a live-verified
|
|
248
|
+
// silent drop (BUG-11). The tool must therefore drive /update_header for
|
|
249
|
+
// an already-published view.
|
|
250
|
+
if (body.queryPresentationType !== undefined && view.publish !== true)
|
|
251
|
+
view.queryPresentationType = body.queryPresentationType;
|
|
252
|
+
// saveBizQueryDetail only swaps queryGantt when the submitted model is
|
|
253
|
+
// non-null (:178-183), so an omitted key leaves the stored gantt.
|
|
254
|
+
if (body.queryGanttModel && typeof body.queryGanttModel === "object")
|
|
255
|
+
view.queryGanttModel = body.queryGanttModel;
|
|
256
|
+
// saveBizQueryDetail assigns queryLayer/multiTableHeaderConfig
|
|
257
|
+
// unconditionally (:184-186); the tool only sends them when configured.
|
|
258
|
+
if (Array.isArray(body.queryLayer))
|
|
259
|
+
view.queryLayer = body.queryLayer;
|
|
260
|
+
if (body.multiTableHeaderConfig !== undefined)
|
|
261
|
+
view.multiTableHeaderConfig = body.multiTableHeaderConfig;
|
|
262
|
+
if (Array.isArray(body.querySorts))
|
|
263
|
+
view.querySorts = body.querySorts;
|
|
264
|
+
if (Array.isArray(body.queryConditions))
|
|
265
|
+
view.queryConditions = body.queryConditions;
|
|
266
|
+
if (Array.isArray(body.queryActions)) {
|
|
267
|
+
view.queryActions = normalizeStoredActions(body.queryActions, String(body.clientType ?? "PC"), String(body.schemaCode ?? view.schemaCode ?? ""));
|
|
268
|
+
}
|
|
269
|
+
// BizQueryDetailServiceImpl.saveBizQueryDetail removes every presentation
|
|
270
|
+
// whose clientType matches and re-adds the submitted one; an omitted
|
|
271
|
+
// presentation (absent body key) leaves the stored one untouched.
|
|
272
|
+
if (body.queryPresentation && typeof body.queryPresentation === "object") {
|
|
273
|
+
view.queryPresentation = body.queryPresentation;
|
|
274
|
+
}
|
|
275
|
+
const buttons = replaceStoredButtons(body.customButtonJson, String(body.schemaCode ?? view.schemaCode ?? ""), code);
|
|
276
|
+
if (buttons !== undefined)
|
|
277
|
+
view.customButtons = buttons;
|
|
278
|
+
return { code: view.code };
|
|
279
|
+
}
|
|
280
|
+
case "/api/api/app/query/list":
|
|
281
|
+
return [...ctx.state.listViews.values()].map((view) => ({ code: view.code, name: view.name }));
|
|
282
|
+
case "/api/api/app/query/delete":
|
|
283
|
+
ctx.state.listViews.delete(q.code);
|
|
284
|
+
return null;
|
|
285
|
+
default:
|
|
286
|
+
return NOT_HANDLED;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
//# sourceMappingURL=list-view.js.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { relationTargetOf } from "../field-families.js";
|
|
2
|
+
import { MODEL_REFERENCED_DELETE_ERRCODE } from "../lanes/model-contract.js";
|
|
3
|
+
import { businessRefusal, NOT_HANDLED } from "./state.js";
|
|
4
|
+
/** model-management lane: app tree, model CRUD, physical delete. Twin of src/lanes/model.ts. */
|
|
5
|
+
export const modelRoute = (ctx, input) => {
|
|
6
|
+
const q = input.query ?? {};
|
|
7
|
+
const body = (input.body ?? {});
|
|
8
|
+
switch (input.path) {
|
|
9
|
+
case "/api/api/app/apppackage/trees": {
|
|
10
|
+
// Live fidelity (ticket 68): the tree row id is the AppFunction id, not
|
|
11
|
+
// the biz-model record id; appCode filters the tree, treeHidden models
|
|
12
|
+
// keep a record but no tree node. Ticket 83: rows carry parentId (live
|
|
13
|
+
// AppFunctionModel shape; bizmodels/update requires it) and the node
|
|
14
|
+
// display name comes from nodeName (the menu node is renamed
|
|
15
|
+
// independently of the schema record).
|
|
16
|
+
const appCode = q.appCode === undefined ? undefined : String(q.appCode);
|
|
17
|
+
// Live 8.6.26 fidelity (2026-09-17): the tree is NESTED — a Folder row
|
|
18
|
+
// carrying its BizModel children — not a flat list. The environment lane
|
|
19
|
+
// used a top-level map and silently dropped every model, so a live
|
|
20
|
+
// discover_environment with applicationCode returned no models. Consumers
|
|
21
|
+
// must flattenTreeRows (as resolveModelTreeNode already does); the fake
|
|
22
|
+
// must nest so a regression cannot pass.
|
|
23
|
+
const children = [...ctx.state.models.values()]
|
|
24
|
+
.filter((model) => !model.treeHidden && (appCode === undefined || (model.appCode ?? "test") === appCode))
|
|
25
|
+
.map((model) => ({ id: model.id, type: "BizModel", code: model.code, name: model.nodeName ?? model.name, parentId: "folder-1" }));
|
|
26
|
+
return [{ id: "folder-1", type: "FOLDER", code: "general", name: "General", children }];
|
|
27
|
+
}
|
|
28
|
+
case "/api/api/app/bizmodels/list_schema":
|
|
29
|
+
return [...ctx.state.models.values()].map((model) => ({ id: model.recordId, schemaCode: model.code, name: model.name }));
|
|
30
|
+
case "/api/api/app/bizmodels/create": {
|
|
31
|
+
const code = String(body.bizSchemaCode ?? body.code);
|
|
32
|
+
const n = ctx.state.models.size + 1;
|
|
33
|
+
ctx.state.models.set(code, {
|
|
34
|
+
id: `model-${n}-fn`,
|
|
35
|
+
recordId: `model-${n}-rec`,
|
|
36
|
+
code,
|
|
37
|
+
name: String(body.name ?? code),
|
|
38
|
+
appCode: String(body.applicationCode ?? body.appCode ?? "test"),
|
|
39
|
+
});
|
|
40
|
+
// Live fidelity (ticket 42): bizmodels/create seeds NO form — the model
|
|
41
|
+
// has no h_biz_sheet row until the form is created (bizsheet/create, or
|
|
42
|
+
// form.draft's scaffold-on-absent path). Do not pre-seed a sheet here.
|
|
43
|
+
return { schemaCode: code };
|
|
44
|
+
}
|
|
45
|
+
case "/api/api/app/apppackage/physically_delete": {
|
|
46
|
+
const rows = Array.isArray(input.body) ? input.body : [];
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
if (row.type === undefined || row.type === null || row.type === "") {
|
|
49
|
+
ctx.state.apps.delete(String(row.code));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const code = String(row.code);
|
|
53
|
+
// deleteFunctions semantics (ticket 23): a model referenced by
|
|
54
|
+
// another model's relation field cannot be deleted. Live: the sync
|
|
55
|
+
// face answers errcode 0 and the async dispatcher fails with
|
|
56
|
+
// ServiceException "xxx被模型 yyy 引用,不能删除"
|
|
57
|
+
// (AppIntegrationFacadeImpl.deleteFunctions:2201); the fake surfaces
|
|
58
|
+
// the check synchronously (MODEL_REFERENCED_DELETE_ERRCODE stands for
|
|
59
|
+
// the ServiceException envelope family) so the blocker and the
|
|
60
|
+
// referencing list are visible even when a caller bypasses the tool
|
|
61
|
+
// precheck. The live per-column async cascade (3-4s/column) is not
|
|
62
|
+
// simulated — the fake removes the model synchronously.
|
|
63
|
+
const referencers = [
|
|
64
|
+
...new Set([...ctx.state.fields.entries()]
|
|
65
|
+
.filter(([key, field]) => !key.startsWith(`${code}#`) && relationTargetOf(field) === code)
|
|
66
|
+
.map(([key]) => key.split("#")[0])),
|
|
67
|
+
];
|
|
68
|
+
if (referencers.length > 0)
|
|
69
|
+
return businessRefusal(MODEL_REFERENCED_DELETE_ERRCODE, `${code}被模型 ${referencers.join(",")} 引用,不能删除`);
|
|
70
|
+
// Ticket 68 live fidelity: physically_delete resolves each row id as an
|
|
71
|
+
// AppFunction id (removeBizModel → getAppFunction(id)); a biz-model
|
|
72
|
+
// record id is refused with 601003 业务模型不存在.
|
|
73
|
+
const model = ctx.state.models.get(code);
|
|
74
|
+
if (model && row.id !== model.id)
|
|
75
|
+
return businessRefusal(601003, "业务模型不存在");
|
|
76
|
+
ctx.state.models.delete(code);
|
|
77
|
+
ctx.state.sheets.delete(code);
|
|
78
|
+
// The cascade drops the model's own bizproperty rows too — without
|
|
79
|
+
// this cleanup a deleted referencing model would keep blocking the
|
|
80
|
+
// referenced model's delete (dependency-inverted chains must pass).
|
|
81
|
+
for (const key of [...ctx.state.fields.keys()])
|
|
82
|
+
if (key.startsWith(`${code}#`))
|
|
83
|
+
ctx.state.fields.delete(key);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
case "/api/api/app/bizmodels/exits_bizschema":
|
|
88
|
+
return ctx.state.models.has(q.schemaCode);
|
|
89
|
+
case "/api/api/app/bizmodels/get_summary":
|
|
90
|
+
// BizSchemaController.get_summary: an absent schema answers "" (ok), an
|
|
91
|
+
// existing one returns its summary string.
|
|
92
|
+
return ctx.state.models.get(q.schemaCode)?.summary ?? "";
|
|
93
|
+
case "/api/api/app/bizmodels/update_summary": {
|
|
94
|
+
// BizSchemaController.updateSummary: non-empty summary required
|
|
95
|
+
// (validateNotEmpty) and a missing schema answers the
|
|
96
|
+
// BIZ_SCHEMA_NOT_PUBLISHED errcode (ErrorCode.java:194 → 300014).
|
|
97
|
+
const schemaCode = String(body.schemaCode ?? "");
|
|
98
|
+
if (!ctx.state.models.has(schemaCode))
|
|
99
|
+
return businessRefusal(300014, "数据模型未发布");
|
|
100
|
+
const model = ctx.state.models.get(schemaCode);
|
|
101
|
+
model.summary = String(body.summary ?? "");
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
case "/api/api/app/bizmodels/get_all": {
|
|
105
|
+
const model = ctx.state.models.get(q.schemaCode);
|
|
106
|
+
return model ? { id: model.recordId, name: model.name, modelType: "LIST" } : null;
|
|
107
|
+
}
|
|
108
|
+
case "/api/api/app/bizmodels/update": {
|
|
109
|
+
// Ticket 83 (source-pinned BizSchemaController.update :266-305): the
|
|
110
|
+
// endpoint patches the AppFunction MENU NODE — bizSchemaCode, id, name,
|
|
111
|
+
// parentId are all validateNotEmpty-required; the schema RECORD is not
|
|
112
|
+
// migrated (get_all keeps the original name/type per probe evidence),
|
|
113
|
+
// so the rename lands on nodeName only.
|
|
114
|
+
const schemaCode = String(body.bizSchemaCode ?? body.schemaCode ?? "");
|
|
115
|
+
const model = ctx.state.models.get(schemaCode);
|
|
116
|
+
if (!model)
|
|
117
|
+
return businessRefusal(300014, "数据模型未发布");
|
|
118
|
+
const nodeId = String(body.id ?? "");
|
|
119
|
+
const name = String(body.name ?? "");
|
|
120
|
+
const parentId = String(body.parentId ?? "");
|
|
121
|
+
if (!nodeId)
|
|
122
|
+
return businessRefusal(10005, "id不能为空");
|
|
123
|
+
if (!name)
|
|
124
|
+
return businessRefusal(10005, "名称不能为空");
|
|
125
|
+
if (!parentId)
|
|
126
|
+
return businessRefusal(10005, "父id不可为空");
|
|
127
|
+
if (nodeId !== model.id)
|
|
128
|
+
return businessRefusal(10005, `id不是模型 ${schemaCode} 的应用树节点`);
|
|
129
|
+
model.nodeName = name;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
return NOT_HANDLED;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=model.js.map
|