@qingflow-tech/qingflow-app-user-mcp 1.0.34 → 1.0.35
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/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +6 -2
- package/skills/qingflow-app-builder/references/create-app.md +2 -0
- package/skills/qingflow-app-builder/references/match-rules.md +15 -0
- package/skills/qingflow-app-builder/references/tool-selection.md +2 -2
- package/skills/qingflow-app-builder/references/update-schema.md +3 -0
- package/skills/qingflow-app-builder/references/update-views.md +6 -4
- package/skills/qingflow-record-insert/SKILL.md +2 -0
- package/src/qingflow_mcp/builder_facade/models.py +20 -1
- package/src/qingflow_mcp/builder_facade/service.py +9 -9
- package/src/qingflow_mcp/tools/ai_builder_tools.py +264 -4
- package/src/qingflow_mcp/tools/record_tools.py +38 -1
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @qingflow-tech/qingflow-app-user-mcp@1.0.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-user-mcp@1.0.35
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.35 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -47,6 +47,7 @@ Builder schema inputs should follow agent-intuitive semantics:
|
|
|
47
47
|
- icons may be `icon/color`, `icon_name/icon_color`, `icon_config`, or `icon: {name, color}`
|
|
48
48
|
- `single_select` / `multi_select` options may be strings or objects such as `{label, value}`; tools normalize to option labels
|
|
49
49
|
- relation fields need a target plus `display_field` and `visible_fields`
|
|
50
|
+
- do not create built-in system fields as form fields: `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, `流程标题`. They are platform-generated; only reference supported system fields where a tool explicitly says so, such as button `source_field: "数据ID"`
|
|
50
51
|
|
|
51
52
|
## Public Tools You Should Think In
|
|
52
53
|
|
|
@@ -84,6 +85,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
|
|
|
84
85
|
- dataset BI reports are not created or edited by `app_charts_apply` yet; create them in QingBI first, then attach the existing report with `app_associated_resources_apply` and `report_source="dataset"`
|
|
85
86
|
- it does not attach the report to the Qingflow app associated-resource display; use `app_associated_resources_apply` for that
|
|
86
87
|
- supported `chart_type` values include legacy public aliases `target`, `table` and QingBI types such as `summary`, `columnar`, `area`, `stacked_area`, `funnel`, `waterfall`, `gauge`, `heatmap`, `histogram`, `treemap`, `radar`, `stacked_bar`, `stacked_column`, `scatter`, `ring`, `rose`, `dualaxes`, `map`, and `timeline`
|
|
88
|
+
- chart `filters` use the unified fixed-filter DSL: `field_name + operator + value/values`; the tool compiles them to QingBI string judge types
|
|
87
89
|
- use `patch_charts` for changing a chart name, visibility, filters, or one config fragment on an existing chart
|
|
88
90
|
- `visibility` is a public capability and should be treated as a base-only permission update
|
|
89
91
|
- do not model chart visibility changes as raw config rewrites
|
|
@@ -108,14 +110,15 @@ Treat these as the official surface. Do not default to `package_create`, `packag
|
|
|
108
110
|
- before creating a resource, check `app_get.associated_resources` for the same `target_app_key + view_key/chart_key`; if it already exists, use `patch_resources` with that `associated_item_id`
|
|
109
111
|
- `client_key` is only a same-call reference for `view_configs[].associated_item_refs`; it is not saved and cannot deduplicate later calls
|
|
110
112
|
- do not pass backend raw `sourceType`; view resources infer the internal Qingflow view source, report/chart resources default to BI app reports, and existing dataset reports use `report_source="dataset"`
|
|
111
|
-
- use `match_mappings` for associated view/report filters; dynamic
|
|
113
|
+
- use `match_mappings` for associated view/report filters; dynamic context matches use `source_field`, static filters use `value`; do not handwrite raw `matchRules`
|
|
112
114
|
- if field type compatibility is unclear, read [references/match-rules.md](references/match-rules.md)
|
|
113
115
|
- Views and flows:
|
|
114
116
|
- stay on `app_views_apply` / `app_flow_apply`
|
|
115
117
|
- use `patch_views` for existing-view parameter changes such as `query_conditions`, `filters`, `columns`, or visibility
|
|
116
118
|
- builder view writes use raw `view_key` values from `app_get.views`; `custom:<viewKey>` is a record-data `view_id` form, not a builder `view_key`
|
|
119
|
+
- do not create platform default/system views named `全部数据`, `我的数据`, `我发起的`, `待办`, `已办`, or `抄送`; they are built in. New views must use business-specific names. To change a built-in view, patch by its existing raw `view_key`
|
|
117
120
|
- use `builder_tool_contract` whenever the minimal legal shape is unclear
|
|
118
|
-
- keep view `filters` and `query_conditions` separate: `filters` are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
|
|
121
|
+
- keep view `filters` and `query_conditions` separate: `filters` use `field_name + operator + value/values` and are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
|
|
119
122
|
- new views default the associated report/view display area to visible with `limit_type="all"`; existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched
|
|
120
123
|
- configure associated reports/views through `app_associated_resources_apply`, not through `app_views_apply`
|
|
121
124
|
|
|
@@ -161,6 +164,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
|
|
|
161
164
|
- Do not use raw `portal_*` writes or raw `qingbi_report_*` writes as the default builder strategy.
|
|
162
165
|
- `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, and `app_views_apply` publish by default; `app_custom_buttons_apply` and `app_associated_resources_apply` publish after at least one write succeeds and do not accept a draft-only `publish` parameter; `app_charts_apply` is immediate-live without publish.
|
|
163
166
|
- When creating or updating fields, configure the app data title/cover directly in field JSON: `as_data_title: true` is required on exactly one readable top-level title field; `as_data_cover: true` is optional and only valid on one top-level `attachment` field.
|
|
167
|
+
- Do not add platform system fields to `add_fields`; if the user asks for record id, number, applicant, creation/update time, or workflow status, use the built-in data/list/readback fields instead of creating duplicate controls.
|
|
164
168
|
- If the same validation error repeats twice, stop guessing and re-read `builder_tool_contract`.
|
|
165
169
|
- For workflow assignees, prefer `role_search` over explicit members unless the user explicitly wants named members.
|
|
166
170
|
- Public flow building is still intentionally limited to stable linear workflows. If a requirement sounds like branches/conditions, explain the limitation instead of freehanding unsupported graph shapes.
|
|
@@ -43,6 +43,8 @@ Use this pattern instead:
|
|
|
43
43
|
|
|
44
44
|
## Example
|
|
45
45
|
|
|
46
|
+
When designing fields, do not add platform system fields such as `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`. Qingflow provides them automatically; create only business fields.
|
|
47
|
+
|
|
46
48
|
Create a new package only after the user confirms package creation:
|
|
47
49
|
|
|
48
50
|
```json
|
|
@@ -4,6 +4,12 @@ Use this reference for builder-side field matching rules in custom buttons and a
|
|
|
4
4
|
|
|
5
5
|
This is not the same thing as `record_access` analysis filters or normal view `filters`.
|
|
6
6
|
|
|
7
|
+
Unified public semantics:
|
|
8
|
+
|
|
9
|
+
- Fixed filters use `field_name + operator + value/values`, for example view `filters` and chart `filters`.
|
|
10
|
+
- Current-record context matches use `target_field + operator + source_field/value`, for example associated resource `match_mappings` and add-data button mappings.
|
|
11
|
+
- Do not handwrite `judgeType`, `judgeValues`, or `matchRules`; the CLI compiles them for the target backend protocol.
|
|
12
|
+
|
|
7
13
|
## Where These Rules Apply
|
|
8
14
|
|
|
9
15
|
- `app_custom_buttons_apply.upsert_buttons[].trigger_add_data_config.field_mappings`
|
|
@@ -80,6 +86,15 @@ Use `match_mappings` for associated view/report filters:
|
|
|
80
86
|
|
|
81
87
|
Dynamic conditions use `source_field`; static conditions use `value`.
|
|
82
88
|
|
|
89
|
+
Recommended operators are `eq`, `neq`, `in`, `contains`, `gte`, `lte`, `is_empty`, and `not_empty`; aliases such as `equal`, `equals`, `=`, `!=`, `any_of`, `one_of`, and `empty` are accepted. `field_name` and `field` can be used as aliases for `target_field`. Static single-value filters should use `value`; a single-item `values` array is also accepted.
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{"target_field": "客户ID", "operator": "eq", "source_field": "数据ID"}
|
|
95
|
+
{"target_field": "状态", "operator": "eq", "value": "有效"}
|
|
96
|
+
```
|
|
97
|
+
|
|
83
98
|
For an existing associated resource, put the same filter mapping under `patch_resources[].set`:
|
|
84
99
|
|
|
85
100
|
```json
|
|
@@ -54,8 +54,8 @@ These execute normalized patches. Some app apply tools publish by default and st
|
|
|
54
54
|
- `app_flow_apply`: replace workflow
|
|
55
55
|
- `app_views_apply`: use `patch_views` for existing-view parameter replacement; use `upsert_views` for creation or full target config; remove views by key; new views default associated report/view display to visible with `limit_type="all"`
|
|
56
56
|
- `app_custom_buttons_apply`: use `patch_buttons` for existing-button parameter replacement; use `upsert_buttons` for creation or full target config; configure add-data field mappings/default values; bind buttons to header/detail/list view positions; `placement=list` maps to the backend `INSIDE` row/list button position; merge-mode view configs require `buttons`; use `view_configs[].mode="replace"` or `buttons=[]` to clear a view's custom button bindings. For child-record creation linked to the current source record, map `source_field: "数据ID"` to the target relation field.
|
|
57
|
-
- `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call.
|
|
58
|
-
- `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique
|
|
57
|
+
- `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` with `target_field + operator + source_field/value` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call.
|
|
58
|
+
- `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `filters` with `field_name + operator + value/values`; the tool compiles them to QingBI string judge types. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique
|
|
59
59
|
- `portal_apply`: create or replace-update portal pages; use `dash_key` for update mode or `package_id + dash_name` for create mode; create mode only prechecks package add_app, matching backend portal creation; edit mode may omit `sections` for base-info-only updates; when sections are supplied they still use replace semantics
|
|
60
60
|
|
|
61
61
|
For object-level updates, the safe partial syntax is `patch_*` with the object's real selector field plus `set` and optional `unset`. `selector` is only a concept, not a literal key. Examples: `patch_views: [{"view_key": "VIEW_KEY", "set": {...}}]`, `patch_buttons: [{"button_id": 1001, "set": {...}}]`, `patch_resources: [{"associated_item_id": 123, "set": {...}}]`, `patch_charts: [{"chart_id": 456, "set": {...}}]`. The tool reads the current backend config, merges the patch, then submits the full backend payload internally. Do not send a partial `upsert_*` and expect missing required fields to be preserved.
|
|
@@ -10,6 +10,8 @@ Use this when the app already exists and the task is only about fields.
|
|
|
10
10
|
|
|
11
11
|
## Example
|
|
12
12
|
|
|
13
|
+
Do not add platform system fields as form controls: `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, `流程标题`. They are generated by Qingflow; use readback/list fields when you need them.
|
|
14
|
+
|
|
13
15
|
Read current fields first:
|
|
14
16
|
|
|
15
17
|
```json
|
|
@@ -70,3 +72,4 @@ Treat it as uncertain write state. Read back with `app_get_fields` before retryi
|
|
|
70
72
|
- `checkbox -> multi_select`
|
|
71
73
|
- `as_data_title: true` marks the app data title; the final form must have exactly one top-level data title field
|
|
72
74
|
- `as_data_cover: true` marks the app data cover; the cover is optional and must be a top-level `attachment` field
|
|
75
|
+
- `数据ID`, `编号`, applicant/creation/update time, and workflow status fields are system-generated and must not appear in `add_fields`
|
|
@@ -28,16 +28,18 @@ Canonical rules before any example:
|
|
|
28
28
|
- Always use `columns`
|
|
29
29
|
- Do not emit `column_names`
|
|
30
30
|
- Treat `fields` only as a legacy alias the MCP may normalize, not as the preferred shape
|
|
31
|
-
- Use `filters` with
|
|
31
|
+
- Use `filters` with the unified fixed-filter DSL: `field_name`, `operator`, `value`/`values`; do not write raw `judgeType` / `judgeValues`
|
|
32
32
|
- Use `query_conditions` for the frontend query panel. Do not put query-panel fields into `filters`.
|
|
33
|
+
- Do not create views named `全部数据`, `我的数据`, `我发起的`, `待办`, `已办`, or `抄送`. These are built-in default/system views. New views must use business-specific names such as `订单台账视图`, `客户跟进视图`, or `逾期任务看板`.
|
|
34
|
+
- To change a built-in default/system view, first read the existing raw `view_key` from `app_get.views` or `app_get_views`, then use `patch_views` or a full `upsert_views` item with that `view_key`.
|
|
33
35
|
- New views created by `app_views_apply` default the frontend associated report/view area to visible with `limit_type="all"`. Existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched.
|
|
34
|
-
- Use `app_associated_resources_apply` for the associated report/view resource pool, selected resources, and match rules. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. `associated_item_id` is the internal associated-resource id from `app_get.associated_resources`; `view_configs`, remove, and reorder may also pass an existing resource's `chart_id`/`chart_key`/`view_key`, which the tool resolves to the internal id. Do not write backend raw `sourceType`; reports default to BI app reports, and dataset reports use `report_source="dataset"`. Use `match_mappings` for associated view/report filters; read `match-rules.md` if field type compatibility is unclear.
|
|
36
|
+
- Use `app_associated_resources_apply` for the associated report/view resource pool, selected resources, and match rules. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. `associated_item_id` is the internal associated-resource id from `app_get.associated_resources`; `view_configs`, remove, and reorder may also pass an existing resource's `chart_id`/`chart_key`/`view_key`, which the tool resolves to the internal id. Do not write backend raw `sourceType`; reports default to BI app reports, and dataset reports use `report_source="dataset"`. Use `match_mappings` with `target_field + operator + source_field/value` for associated view/report filters; read `match-rules.md` if field type compatibility is unclear.
|
|
35
37
|
- For gantt, use `start_field`, `end_field`, and optionally `title_field`
|
|
36
38
|
- If `app_get.views` or `app_get_views` shows duplicate view names, include `view_key` in `upsert_views[]` and update that exact target
|
|
37
39
|
- Builder view writes always use the raw `view_key` from `app_get.views[].view_key`, such as `emsrao25rs02`. Do not pass `custom:emsrao25rs02`; that prefixed form is only for record-data `view_id`.
|
|
38
40
|
- For an existing view, prefer `patch_views` for parameter replacement. Do not send a partial `upsert_views` object such as only `name/type/query_conditions`; backend view saves require other type-specific fields, and the patch path preserves them for you.
|
|
39
41
|
|
|
40
|
-
Apply a
|
|
42
|
+
Apply a business table view:
|
|
41
43
|
|
|
42
44
|
```json
|
|
43
45
|
{
|
|
@@ -48,7 +50,7 @@ Apply a default table view:
|
|
|
48
50
|
"publish": true,
|
|
49
51
|
"upsert_views": [
|
|
50
52
|
{
|
|
51
|
-
"name": "
|
|
53
|
+
"name": "订单台账视图",
|
|
52
54
|
"view_key": "VIEW_KEY_IF_DUPLICATE_NAMES_EXIST",
|
|
53
55
|
"type": "table",
|
|
54
56
|
"columns": ["订单编号", "客户名称", "订单金额", "状态"]
|
|
@@ -79,6 +79,7 @@ Without `record_id` / `workflow_node_id` / `fields-file`, the result is a static
|
|
|
79
79
|
16. If post-write detail context matters, read `record_get.fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs and do not expect legacy flat record shapes
|
|
80
80
|
17. Treat nested schema shape as guidance, not a brittle contract; do not hard-code transient implementation details like optional nested `field_id` shape when composing inserts
|
|
81
81
|
18. For `partial_success`, read `created_record_ids`, then repair only the failed `items[].row_number` using `failed_fields`; never retry the whole batch after any row has `write_executed=true`
|
|
82
|
+
19. Do not put Qingflow system fields in `fields`: `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, `流程标题`. They are generated by the platform and can be read after creation, not manually inserted.
|
|
82
83
|
|
|
83
84
|
## Field Notes
|
|
84
85
|
|
|
@@ -120,6 +121,7 @@ qingflow --json record insert --app-key APP_KEY --items-file records.json
|
|
|
120
121
|
|
|
121
122
|
- Do not skip `record_insert_schema_get`
|
|
122
123
|
- Do not invent missing required fields
|
|
124
|
+
- Do not fill platform system fields such as `数据ID`, `编号`, `申请人`, `创建时间`, or `更新时间`
|
|
123
125
|
- Do not flatten subtable leaf fields to the top level
|
|
124
126
|
- Do not pre-query or silently guess member / department / relation ids when a natural string is enough
|
|
125
127
|
- Do not retry a whole batch after `created_record_ids` is non-empty
|
|
@@ -1259,11 +1259,30 @@ class CustomButtonFieldMappingPatch(StrictModel):
|
|
|
1259
1259
|
|
|
1260
1260
|
|
|
1261
1261
|
class FieldMatchMappingPatch(StrictModel):
|
|
1262
|
-
target_field: Any = Field(
|
|
1262
|
+
target_field: Any = Field(
|
|
1263
|
+
validation_alias=AliasChoices("target_field", "targetField", "target", "field", "field_name", "fieldName")
|
|
1264
|
+
)
|
|
1263
1265
|
source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
|
|
1264
1266
|
value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
|
|
1265
1267
|
operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
|
|
1266
1268
|
|
|
1269
|
+
@model_validator(mode="before")
|
|
1270
|
+
@classmethod
|
|
1271
|
+
def normalize_semantic_aliases(cls, value: Any) -> Any:
|
|
1272
|
+
if not isinstance(value, dict):
|
|
1273
|
+
return value
|
|
1274
|
+
payload = dict(value)
|
|
1275
|
+
has_static_value = any(key in payload for key in ("value", "static_value", "staticValue"))
|
|
1276
|
+
if "values" in payload:
|
|
1277
|
+
values = payload.pop("values")
|
|
1278
|
+
if has_static_value:
|
|
1279
|
+
return payload
|
|
1280
|
+
if isinstance(values, list) and len(values) == 1:
|
|
1281
|
+
payload["value"] = values[0]
|
|
1282
|
+
else:
|
|
1283
|
+
payload["value"] = values
|
|
1284
|
+
return payload
|
|
1285
|
+
|
|
1267
1286
|
@model_validator(mode="after")
|
|
1268
1287
|
def validate_shape(self) -> "FieldMatchMappingPatch":
|
|
1269
1288
|
has_source = self.source_field is not None and str(self.source_field).strip() != ""
|
|
@@ -15049,14 +15049,14 @@ def _build_public_chart_filter_matrix(
|
|
|
15049
15049
|
return []
|
|
15050
15050
|
group: list[dict[str, Any]] = []
|
|
15051
15051
|
judge_map = {
|
|
15052
|
-
ViewFilterOperator.eq.value:
|
|
15053
|
-
ViewFilterOperator.neq.value:
|
|
15054
|
-
ViewFilterOperator.gte.value:
|
|
15055
|
-
ViewFilterOperator.lte.value:
|
|
15056
|
-
ViewFilterOperator.in_.value:
|
|
15057
|
-
ViewFilterOperator.contains.value:
|
|
15058
|
-
ViewFilterOperator.is_empty.value:
|
|
15059
|
-
ViewFilterOperator.not_empty.value:
|
|
15052
|
+
ViewFilterOperator.eq.value: "equal",
|
|
15053
|
+
ViewFilterOperator.neq.value: "unequal",
|
|
15054
|
+
ViewFilterOperator.gte.value: "greaterOrEqual",
|
|
15055
|
+
ViewFilterOperator.lte.value: "lessOrEqual",
|
|
15056
|
+
ViewFilterOperator.in_.value: "anyMatch",
|
|
15057
|
+
ViewFilterOperator.contains.value: "include",
|
|
15058
|
+
ViewFilterOperator.is_empty.value: "isNull",
|
|
15059
|
+
ViewFilterOperator.not_empty.value: "notNull",
|
|
15060
15060
|
}
|
|
15061
15061
|
for rule in rules:
|
|
15062
15062
|
qingbi_field = _resolve_qingbi_chart_field(
|
|
@@ -15074,7 +15074,7 @@ def _build_public_chart_filter_matrix(
|
|
|
15074
15074
|
"fieldId": field_id,
|
|
15075
15075
|
"fieldName": qingbi_field.get("fieldName") or form_field.get("name") or field_id,
|
|
15076
15076
|
"fieldType": qingbi_field.get("fieldType") or _qingbi_field_type_from_public_field(str(form_field.get("type") or "")),
|
|
15077
|
-
"judgeType": judge_map.get(operator,
|
|
15077
|
+
"judgeType": judge_map.get(operator, "equal"),
|
|
15078
15078
|
"judgeValues": values,
|
|
15079
15079
|
"matchType": 1,
|
|
15080
15080
|
}
|
|
@@ -1481,6 +1481,22 @@ class AiBuilderTools(ToolBase):
|
|
|
1481
1481
|
remove_fields: list[JSONObject],
|
|
1482
1482
|
) -> JSONObject:
|
|
1483
1483
|
"""执行应用相关逻辑。"""
|
|
1484
|
+
system_field_failure = _reserved_system_field_name_failure(
|
|
1485
|
+
tool_name="app_schema_plan",
|
|
1486
|
+
profile=profile,
|
|
1487
|
+
app_key=app_key,
|
|
1488
|
+
package_id=package_id,
|
|
1489
|
+
app_name=app_name,
|
|
1490
|
+
icon=icon,
|
|
1491
|
+
color=color,
|
|
1492
|
+
visibility=visibility,
|
|
1493
|
+
create_if_missing=create_if_missing,
|
|
1494
|
+
add_fields=add_fields,
|
|
1495
|
+
update_fields=update_fields,
|
|
1496
|
+
remove_fields=remove_fields,
|
|
1497
|
+
)
|
|
1498
|
+
if system_field_failure is not None:
|
|
1499
|
+
return system_field_failure
|
|
1484
1500
|
try:
|
|
1485
1501
|
request = SchemaPlanRequest.model_validate(
|
|
1486
1502
|
{
|
|
@@ -1631,6 +1647,15 @@ class AiBuilderTools(ToolBase):
|
|
|
1631
1647
|
preset: str | None = None,
|
|
1632
1648
|
) -> JSONObject:
|
|
1633
1649
|
"""执行应用相关逻辑。"""
|
|
1650
|
+
reserved_failure = _reserved_system_view_name_failure(
|
|
1651
|
+
tool_name="app_views_plan",
|
|
1652
|
+
profile=profile,
|
|
1653
|
+
app_key=app_key,
|
|
1654
|
+
upsert_views=upsert_views,
|
|
1655
|
+
remove_views=remove_views,
|
|
1656
|
+
)
|
|
1657
|
+
if reserved_failure is not None:
|
|
1658
|
+
return reserved_failure
|
|
1634
1659
|
try:
|
|
1635
1660
|
request = ViewsPlanRequest.model_validate(
|
|
1636
1661
|
{
|
|
@@ -1758,6 +1783,17 @@ class AiBuilderTools(ToolBase):
|
|
|
1758
1783
|
}
|
|
1759
1784
|
if visibility is not None:
|
|
1760
1785
|
normalized_args["visibility"] = deepcopy(visibility)
|
|
1786
|
+
system_field_failure = _reserved_system_field_name_failure_for_apps(
|
|
1787
|
+
tool_name="app_schema_apply",
|
|
1788
|
+
profile=profile,
|
|
1789
|
+
package_id=package_id,
|
|
1790
|
+
visibility=visibility,
|
|
1791
|
+
create_if_missing=create_if_missing,
|
|
1792
|
+
publish=publish,
|
|
1793
|
+
apps=apps,
|
|
1794
|
+
)
|
|
1795
|
+
if system_field_failure is not None:
|
|
1796
|
+
return system_field_failure
|
|
1761
1797
|
if package_id is None:
|
|
1762
1798
|
return _config_failure(
|
|
1763
1799
|
tool_name="app_schema_apply",
|
|
@@ -2049,6 +2085,23 @@ class AiBuilderTools(ToolBase):
|
|
|
2049
2085
|
) -> JSONObject:
|
|
2050
2086
|
"""执行内部辅助逻辑。"""
|
|
2051
2087
|
effective_app_name = app_name or app_title
|
|
2088
|
+
system_field_failure = _reserved_system_field_name_failure(
|
|
2089
|
+
tool_name="app_schema_apply",
|
|
2090
|
+
profile=profile,
|
|
2091
|
+
app_key=app_key,
|
|
2092
|
+
package_id=package_id,
|
|
2093
|
+
app_name=effective_app_name,
|
|
2094
|
+
icon=icon,
|
|
2095
|
+
color=color,
|
|
2096
|
+
visibility=visibility,
|
|
2097
|
+
create_if_missing=create_if_missing,
|
|
2098
|
+
publish=publish,
|
|
2099
|
+
add_fields=add_fields,
|
|
2100
|
+
update_fields=update_fields,
|
|
2101
|
+
remove_fields=remove_fields,
|
|
2102
|
+
)
|
|
2103
|
+
if system_field_failure is not None:
|
|
2104
|
+
return system_field_failure
|
|
2052
2105
|
icon_failure = _validate_workspace_icon_for_builder(
|
|
2053
2106
|
tool_name="app_schema_apply",
|
|
2054
2107
|
icon=icon,
|
|
@@ -2380,6 +2433,17 @@ class AiBuilderTools(ToolBase):
|
|
|
2380
2433
|
remove_views: list[str],
|
|
2381
2434
|
) -> JSONObject:
|
|
2382
2435
|
"""执行内部辅助逻辑。"""
|
|
2436
|
+
reserved_failure = _reserved_system_view_name_failure(
|
|
2437
|
+
tool_name="app_views_apply",
|
|
2438
|
+
profile=profile,
|
|
2439
|
+
app_key=app_key,
|
|
2440
|
+
publish=publish,
|
|
2441
|
+
upsert_views=upsert_views,
|
|
2442
|
+
patch_views=patch_views,
|
|
2443
|
+
remove_views=remove_views,
|
|
2444
|
+
)
|
|
2445
|
+
if reserved_failure is not None:
|
|
2446
|
+
return reserved_failure
|
|
2383
2447
|
if patch_views:
|
|
2384
2448
|
try:
|
|
2385
2449
|
parsed_views = [ViewUpsertPatch.model_validate(item) for item in (upsert_views or [])]
|
|
@@ -2454,7 +2518,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2454
2518
|
"profile": profile,
|
|
2455
2519
|
"app_key": str(plan_args.get("app_key") or app_key),
|
|
2456
2520
|
"publish": publish,
|
|
2457
|
-
"upsert_views": plan_args.get("upsert_views") or [{"name": "
|
|
2521
|
+
"upsert_views": plan_args.get("upsert_views") or [{"name": "业务台账视图", "type": "table", "columns": ["字段A"]}],
|
|
2458
2522
|
"remove_views": plan_args.get("remove_views") or [],
|
|
2459
2523
|
},
|
|
2460
2524
|
},
|
|
@@ -3062,6 +3126,197 @@ def _visibility_validation_failure(
|
|
|
3062
3126
|
return result
|
|
3063
3127
|
|
|
3064
3128
|
|
|
3129
|
+
_RESERVED_SYSTEM_FIELD_NAMES = {
|
|
3130
|
+
"数据ID",
|
|
3131
|
+
"编号",
|
|
3132
|
+
"申请人",
|
|
3133
|
+
"申请时间",
|
|
3134
|
+
"创建人",
|
|
3135
|
+
"创建时间",
|
|
3136
|
+
"提交人",
|
|
3137
|
+
"提交时间",
|
|
3138
|
+
"更新时间",
|
|
3139
|
+
"更新人",
|
|
3140
|
+
"当前流程状态",
|
|
3141
|
+
"当前处理人",
|
|
3142
|
+
"当前处理节点",
|
|
3143
|
+
"流程标题",
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
|
|
3147
|
+
def _field_patch_name(item: object) -> str:
|
|
3148
|
+
if not isinstance(item, dict):
|
|
3149
|
+
return ""
|
|
3150
|
+
value = item.get("name")
|
|
3151
|
+
if value is None:
|
|
3152
|
+
value = item.get("title")
|
|
3153
|
+
if value is None:
|
|
3154
|
+
value = item.get("field_name")
|
|
3155
|
+
if value is None:
|
|
3156
|
+
value = item.get("fieldName")
|
|
3157
|
+
return str(value or "").strip()
|
|
3158
|
+
|
|
3159
|
+
|
|
3160
|
+
def _reserved_system_field_name_failure(
|
|
3161
|
+
*,
|
|
3162
|
+
tool_name: str,
|
|
3163
|
+
profile: str,
|
|
3164
|
+
app_key: str = "",
|
|
3165
|
+
package_id: int | None = None,
|
|
3166
|
+
app_name: str = "",
|
|
3167
|
+
icon: str | None = None,
|
|
3168
|
+
color: str | None = None,
|
|
3169
|
+
visibility: JSONObject | None = None,
|
|
3170
|
+
create_if_missing: bool = False,
|
|
3171
|
+
publish: bool | None = None,
|
|
3172
|
+
add_fields: list[JSONObject] | None = None,
|
|
3173
|
+
update_fields: list[JSONObject] | None = None,
|
|
3174
|
+
remove_fields: list[JSONObject] | None = None,
|
|
3175
|
+
app_index: int | None = None,
|
|
3176
|
+
) -> JSONObject | None:
|
|
3177
|
+
for index, item in enumerate(add_fields or []):
|
|
3178
|
+
name = _field_patch_name(item)
|
|
3179
|
+
if name not in _RESERVED_SYSTEM_FIELD_NAMES:
|
|
3180
|
+
continue
|
|
3181
|
+
suggested_add_fields = [field for field in (add_fields or []) if _field_patch_name(field) not in _RESERVED_SYSTEM_FIELD_NAMES]
|
|
3182
|
+
arguments: JSONObject = {
|
|
3183
|
+
"profile": profile,
|
|
3184
|
+
"app_key": app_key,
|
|
3185
|
+
"package_id": package_id,
|
|
3186
|
+
"app_name": app_name,
|
|
3187
|
+
"icon": icon or "",
|
|
3188
|
+
"color": color or "",
|
|
3189
|
+
"visibility": visibility,
|
|
3190
|
+
"create_if_missing": create_if_missing,
|
|
3191
|
+
"add_fields": suggested_add_fields,
|
|
3192
|
+
"update_fields": update_fields or [],
|
|
3193
|
+
"remove_fields": remove_fields or [],
|
|
3194
|
+
}
|
|
3195
|
+
if publish is not None:
|
|
3196
|
+
arguments["publish"] = publish
|
|
3197
|
+
return _config_failure(
|
|
3198
|
+
tool_name=tool_name,
|
|
3199
|
+
error_code="RESERVED_SYSTEM_FIELD_NAME",
|
|
3200
|
+
message=f"add_fields[{index}].name uses built-in system field name '{name}'",
|
|
3201
|
+
fix_hint="Do not create form fields named 数据ID, 编号, 申请人, 申请时间, 创建人, 创建时间, 提交人, 提交时间, 更新时间, 更新人, 当前流程状态, 当前处理人, 当前处理节点, or 流程标题. These fields are provided by Qingflow. Remove them from add_fields; only reference them where the tool explicitly supports system fields, such as button source_field 数据ID.",
|
|
3202
|
+
details={
|
|
3203
|
+
"reserved_field_names": sorted(_RESERVED_SYSTEM_FIELD_NAMES),
|
|
3204
|
+
"blocked_index": index,
|
|
3205
|
+
"blocked_name": name,
|
|
3206
|
+
"app_index": app_index,
|
|
3207
|
+
},
|
|
3208
|
+
suggested_next_call={"tool_name": tool_name, "arguments": arguments},
|
|
3209
|
+
)
|
|
3210
|
+
return None
|
|
3211
|
+
|
|
3212
|
+
|
|
3213
|
+
def _reserved_system_field_name_failure_for_apps(
|
|
3214
|
+
*,
|
|
3215
|
+
tool_name: str,
|
|
3216
|
+
profile: str,
|
|
3217
|
+
package_id: int | None,
|
|
3218
|
+
visibility: JSONObject | None,
|
|
3219
|
+
create_if_missing: bool,
|
|
3220
|
+
publish: bool,
|
|
3221
|
+
apps: list[JSONObject],
|
|
3222
|
+
) -> JSONObject | None:
|
|
3223
|
+
for index, item in enumerate(apps or []):
|
|
3224
|
+
if not isinstance(item, dict):
|
|
3225
|
+
continue
|
|
3226
|
+
failure = _reserved_system_field_name_failure(
|
|
3227
|
+
tool_name=tool_name,
|
|
3228
|
+
profile=profile,
|
|
3229
|
+
app_key=str(item.get("app_key") or item.get("appKey") or ""),
|
|
3230
|
+
package_id=package_id,
|
|
3231
|
+
app_name=str(item.get("app_name") or item.get("appName") or ""),
|
|
3232
|
+
icon=str(item.get("icon") or ""),
|
|
3233
|
+
color=str(item.get("color") or ""),
|
|
3234
|
+
visibility=item.get("visibility") if isinstance(item.get("visibility"), dict) else visibility,
|
|
3235
|
+
create_if_missing=create_if_missing,
|
|
3236
|
+
publish=publish,
|
|
3237
|
+
add_fields=list(item.get("add_fields") or item.get("addFields") or []),
|
|
3238
|
+
update_fields=list(item.get("update_fields") or item.get("updateFields") or []),
|
|
3239
|
+
remove_fields=list(item.get("remove_fields") or item.get("removeFields") or []),
|
|
3240
|
+
app_index=index,
|
|
3241
|
+
)
|
|
3242
|
+
if failure is not None:
|
|
3243
|
+
suggested = deepcopy(failure.get("suggested_next_call") or {})
|
|
3244
|
+
arguments = suggested.get("arguments")
|
|
3245
|
+
if isinstance(arguments, dict):
|
|
3246
|
+
fixed_apps = deepcopy(apps)
|
|
3247
|
+
if isinstance(fixed_apps[index], dict):
|
|
3248
|
+
fixed_apps[index]["add_fields"] = arguments.get("add_fields") or []
|
|
3249
|
+
arguments = {
|
|
3250
|
+
"profile": profile,
|
|
3251
|
+
"package_id": package_id,
|
|
3252
|
+
"visibility": visibility,
|
|
3253
|
+
"create_if_missing": create_if_missing,
|
|
3254
|
+
"publish": publish,
|
|
3255
|
+
"apps": fixed_apps,
|
|
3256
|
+
}
|
|
3257
|
+
failure["suggested_next_call"] = {"tool_name": tool_name, "arguments": arguments}
|
|
3258
|
+
return failure
|
|
3259
|
+
return None
|
|
3260
|
+
|
|
3261
|
+
|
|
3262
|
+
_RESERVED_SYSTEM_VIEW_NAMES = {
|
|
3263
|
+
"全部数据",
|
|
3264
|
+
"我的数据",
|
|
3265
|
+
"我发起的",
|
|
3266
|
+
"待办",
|
|
3267
|
+
"已办",
|
|
3268
|
+
"抄送",
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
|
|
3272
|
+
def _reserved_system_view_name_failure(
|
|
3273
|
+
*,
|
|
3274
|
+
tool_name: str,
|
|
3275
|
+
profile: str,
|
|
3276
|
+
app_key: str,
|
|
3277
|
+
publish: bool | None = None,
|
|
3278
|
+
upsert_views: list[JSONObject] | None = None,
|
|
3279
|
+
patch_views: list[JSONObject] | None = None,
|
|
3280
|
+
remove_views: list[str] | None = None,
|
|
3281
|
+
) -> JSONObject | None:
|
|
3282
|
+
for index, item in enumerate(upsert_views or []):
|
|
3283
|
+
if not isinstance(item, dict):
|
|
3284
|
+
continue
|
|
3285
|
+
name = str(item.get("name") or "").strip()
|
|
3286
|
+
view_key = str(item.get("view_key") or item.get("viewKey") or "").strip()
|
|
3287
|
+
if name in _RESERVED_SYSTEM_VIEW_NAMES and not view_key:
|
|
3288
|
+
suggested: JSONObject = {
|
|
3289
|
+
"tool_name": tool_name,
|
|
3290
|
+
"arguments": {
|
|
3291
|
+
"profile": profile,
|
|
3292
|
+
"app_key": app_key,
|
|
3293
|
+
"upsert_views": [
|
|
3294
|
+
{
|
|
3295
|
+
**item,
|
|
3296
|
+
"name": "业务台账视图",
|
|
3297
|
+
}
|
|
3298
|
+
],
|
|
3299
|
+
"patch_views": patch_views or [],
|
|
3300
|
+
"remove_views": remove_views or [],
|
|
3301
|
+
},
|
|
3302
|
+
}
|
|
3303
|
+
if publish is not None:
|
|
3304
|
+
suggested["arguments"]["publish"] = publish
|
|
3305
|
+
return _config_failure(
|
|
3306
|
+
tool_name=tool_name,
|
|
3307
|
+
error_code="RESERVED_SYSTEM_VIEW_NAME",
|
|
3308
|
+
message=f"upsert_views[{index}].name uses built-in system view name '{name}' without view_key",
|
|
3309
|
+
fix_hint="Do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送. Use a business-specific view name for new views; to modify a built-in view, pass its existing raw view_key or use patch_views.",
|
|
3310
|
+
details={
|
|
3311
|
+
"reserved_view_names": sorted(_RESERVED_SYSTEM_VIEW_NAMES),
|
|
3312
|
+
"blocked_index": index,
|
|
3313
|
+
"blocked_name": name,
|
|
3314
|
+
},
|
|
3315
|
+
suggested_next_call=suggested,
|
|
3316
|
+
)
|
|
3317
|
+
return None
|
|
3318
|
+
|
|
3319
|
+
|
|
3065
3320
|
def _config_failure(
|
|
3066
3321
|
*,
|
|
3067
3322
|
tool_name: str,
|
|
@@ -3070,6 +3325,7 @@ def _config_failure(
|
|
|
3070
3325
|
error_code: str = "CONFIG_ERROR",
|
|
3071
3326
|
details: JSONObject | None = None,
|
|
3072
3327
|
allowed_values: JSONObject | None = None,
|
|
3328
|
+
suggested_next_call: JSONObject | None = None,
|
|
3073
3329
|
) -> JSONObject:
|
|
3074
3330
|
contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
|
|
3075
3331
|
public_allowed_values = deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {}
|
|
@@ -3090,7 +3346,7 @@ def _config_failure(
|
|
|
3090
3346
|
"missing_fields": [],
|
|
3091
3347
|
"allowed_values": public_allowed_values,
|
|
3092
3348
|
"details": public_details,
|
|
3093
|
-
"suggested_next_call":
|
|
3349
|
+
"suggested_next_call": suggested_next_call,
|
|
3094
3350
|
"request_id": None,
|
|
3095
3351
|
"backend_code": None,
|
|
3096
3352
|
"http_status": None,
|
|
@@ -4647,6 +4903,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
4647
4903
|
"create mode may set visibility for the new app; edit mode may update visibility on an existing app",
|
|
4648
4904
|
"create mode should include explicit non-template icon + color; apply mode enforces this before writing",
|
|
4649
4905
|
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
4906
|
+
"do not add form fields named 数据ID, 编号, 申请人, 申请时间, 创建人, 创建时间, 提交人, 提交时间, 更新时间, 更新人, 当前流程状态, 当前处理人, 当前处理节点, or 流程标题; these are Qingflow built-in system fields, not fields to create",
|
|
4650
4907
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
4651
4908
|
],
|
|
4652
4909
|
"minimal_example": {
|
|
@@ -4777,6 +5034,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
4777
5034
|
"single_select and multi_select options accept strings or objects such as {label,value}; builder normalizes them to option labels before writing",
|
|
4778
5035
|
"edit mode preserves existing icon/color when omitted; explicit icon/color values are still validated",
|
|
4779
5036
|
"call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
|
|
5037
|
+
"do not add form fields named 数据ID, 编号, 申请人, 申请时间, 创建人, 创建时间, 提交人, 提交时间, 更新时间, 更新人, 当前流程状态, 当前处理人, 当前处理节点, or 流程标题; these are Qingflow built-in system fields, not fields to create",
|
|
4780
5038
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
4781
5039
|
"update_fields is the field-level partial update path; it reads current form schema and preserves untouched field config",
|
|
4782
5040
|
"multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
|
|
@@ -5176,6 +5434,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5176
5434
|
"use patch_views for partial parameter replacement on existing views; the tool reads current config, merges patch_views[].set/unset, then submits the backend full-save payload internally",
|
|
5177
5435
|
"remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
|
|
5178
5436
|
"deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
|
|
5437
|
+
"do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送; these are built-in system/default views. Use business-specific names for new views, and pass the existing raw view_key or patch_views when changing a built-in view",
|
|
5179
5438
|
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_views explicitly changes associated_resources",
|
|
5180
5439
|
"associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply only keeps legacy associated_resources input compatible",
|
|
5181
5440
|
"for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
|
|
@@ -5184,7 +5443,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5184
5443
|
"minimal_example": {
|
|
5185
5444
|
"profile": "default",
|
|
5186
5445
|
"app_key": "APP_KEY",
|
|
5187
|
-
"upsert_views": [{"name": "
|
|
5446
|
+
"upsert_views": [{"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
5188
5447
|
"remove_views": [],
|
|
5189
5448
|
},
|
|
5190
5449
|
"query_conditions_example": {
|
|
@@ -5305,6 +5564,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5305
5564
|
"use patch_views for partial parameter replacement on existing views; the public update mode is patch even though the backend save is still a full view payload",
|
|
5306
5565
|
"remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
|
|
5307
5566
|
"deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
|
|
5567
|
+
"do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送; these are built-in system/default views. Use business-specific names for new views, and pass the existing raw view_key or patch_views when changing a built-in view",
|
|
5308
5568
|
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_views explicitly changes associated_resources",
|
|
5309
5569
|
"associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply keeps legacy associated_resources input compatible but it is no longer the recommended public contract",
|
|
5310
5570
|
"for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
|
|
@@ -5314,7 +5574,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5314
5574
|
"profile": "default",
|
|
5315
5575
|
"app_key": "APP_KEY",
|
|
5316
5576
|
"publish": True,
|
|
5317
|
-
"upsert_views": [{"name": "
|
|
5577
|
+
"upsert_views": [{"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
5318
5578
|
"remove_views": [],
|
|
5319
5579
|
},
|
|
5320
5580
|
"query_conditions_example": {
|
|
@@ -75,6 +75,22 @@ MAX_SUMMARY_PREVIEW_COLUMN_LIMIT = 6
|
|
|
75
75
|
VERIFY_TASK_FALLBACK_PAGE_SIZE = 50
|
|
76
76
|
VERIFY_TASK_FALLBACK_MAX_PAGES = 3
|
|
77
77
|
BACKEND_LIST_SEARCH_FIELD_LIMIT = 10
|
|
78
|
+
RECORD_WRITE_SYSTEM_FIELD_NAMES = {
|
|
79
|
+
"数据ID",
|
|
80
|
+
"编号",
|
|
81
|
+
"申请人",
|
|
82
|
+
"申请时间",
|
|
83
|
+
"创建人",
|
|
84
|
+
"创建时间",
|
|
85
|
+
"提交人",
|
|
86
|
+
"提交时间",
|
|
87
|
+
"更新时间",
|
|
88
|
+
"更新人",
|
|
89
|
+
"当前流程状态",
|
|
90
|
+
"当前处理人",
|
|
91
|
+
"当前处理节点",
|
|
92
|
+
"流程标题",
|
|
93
|
+
}
|
|
78
94
|
LOOKUP_RESOLUTION_MIN_SCORE = 0.92
|
|
79
95
|
LOOKUP_RESOLUTION_MIN_MARGIN = 0.08
|
|
80
96
|
LOOKUP_CONFIRMATION_CANDIDATE_LIMIT = 5
|
|
@@ -3649,6 +3665,7 @@ class RecordTools(ToolBase):
|
|
|
3649
3665
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
3650
3666
|
if items is not None:
|
|
3651
3667
|
normalized_items = self._normalize_public_record_insert_batch_items(fields=fields, items=items)
|
|
3668
|
+
self._reject_record_insert_system_fields([cast(JSONObject, item["fields"]) for item in normalized_items])
|
|
3652
3669
|
return self._record_insert_public_batch(
|
|
3653
3670
|
profile=profile,
|
|
3654
3671
|
app_key=app_key,
|
|
@@ -3658,15 +3675,35 @@ class RecordTools(ToolBase):
|
|
|
3658
3675
|
)
|
|
3659
3676
|
if fields is not None and not isinstance(fields, dict):
|
|
3660
3677
|
raise_tool_error(QingflowApiError.config_error("fields must be an object map keyed by field title"))
|
|
3678
|
+
normalized_fields = cast(JSONObject, fields or {})
|
|
3679
|
+
self._reject_record_insert_system_fields([normalized_fields])
|
|
3661
3680
|
return self._record_insert_public_single(
|
|
3662
3681
|
profile=profile,
|
|
3663
3682
|
app_key=app_key,
|
|
3664
|
-
fields=
|
|
3683
|
+
fields=normalized_fields,
|
|
3665
3684
|
verify_write=verify_write,
|
|
3666
3685
|
output_profile=normalized_output_profile,
|
|
3667
3686
|
capture_exceptions=False,
|
|
3668
3687
|
)
|
|
3669
3688
|
|
|
3689
|
+
def _reject_record_insert_system_fields(self, field_maps: list[JSONObject]) -> None:
|
|
3690
|
+
for row_index, field_map in enumerate(field_maps):
|
|
3691
|
+
for field_name in field_map:
|
|
3692
|
+
normalized_name = str(field_name or "").strip()
|
|
3693
|
+
if normalized_name in RECORD_WRITE_SYSTEM_FIELD_NAMES:
|
|
3694
|
+
raise_tool_error(
|
|
3695
|
+
QingflowApiError.config_error(
|
|
3696
|
+
f"record_insert fields must not include built-in system field '{normalized_name}'",
|
|
3697
|
+
details={
|
|
3698
|
+
"error_code": "RESERVED_SYSTEM_FIELD_NAME",
|
|
3699
|
+
"row_number": row_index + 1,
|
|
3700
|
+
"field_name": normalized_name,
|
|
3701
|
+
"reserved_field_names": sorted(RECORD_WRITE_SYSTEM_FIELD_NAMES),
|
|
3702
|
+
"fix_hint": "Remove Qingflow built-in system fields from record_insert payload. They are generated by the platform and can be read after creation, not manually inserted.",
|
|
3703
|
+
},
|
|
3704
|
+
)
|
|
3705
|
+
)
|
|
3706
|
+
|
|
3670
3707
|
def _record_insert_public_single(
|
|
3671
3708
|
self,
|
|
3672
3709
|
*,
|