@josephyan/qingflow-app-builder-mcp 1.1.16 → 1.1.18
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 +56 -11
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +54 -5
- package/skills/qingflow-app-builder/references/create-app.md +10 -1
- package/skills/qingflow-app-builder/references/gotchas.md +17 -3
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +33 -4
- package/skills/qingflow-app-builder/references/tool-selection.md +20 -4
- package/skills/qingflow-app-builder/references/update-schema.md +11 -0
- package/skills/qingflow-app-builder/references/update-views.md +49 -0
- package/src/qingflow_mcp/builder_facade/models.py +202 -2
- package/src/qingflow_mcp/builder_facade/service.py +622 -29
- package/src/qingflow_mcp/cli/commands/builder.py +6 -3
- package/src/qingflow_mcp/server_app_builder.py +6 -3
- package/src/qingflow_mcp/tools/ai_builder_tools.py +548 -30
|
@@ -49,6 +49,8 @@ from ..builder_facade.models import (
|
|
|
49
49
|
ViewUpsertPatch,
|
|
50
50
|
ViewsPreset,
|
|
51
51
|
ViewsPlanRequest,
|
|
52
|
+
public_view_partial_payload,
|
|
53
|
+
public_view_upsert_payload,
|
|
52
54
|
)
|
|
53
55
|
from ..builder_facade.service import AiBuilderFacade, INTEGRATION_OUTPUT_TARGET_FIELD_TYPES
|
|
54
56
|
from ..solution.compiler.icon_utils import (
|
|
@@ -575,17 +577,18 @@ class AiBuilderTools(ToolBase):
|
|
|
575
577
|
visibility: JSONObject | None = None,
|
|
576
578
|
create_if_missing: bool | None = None,
|
|
577
579
|
publish: bool = True,
|
|
580
|
+
form: list[JSONObject] | None = None,
|
|
578
581
|
add_fields: list[JSONObject] | None = None,
|
|
579
582
|
update_fields: list[JSONObject] | None = None,
|
|
580
583
|
remove_fields: list[JSONObject] | None = None,
|
|
581
584
|
apps: list[JSONObject] | None = None,
|
|
582
585
|
) -> JSONObject:
|
|
583
586
|
if apps is not None:
|
|
584
|
-
if app_key or app_name or app_title or add_fields or update_fields or remove_fields:
|
|
587
|
+
if app_key or app_name or app_title or form or add_fields or update_fields or remove_fields:
|
|
585
588
|
return _config_failure(
|
|
586
589
|
tool_name="app_schema_apply",
|
|
587
590
|
message="app_schema_apply multi-app mode accepts package_id plus apps only; create_if_missing is optional.",
|
|
588
|
-
fix_hint="
|
|
591
|
+
fix_hint="Put per-app form in apps[].form when using batch mode.",
|
|
589
592
|
)
|
|
590
593
|
if package_id is None:
|
|
591
594
|
return _config_failure(
|
|
@@ -600,6 +603,7 @@ class AiBuilderTools(ToolBase):
|
|
|
600
603
|
create_if_missing=True if create_if_missing is None else bool(create_if_missing),
|
|
601
604
|
publish=publish,
|
|
602
605
|
apps=apps,
|
|
606
|
+
form=None,
|
|
603
607
|
add_fields=[],
|
|
604
608
|
update_fields=[],
|
|
605
609
|
remove_fields=[],
|
|
@@ -636,10 +640,11 @@ class AiBuilderTools(ToolBase):
|
|
|
636
640
|
visibility=visibility,
|
|
637
641
|
create_if_missing=effective_create_if_missing,
|
|
638
642
|
publish=publish,
|
|
643
|
+
form=form or [],
|
|
639
644
|
add_fields=add_fields or [],
|
|
640
645
|
update_fields=update_fields or [],
|
|
641
646
|
remove_fields=remove_fields or [],
|
|
642
|
-
apps=
|
|
647
|
+
apps=None,
|
|
643
648
|
)
|
|
644
649
|
|
|
645
650
|
@mcp.tool()
|
|
@@ -2018,6 +2023,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2018
2023
|
profile: str,
|
|
2019
2024
|
app_key: str,
|
|
2020
2025
|
upsert_views: list[JSONObject] | None = None,
|
|
2026
|
+
patch_views: list[JSONObject] | None = None,
|
|
2021
2027
|
remove_views: list[str] | None = None,
|
|
2022
2028
|
preset: str | None = None,
|
|
2023
2029
|
) -> JSONObject:
|
|
@@ -2036,6 +2042,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2036
2042
|
{
|
|
2037
2043
|
"app_key": app_key,
|
|
2038
2044
|
"upsert_views": upsert_views or [],
|
|
2045
|
+
"patch_views": patch_views or [],
|
|
2039
2046
|
"remove_views": remove_views or [],
|
|
2040
2047
|
"preset": preset,
|
|
2041
2048
|
}
|
|
@@ -2052,6 +2059,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2052
2059
|
"app_key": app_key,
|
|
2053
2060
|
"preset": "default_table",
|
|
2054
2061
|
"upsert_views": [],
|
|
2062
|
+
"patch_views": [],
|
|
2055
2063
|
"remove_views": [],
|
|
2056
2064
|
},
|
|
2057
2065
|
},
|
|
@@ -2059,8 +2067,24 @@ class AiBuilderTools(ToolBase):
|
|
|
2059
2067
|
return _safe_tool_call(
|
|
2060
2068
|
lambda: self._facade.app_views_plan(profile=profile, request=request),
|
|
2061
2069
|
error_code="VIEWS_PLAN_FAILED",
|
|
2062
|
-
normalized_args=
|
|
2063
|
-
|
|
2070
|
+
normalized_args={
|
|
2071
|
+
"app_key": request.app_key,
|
|
2072
|
+
"upsert_views": [public_view_upsert_payload(view) for view in request.upsert_views],
|
|
2073
|
+
"patch_views": [public_view_partial_payload(patch) for patch in request.patch_views],
|
|
2074
|
+
"remove_views": list(request.remove_views),
|
|
2075
|
+
"preset": request.preset.value if request.preset is not None else None,
|
|
2076
|
+
},
|
|
2077
|
+
suggested_next_call={
|
|
2078
|
+
"tool_name": "app_views_plan",
|
|
2079
|
+
"arguments": {
|
|
2080
|
+
"profile": profile,
|
|
2081
|
+
"app_key": request.app_key,
|
|
2082
|
+
"upsert_views": [public_view_upsert_payload(view) for view in request.upsert_views],
|
|
2083
|
+
"patch_views": [public_view_partial_payload(patch) for patch in request.patch_views],
|
|
2084
|
+
"remove_views": list(request.remove_views),
|
|
2085
|
+
"preset": request.preset.value if request.preset is not None else None,
|
|
2086
|
+
},
|
|
2087
|
+
},
|
|
2064
2088
|
)
|
|
2065
2089
|
|
|
2066
2090
|
@tool_cn_name("应用结构应用")
|
|
@@ -2080,6 +2104,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2080
2104
|
visibility: JSONObject | None = None,
|
|
2081
2105
|
create_if_missing: bool | None = None,
|
|
2082
2106
|
publish: bool = True,
|
|
2107
|
+
form: list[JSONObject] | None = None,
|
|
2083
2108
|
add_fields: list[JSONObject],
|
|
2084
2109
|
update_fields: list[JSONObject],
|
|
2085
2110
|
remove_fields: list[JSONObject],
|
|
@@ -2105,6 +2130,21 @@ class AiBuilderTools(ToolBase):
|
|
|
2105
2130
|
package_id = normalized_apps_payload.get("package_id") # type: ignore[assignment]
|
|
2106
2131
|
apps = normalized_apps_payload.get("apps") # type: ignore[assignment]
|
|
2107
2132
|
input_warnings = list(normalized_apps_payload.get("warnings") or [])
|
|
2133
|
+
normalized_apps: list[JSONObject] = []
|
|
2134
|
+
for index, app_item in enumerate(apps or []):
|
|
2135
|
+
if not isinstance(app_item, dict):
|
|
2136
|
+
normalized_apps.append(app_item)
|
|
2137
|
+
continue
|
|
2138
|
+
form_result = _apply_schema_form_to_payload(
|
|
2139
|
+
tool_name="app_schema_apply",
|
|
2140
|
+
payload=app_item,
|
|
2141
|
+
path=f"apps[{index}]",
|
|
2142
|
+
)
|
|
2143
|
+
failure = form_result.get("failure")
|
|
2144
|
+
if isinstance(failure, dict):
|
|
2145
|
+
return _attach_builder_apply_envelope("app_schema_apply", failure)
|
|
2146
|
+
normalized_apps.append(form_result.get("payload") or app_item)
|
|
2147
|
+
apps = normalized_apps
|
|
2108
2148
|
result = self._app_schema_apply_multi(
|
|
2109
2149
|
profile=profile,
|
|
2110
2150
|
package_id=package_id,
|
|
@@ -2118,6 +2158,22 @@ class AiBuilderTools(ToolBase):
|
|
|
2118
2158
|
result_warnings.extend(deepcopy(input_warnings))
|
|
2119
2159
|
result["warnings"] = result_warnings
|
|
2120
2160
|
return _attach_builder_apply_envelope("app_schema_apply", result)
|
|
2161
|
+
inline_form_layout_sections: list[JSONObject] = []
|
|
2162
|
+
if form:
|
|
2163
|
+
form_payload: JSONObject = {"form": form}
|
|
2164
|
+
if add_fields:
|
|
2165
|
+
form_payload["add_fields"] = add_fields
|
|
2166
|
+
form_result = _apply_schema_form_to_payload(
|
|
2167
|
+
tool_name="app_schema_apply",
|
|
2168
|
+
payload=form_payload,
|
|
2169
|
+
path="schema",
|
|
2170
|
+
)
|
|
2171
|
+
failure = form_result.get("failure")
|
|
2172
|
+
if isinstance(failure, dict):
|
|
2173
|
+
return _attach_builder_apply_envelope("app_schema_apply", failure)
|
|
2174
|
+
normalized_payload = form_result.get("payload") if isinstance(form_result.get("payload"), dict) else {}
|
|
2175
|
+
add_fields = list(normalized_payload.get("add_fields") or [])
|
|
2176
|
+
inline_form_layout_sections = list(form_result.get("layout_sections") or [])
|
|
2121
2177
|
result = self._app_schema_apply_once(
|
|
2122
2178
|
profile=profile,
|
|
2123
2179
|
app_key=app_key,
|
|
@@ -2132,6 +2188,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2132
2188
|
add_fields=add_fields,
|
|
2133
2189
|
update_fields=update_fields,
|
|
2134
2190
|
remove_fields=remove_fields,
|
|
2191
|
+
inline_form_layout_sections=inline_form_layout_sections,
|
|
2135
2192
|
)
|
|
2136
2193
|
result = self._retry_after_self_lock_release(
|
|
2137
2194
|
profile=profile,
|
|
@@ -2150,6 +2207,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2150
2207
|
add_fields=add_fields,
|
|
2151
2208
|
update_fields=update_fields,
|
|
2152
2209
|
remove_fields=remove_fields,
|
|
2210
|
+
inline_form_layout_sections=inline_form_layout_sections,
|
|
2153
2211
|
),
|
|
2154
2212
|
)
|
|
2155
2213
|
return _attach_builder_apply_envelope("app_schema_apply", result)
|
|
@@ -2408,6 +2466,19 @@ class AiBuilderTools(ToolBase):
|
|
|
2408
2466
|
remove_fields = list(compiled_item.get("remove_fields") or [])
|
|
2409
2467
|
if bool(existing.get("created")) and not deferred_add_fields and not update_fields and not remove_fields:
|
|
2410
2468
|
shell_result = existing.get("shell_result") if isinstance(existing.get("shell_result"), dict) else {}
|
|
2469
|
+
layout_sections = list(compiled_item.get("_inline_form_layout_sections") or [])
|
|
2470
|
+
if layout_sections:
|
|
2471
|
+
shell_result = self._apply_inline_form_layout_after_schema(
|
|
2472
|
+
profile=profile,
|
|
2473
|
+
schema_result=shell_result,
|
|
2474
|
+
fallback_app_key=app_key,
|
|
2475
|
+
publish=publish,
|
|
2476
|
+
sections=layout_sections,
|
|
2477
|
+
)
|
|
2478
|
+
if _schema_apply_result_has_write(shell_result):
|
|
2479
|
+
any_write_executed = True
|
|
2480
|
+
if _schema_apply_result_may_have_write(shell_result):
|
|
2481
|
+
any_write_may_have_succeeded = True
|
|
2411
2482
|
item_status = shell_result.get("status") if shell_result.get("status") in {"success", "partial_success"} else "failed"
|
|
2412
2483
|
shell_field_diff = existing.get("shell_field_diff") if isinstance(existing.get("shell_field_diff"), dict) else {}
|
|
2413
2484
|
shell_field_diff_details = existing.get("shell_field_diff_details") if isinstance(existing.get("shell_field_diff_details"), dict) else {}
|
|
@@ -2428,6 +2499,8 @@ class AiBuilderTools(ToolBase):
|
|
|
2428
2499
|
"safe_to_retry": False,
|
|
2429
2500
|
**({"next_action": shell_result.get("next_action")} if shell_result.get("next_action") else {}),
|
|
2430
2501
|
**({"verification": shell_result.get("verification")} if isinstance(shell_result.get("verification"), dict) else {}),
|
|
2502
|
+
**({"inline_form_layout": shell_result.get("inline_form_layout")} if isinstance(shell_result.get("inline_form_layout"), dict) else {}),
|
|
2503
|
+
**({"inline_form_layout_result": shell_result.get("inline_form_layout_result")} if isinstance(shell_result.get("inline_form_layout_result"), dict) else {}),
|
|
2431
2504
|
})
|
|
2432
2505
|
continue
|
|
2433
2506
|
|
|
@@ -2447,6 +2520,15 @@ class AiBuilderTools(ToolBase):
|
|
|
2447
2520
|
remove_fields=remove_fields,
|
|
2448
2521
|
)
|
|
2449
2522
|
public_result = _publicize_package_fields(field_result)
|
|
2523
|
+
layout_sections = list(compiled_item.get("_inline_form_layout_sections") or [])
|
|
2524
|
+
if layout_sections:
|
|
2525
|
+
public_result = self._apply_inline_form_layout_after_schema(
|
|
2526
|
+
profile=profile,
|
|
2527
|
+
schema_result=public_result,
|
|
2528
|
+
fallback_app_key=app_key,
|
|
2529
|
+
publish=publish,
|
|
2530
|
+
sections=layout_sections,
|
|
2531
|
+
)
|
|
2450
2532
|
if _schema_apply_result_has_write(public_result):
|
|
2451
2533
|
any_write_executed = True
|
|
2452
2534
|
if _schema_apply_result_may_have_write(public_result):
|
|
@@ -2478,6 +2560,8 @@ class AiBuilderTools(ToolBase):
|
|
|
2478
2560
|
"safe_to_retry": False,
|
|
2479
2561
|
**({"next_action": public_result.get("next_action")} if public_result.get("next_action") else {}),
|
|
2480
2562
|
**({"verification": public_result.get("verification")} if isinstance(public_result.get("verification"), dict) else {}),
|
|
2563
|
+
**({"inline_form_layout": public_result.get("inline_form_layout")} if isinstance(public_result.get("inline_form_layout"), dict) else {}),
|
|
2564
|
+
**({"inline_form_layout_result": public_result.get("inline_form_layout_result")} if isinstance(public_result.get("inline_form_layout_result"), dict) else {}),
|
|
2481
2565
|
})
|
|
2482
2566
|
|
|
2483
2567
|
pending_readback = sum(1 for item in final_items if item.get("status") == "pending_readback")
|
|
@@ -2541,6 +2625,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2541
2625
|
add_fields: list[JSONObject],
|
|
2542
2626
|
update_fields: list[JSONObject],
|
|
2543
2627
|
remove_fields: list[JSONObject],
|
|
2628
|
+
inline_form_layout_sections: list[JSONObject] | None = None,
|
|
2544
2629
|
) -> JSONObject:
|
|
2545
2630
|
"""执行内部辅助逻辑。"""
|
|
2546
2631
|
effective_app_name = app_name or app_title
|
|
@@ -2652,7 +2737,83 @@ class AiBuilderTools(ToolBase):
|
|
|
2652
2737
|
normalized_args=normalized_args,
|
|
2653
2738
|
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
2654
2739
|
)
|
|
2655
|
-
|
|
2740
|
+
public_result = _publicize_package_fields(result)
|
|
2741
|
+
if inline_form_layout_sections:
|
|
2742
|
+
public_result = self._apply_inline_form_layout_after_schema(
|
|
2743
|
+
profile=profile,
|
|
2744
|
+
schema_result=public_result,
|
|
2745
|
+
fallback_app_key=str(plan_args.get("app_key") or app_key),
|
|
2746
|
+
publish=publish,
|
|
2747
|
+
sections=inline_form_layout_sections,
|
|
2748
|
+
)
|
|
2749
|
+
return public_result
|
|
2750
|
+
|
|
2751
|
+
def _apply_inline_form_layout_after_schema(
|
|
2752
|
+
self,
|
|
2753
|
+
*,
|
|
2754
|
+
profile: str,
|
|
2755
|
+
schema_result: JSONObject,
|
|
2756
|
+
fallback_app_key: str = "",
|
|
2757
|
+
publish: bool,
|
|
2758
|
+
sections: list[JSONObject],
|
|
2759
|
+
) -> JSONObject:
|
|
2760
|
+
result = deepcopy(schema_result)
|
|
2761
|
+
if not sections:
|
|
2762
|
+
return result
|
|
2763
|
+
app_key = str(result.get("app_key") or result.get("appKey") or fallback_app_key or "").strip()
|
|
2764
|
+
result["inline_form_layout"] = {
|
|
2765
|
+
"requested": True,
|
|
2766
|
+
"section_count": len(sections),
|
|
2767
|
+
"applied": False,
|
|
2768
|
+
}
|
|
2769
|
+
if result.get("status") not in {"success", "partial_success"}:
|
|
2770
|
+
result["inline_form_layout"]["skipped_reason"] = "schema_apply_failed"
|
|
2771
|
+
verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
|
|
2772
|
+
verification = deepcopy(verification)
|
|
2773
|
+
verification["inline_form_layout_verified"] = False
|
|
2774
|
+
result["verification"] = verification
|
|
2775
|
+
return result
|
|
2776
|
+
if not app_key:
|
|
2777
|
+
result["status"] = "partial_success"
|
|
2778
|
+
result["error_code"] = result.get("error_code") or "INLINE_FORM_LAYOUT_APP_KEY_MISSING"
|
|
2779
|
+
result["message"] = "schema applied but inline form layout could not run because app_key was not resolved"
|
|
2780
|
+
result["inline_form_layout"]["error_code"] = "INLINE_FORM_LAYOUT_APP_KEY_MISSING"
|
|
2781
|
+
verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
|
|
2782
|
+
verification = deepcopy(verification)
|
|
2783
|
+
verification["inline_form_layout_verified"] = False
|
|
2784
|
+
result["verification"] = verification
|
|
2785
|
+
result["safe_to_retry"] = False
|
|
2786
|
+
return result
|
|
2787
|
+
layout_result = self._app_layout_apply_once(
|
|
2788
|
+
profile=profile,
|
|
2789
|
+
app_key=app_key,
|
|
2790
|
+
mode="merge",
|
|
2791
|
+
publish=publish,
|
|
2792
|
+
sections=sections,
|
|
2793
|
+
)
|
|
2794
|
+
layout_ok = isinstance(layout_result, dict) and layout_result.get("status") in {"success", "partial_success"}
|
|
2795
|
+
result["inline_form_layout"] = {
|
|
2796
|
+
"requested": True,
|
|
2797
|
+
"section_count": len(sections),
|
|
2798
|
+
"app_key": app_key,
|
|
2799
|
+
"applied": layout_ok,
|
|
2800
|
+
"layout_status": layout_result.get("status") if isinstance(layout_result, dict) else "failed",
|
|
2801
|
+
**({"error_code": layout_result.get("error_code")} if isinstance(layout_result, dict) and layout_result.get("error_code") else {}),
|
|
2802
|
+
**({"message": layout_result.get("message")} if isinstance(layout_result, dict) and layout_result.get("message") else {}),
|
|
2803
|
+
}
|
|
2804
|
+
result["inline_form_layout_result"] = layout_result
|
|
2805
|
+
verification = result.get("verification") if isinstance(result.get("verification"), dict) else {}
|
|
2806
|
+
verification = deepcopy(verification)
|
|
2807
|
+
verification["inline_form_layout_verified"] = layout_ok
|
|
2808
|
+
result["verification"] = verification
|
|
2809
|
+
if layout_ok:
|
|
2810
|
+
result["write_executed"] = True
|
|
2811
|
+
return result
|
|
2812
|
+
result["status"] = "partial_success" if result.get("status") == "success" else result.get("status", "partial_success")
|
|
2813
|
+
result["error_code"] = result.get("error_code") or "INLINE_FORM_LAYOUT_APPLY_FAILED"
|
|
2814
|
+
result["message"] = "schema applied but inline form layout failed"
|
|
2815
|
+
result["safe_to_retry"] = False
|
|
2816
|
+
return result
|
|
2656
2817
|
|
|
2657
2818
|
@tool_cn_name("应用布局应用")
|
|
2658
2819
|
def app_layout_apply(
|
|
@@ -2990,7 +3151,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2990
3151
|
)
|
|
2991
3152
|
if reserved_failure is not None:
|
|
2992
3153
|
return reserved_failure
|
|
2993
|
-
if patch_views:
|
|
3154
|
+
if patch_views or _view_payload_has_action_buttons(upsert_views=upsert_views, patch_views=patch_views):
|
|
2994
3155
|
try:
|
|
2995
3156
|
parsed_views = [ViewUpsertPatch.model_validate(item) for item in (upsert_views or [])]
|
|
2996
3157
|
parsed_patch_views = [ViewPartialPatch.model_validate(item) for item in patch_views]
|
|
@@ -3016,8 +3177,8 @@ class AiBuilderTools(ToolBase):
|
|
|
3016
3177
|
normalized_args = {
|
|
3017
3178
|
"app_key": app_key,
|
|
3018
3179
|
"publish": publish,
|
|
3019
|
-
"upsert_views": [view
|
|
3020
|
-
"patch_views": [patch
|
|
3180
|
+
"upsert_views": [public_view_upsert_payload(view) for view in parsed_views],
|
|
3181
|
+
"patch_views": [public_view_partial_payload(patch) for patch in parsed_patch_views],
|
|
3021
3182
|
"remove_views": list(remove_views or []),
|
|
3022
3183
|
}
|
|
3023
3184
|
return _safe_tool_call(
|
|
@@ -3072,7 +3233,8 @@ class AiBuilderTools(ToolBase):
|
|
|
3072
3233
|
normalized_args = {
|
|
3073
3234
|
"app_key": str(plan_args.get("app_key") or app_key),
|
|
3074
3235
|
"publish": publish,
|
|
3075
|
-
"upsert_views": [view
|
|
3236
|
+
"upsert_views": [public_view_upsert_payload(view) for view in parsed_views],
|
|
3237
|
+
"patch_views": [],
|
|
3076
3238
|
"remove_views": list(plan_args.get("remove_views") or remove_views),
|
|
3077
3239
|
}
|
|
3078
3240
|
return _safe_tool_call(
|
|
@@ -3534,7 +3696,12 @@ def _schema_apps_expected_shape() -> JSONObject:
|
|
|
3534
3696
|
"app_name": "员工花名册",
|
|
3535
3697
|
"icon": "business-personalcard",
|
|
3536
3698
|
"color": "emerald",
|
|
3537
|
-
"
|
|
3699
|
+
"form": [
|
|
3700
|
+
{
|
|
3701
|
+
"section": "基础信息",
|
|
3702
|
+
"rows": [[{"name": "员工名称", "type": "text", "data_title": True}]],
|
|
3703
|
+
}
|
|
3704
|
+
],
|
|
3538
3705
|
}
|
|
3539
3706
|
],
|
|
3540
3707
|
}
|
|
@@ -3543,7 +3710,7 @@ def _schema_apps_expected_shape() -> JSONObject:
|
|
|
3543
3710
|
def _schema_apps_expected_shape_json() -> str:
|
|
3544
3711
|
return (
|
|
3545
3712
|
'{"package_id":1001,"apps":[{"client_key":"employee","app_name":"员工花名册",'
|
|
3546
|
-
'"icon":"business-personalcard","color":"emerald","
|
|
3713
|
+
'"icon":"business-personalcard","color":"emerald","form":[{"section":"基础信息","rows":[[{"name":"员工名称","type":"text","data_title":true}]]}]}]}'
|
|
3547
3714
|
)
|
|
3548
3715
|
|
|
3549
3716
|
|
|
@@ -3628,6 +3795,163 @@ def _normalize_schema_apps_argument(*, tool_name: str, package_id: int | None, a
|
|
|
3628
3795
|
return {"package_id": normalized_package_id, "apps": normalized_items, "warnings": warnings}
|
|
3629
3796
|
|
|
3630
3797
|
|
|
3798
|
+
def _normalize_schema_form_field(field: JSONObject) -> JSONObject:
|
|
3799
|
+
payload = deepcopy(field)
|
|
3800
|
+
if "data_title" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
3801
|
+
payload["as_data_title"] = payload.pop("data_title")
|
|
3802
|
+
if "dataTitle" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
3803
|
+
payload["as_data_title"] = payload.pop("dataTitle")
|
|
3804
|
+
if "data_cover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
3805
|
+
payload["as_data_cover"] = payload.pop("data_cover")
|
|
3806
|
+
if "dataCover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
3807
|
+
payload["as_data_cover"] = payload.pop("dataCover")
|
|
3808
|
+
return payload
|
|
3809
|
+
|
|
3810
|
+
|
|
3811
|
+
def _schema_form_entry_name(entry: object) -> str:
|
|
3812
|
+
if isinstance(entry, dict):
|
|
3813
|
+
for key in ("name", "title", "label", "field_name", "fieldName"):
|
|
3814
|
+
if entry.get(key) is not None:
|
|
3815
|
+
return str(entry.get(key) or "").strip()
|
|
3816
|
+
return ""
|
|
3817
|
+
if isinstance(entry, (str, int)):
|
|
3818
|
+
return str(entry).strip()
|
|
3819
|
+
return ""
|
|
3820
|
+
|
|
3821
|
+
|
|
3822
|
+
def _schema_form_failure(*, tool_name: str, path: str, message: str, fix_hint: str) -> JSONObject:
|
|
3823
|
+
return _config_failure(
|
|
3824
|
+
tool_name=tool_name,
|
|
3825
|
+
error_code="FORM_SHAPE_INVALID",
|
|
3826
|
+
message=message,
|
|
3827
|
+
fix_hint=fix_hint,
|
|
3828
|
+
details={
|
|
3829
|
+
"path": path,
|
|
3830
|
+
"expected_shape": {
|
|
3831
|
+
"form": [
|
|
3832
|
+
{
|
|
3833
|
+
"section": "基础信息",
|
|
3834
|
+
"rows": [
|
|
3835
|
+
[
|
|
3836
|
+
{"name": "标题", "type": "text", "data_title": True},
|
|
3837
|
+
{"name": "状态", "type": "single_select", "options": ["待处理", "处理中", "已完成"]},
|
|
3838
|
+
]
|
|
3839
|
+
],
|
|
3840
|
+
}
|
|
3841
|
+
]
|
|
3842
|
+
},
|
|
3843
|
+
},
|
|
3844
|
+
)
|
|
3845
|
+
|
|
3846
|
+
|
|
3847
|
+
def _compile_schema_form_payload(*, tool_name: str, form: object, path: str = "form") -> JSONObject:
|
|
3848
|
+
if form is None:
|
|
3849
|
+
return {"add_fields": [], "layout_sections": []}
|
|
3850
|
+
if not isinstance(form, list) or not form:
|
|
3851
|
+
return {
|
|
3852
|
+
"failure": _schema_form_failure(
|
|
3853
|
+
tool_name=tool_name,
|
|
3854
|
+
path=path,
|
|
3855
|
+
message=f"{path} must be a non-empty list of form sections.",
|
|
3856
|
+
fix_hint='Use form=[{"section":"基础信息","rows":[[{"name":"标题","type":"text","data_title":true}]]}].',
|
|
3857
|
+
)
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
add_fields: list[JSONObject] = []
|
|
3861
|
+
layout_sections: list[JSONObject] = []
|
|
3862
|
+
for section_index, section in enumerate(form):
|
|
3863
|
+
section_path = f"{path}[{section_index}]"
|
|
3864
|
+
if not isinstance(section, dict):
|
|
3865
|
+
return {
|
|
3866
|
+
"failure": _schema_form_failure(
|
|
3867
|
+
tool_name=tool_name,
|
|
3868
|
+
path=section_path,
|
|
3869
|
+
message=f"{section_path} must be an object.",
|
|
3870
|
+
fix_hint="Each form section must include section and rows.",
|
|
3871
|
+
)
|
|
3872
|
+
}
|
|
3873
|
+
section_title = str(section.get("section") or section.get("title") or section.get("name") or "").strip()
|
|
3874
|
+
rows = section.get("rows")
|
|
3875
|
+
if not section_title:
|
|
3876
|
+
return {
|
|
3877
|
+
"failure": _schema_form_failure(
|
|
3878
|
+
tool_name=tool_name,
|
|
3879
|
+
path=f"{section_path}.section",
|
|
3880
|
+
message=f"{section_path}.section is required.",
|
|
3881
|
+
fix_hint='Set a business section title such as "基础信息" or "生产信息".',
|
|
3882
|
+
)
|
|
3883
|
+
}
|
|
3884
|
+
if not isinstance(rows, list) or not rows:
|
|
3885
|
+
return {
|
|
3886
|
+
"failure": _schema_form_failure(
|
|
3887
|
+
tool_name=tool_name,
|
|
3888
|
+
path=f"{section_path}.rows",
|
|
3889
|
+
message=f"{section_path}.rows must be a non-empty list.",
|
|
3890
|
+
fix_hint="Use rows as a list of field rows; each row is a list of field objects.",
|
|
3891
|
+
)
|
|
3892
|
+
}
|
|
3893
|
+
layout_rows: list[list[str]] = []
|
|
3894
|
+
for row_index, row in enumerate(rows):
|
|
3895
|
+
row_path = f"{section_path}.rows[{row_index}]"
|
|
3896
|
+
if isinstance(row, dict):
|
|
3897
|
+
row_fields = row.get("fields")
|
|
3898
|
+
else:
|
|
3899
|
+
row_fields = row
|
|
3900
|
+
if not isinstance(row_fields, list) or not row_fields:
|
|
3901
|
+
return {
|
|
3902
|
+
"failure": _schema_form_failure(
|
|
3903
|
+
tool_name=tool_name,
|
|
3904
|
+
path=row_path,
|
|
3905
|
+
message=f"{row_path} must be a non-empty field list.",
|
|
3906
|
+
fix_hint='Use rows like [[{"name":"字段A","type":"text"},{"name":"字段B","type":"number"}]].',
|
|
3907
|
+
)
|
|
3908
|
+
}
|
|
3909
|
+
layout_row: list[str] = []
|
|
3910
|
+
for field_index, field in enumerate(row_fields):
|
|
3911
|
+
field_path = f"{row_path}[{field_index}]"
|
|
3912
|
+
field_name = _schema_form_entry_name(field)
|
|
3913
|
+
if not field_name:
|
|
3914
|
+
return {
|
|
3915
|
+
"failure": _schema_form_failure(
|
|
3916
|
+
tool_name=tool_name,
|
|
3917
|
+
path=field_path,
|
|
3918
|
+
message=f"{field_path} must include a field name.",
|
|
3919
|
+
fix_hint='Each new field object must include name, for example {"name":"工单编号","type":"text"}.',
|
|
3920
|
+
)
|
|
3921
|
+
}
|
|
3922
|
+
layout_row.append(field_name)
|
|
3923
|
+
if isinstance(field, dict):
|
|
3924
|
+
add_fields.append(_normalize_schema_form_field(field))
|
|
3925
|
+
layout_rows.append(layout_row)
|
|
3926
|
+
layout_sections.append({"title": section_title, "rows": layout_rows})
|
|
3927
|
+
return {"add_fields": add_fields, "layout_sections": layout_sections}
|
|
3928
|
+
|
|
3929
|
+
|
|
3930
|
+
def _apply_schema_form_to_payload(*, tool_name: str, payload: JSONObject, path: str) -> JSONObject:
|
|
3931
|
+
form = payload.get("form")
|
|
3932
|
+
if form is None:
|
|
3933
|
+
return {"payload": payload, "layout_sections": []}
|
|
3934
|
+
existing_add_fields = payload.get("add_fields") or payload.get("addFields")
|
|
3935
|
+
if isinstance(existing_add_fields, list) and existing_add_fields:
|
|
3936
|
+
return {
|
|
3937
|
+
"failure": _config_failure(
|
|
3938
|
+
tool_name=tool_name,
|
|
3939
|
+
error_code="FORM_WITH_ADD_FIELDS_CONFLICT",
|
|
3940
|
+
message=f"{path} cannot use form and add_fields together.",
|
|
3941
|
+
fix_hint="Use form as the single schema creation shape: put fields under form[].rows[][].",
|
|
3942
|
+
details={"path": path, "conflicting_keys": ["form", "add_fields"]},
|
|
3943
|
+
)
|
|
3944
|
+
}
|
|
3945
|
+
compiled = _compile_schema_form_payload(tool_name=tool_name, form=form, path=f"{path}.form")
|
|
3946
|
+
if isinstance(compiled.get("failure"), dict):
|
|
3947
|
+
return {"failure": compiled["failure"]}
|
|
3948
|
+
normalized = deepcopy(payload)
|
|
3949
|
+
normalized.pop("form", None)
|
|
3950
|
+
normalized["add_fields"] = compiled.get("add_fields") or []
|
|
3951
|
+
normalized["_inline_form_layout_sections"] = compiled.get("layout_sections") or []
|
|
3952
|
+
return {"payload": normalized, "layout_sections": compiled.get("layout_sections") or []}
|
|
3953
|
+
|
|
3954
|
+
|
|
3631
3955
|
def _multi_app_item_app_name(item: JSONObject) -> str:
|
|
3632
3956
|
return str(item.get("app_name") or item.get("appName") or item.get("appTitle") or item.get("app_title") or item.get("title") or "").strip()
|
|
3633
3957
|
|
|
@@ -3649,7 +3973,7 @@ def _field_type_for_static_validation(field: JSONObject) -> str:
|
|
|
3649
3973
|
|
|
3650
3974
|
|
|
3651
3975
|
def _is_data_title_field(field: JSONObject) -> bool:
|
|
3652
|
-
return bool(field.get("as_data_title") or field.get("asDataTitle"))
|
|
3976
|
+
return bool(field.get("as_data_title") or field.get("asDataTitle") or field.get("data_title") or field.get("dataTitle"))
|
|
3653
3977
|
|
|
3654
3978
|
|
|
3655
3979
|
def _collect_multi_app_target_refs(value: object, *, path: str) -> list[JSONObject]:
|
|
@@ -4278,6 +4602,23 @@ def _reserved_system_view_name_failure(
|
|
|
4278
4602
|
return None
|
|
4279
4603
|
|
|
4280
4604
|
|
|
4605
|
+
def _view_payload_has_action_buttons(
|
|
4606
|
+
*,
|
|
4607
|
+
upsert_views: list[JSONObject] | None = None,
|
|
4608
|
+
patch_views: list[JSONObject] | None = None,
|
|
4609
|
+
) -> bool:
|
|
4610
|
+
for item in upsert_views or []:
|
|
4611
|
+
if isinstance(item, dict) and any(key in item for key in ("action_buttons", "actionButtons")):
|
|
4612
|
+
return True
|
|
4613
|
+
for item in patch_views or []:
|
|
4614
|
+
if not isinstance(item, dict):
|
|
4615
|
+
continue
|
|
4616
|
+
raw_set = item.get("set")
|
|
4617
|
+
if isinstance(raw_set, dict) and any(key in raw_set for key in ("action_buttons", "actionButtons")):
|
|
4618
|
+
return True
|
|
4619
|
+
return False
|
|
4620
|
+
|
|
4621
|
+
|
|
4281
4622
|
def _config_failure(
|
|
4282
4623
|
*,
|
|
4283
4624
|
tool_name: str,
|
|
@@ -4986,6 +5327,15 @@ def _builder_view_resources(payload: JSONObject) -> list[JSONObject]:
|
|
|
4986
5327
|
message=message,
|
|
4987
5328
|
)
|
|
4988
5329
|
)
|
|
5330
|
+
details = payload.get("details") if isinstance(payload.get("details"), dict) else {}
|
|
5331
|
+
action_buttons_result = details.get("action_buttons_result") if isinstance(details, dict) else None
|
|
5332
|
+
if isinstance(action_buttons_result, dict):
|
|
5333
|
+
nested_payload = {
|
|
5334
|
+
**action_buttons_result,
|
|
5335
|
+
"app_key": payload.get("app_key"),
|
|
5336
|
+
"app_name": payload.get("app_name"),
|
|
5337
|
+
}
|
|
5338
|
+
resources.extend(_builder_button_resources(nested_payload))
|
|
4989
5339
|
return resources
|
|
4990
5340
|
|
|
4991
5341
|
|
|
@@ -5956,6 +6306,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5956
6306
|
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
5957
6307
|
"field.customBtnText": "field.custom_button_text",
|
|
5958
6308
|
"field.subfieldUpdates": "field.subfield_updates",
|
|
6309
|
+
"field.data_title": "field.as_data_title",
|
|
6310
|
+
"field.dataTitle": "field.as_data_title",
|
|
6311
|
+
"field.data_cover": "field.as_data_cover",
|
|
6312
|
+
"field.dataCover": "field.as_data_cover",
|
|
5959
6313
|
"field.asDataTitle": "field.as_data_title",
|
|
5960
6314
|
"field.asDataCover": "field.as_data_cover",
|
|
5961
6315
|
},
|
|
@@ -5983,7 +6337,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
5983
6337
|
"color": "emerald",
|
|
5984
6338
|
"visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
|
|
5985
6339
|
"create_if_missing": True,
|
|
5986
|
-
"add_fields": [{"name": "项目名称", "type": "text", "
|
|
6340
|
+
"add_fields": [{"name": "项目名称", "type": "text", "data_title": True}],
|
|
5987
6341
|
"update_fields": [],
|
|
5988
6342
|
"remove_fields": [],
|
|
5989
6343
|
},
|
|
@@ -6018,6 +6372,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6018
6372
|
"visibility",
|
|
6019
6373
|
"create_if_missing",
|
|
6020
6374
|
"publish",
|
|
6375
|
+
"form",
|
|
6021
6376
|
"add_fields",
|
|
6022
6377
|
"update_fields",
|
|
6023
6378
|
"remove_fields",
|
|
@@ -6031,6 +6386,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6031
6386
|
"apps[].icon_color",
|
|
6032
6387
|
"apps[].icon_config",
|
|
6033
6388
|
"apps[].visibility",
|
|
6389
|
+
"apps[].form",
|
|
6390
|
+
"apps[].form[].section",
|
|
6391
|
+
"apps[].form[].rows",
|
|
6034
6392
|
"apps[].add_fields",
|
|
6035
6393
|
"apps[].update_fields",
|
|
6036
6394
|
"apps[].remove_fields",
|
|
@@ -6075,6 +6433,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6075
6433
|
"field.customBtnTextStatus": "field.custom_button_text_enabled",
|
|
6076
6434
|
"field.customBtnText": "field.custom_button_text",
|
|
6077
6435
|
"field.subfieldUpdates": "field.subfield_updates",
|
|
6436
|
+
"field.data_title": "field.as_data_title",
|
|
6437
|
+
"field.dataTitle": "field.as_data_title",
|
|
6438
|
+
"field.data_cover": "field.as_data_cover",
|
|
6439
|
+
"field.dataCover": "field.as_data_cover",
|
|
6078
6440
|
"field.asDataTitle": "field.as_data_title",
|
|
6079
6441
|
"field.asDataCover": "field.as_data_cover",
|
|
6080
6442
|
},
|
|
@@ -6095,6 +6457,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6095
6457
|
"create mode follows backend CreateAppBean: package add_app permission is checked on the target package; package edit_app is not required for the create precheck",
|
|
6096
6458
|
"multi-app mode: pass package_id + apps[]; create_if_missing defaults to true for app_name items and may be set false only when every item uses app_key",
|
|
6097
6459
|
"CLI --apps-file primary shape is {package_id, apps:[...]}; raw app arrays and singleton wrapper arrays are compatibility paths, not recommended examples",
|
|
6460
|
+
"create app schemas with form: form[].section names form sections; form[].rows[][] contains field objects; builder splits form into field creation plus form layout apply",
|
|
6461
|
+
"single-app CLI primary shape is --form-file; multi-app primary shape is apps[].form inside --apps-file",
|
|
6098
6462
|
"multi-app mode preflights static errors before writing: duplicate client_key/app_name, missing data title on new apps, explicit create_if_missing=false with app_name items, and unresolved target_app_ref/target_app",
|
|
6099
6463
|
"multi-app relation fields may use target_app_ref to point at another apps[].client_key; the tool creates/resolves app shells first and compiles it to target_app_key",
|
|
6100
6464
|
"multi-app relation fields may also use target_app with another apps[].app_name; prefer target_app_ref/client_key when names may collide",
|
|
@@ -6138,9 +6502,16 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6138
6502
|
"visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE),
|
|
6139
6503
|
"create_if_missing": True,
|
|
6140
6504
|
"publish": True,
|
|
6141
|
-
"
|
|
6142
|
-
{
|
|
6143
|
-
|
|
6505
|
+
"form": [
|
|
6506
|
+
{
|
|
6507
|
+
"section": "基础信息",
|
|
6508
|
+
"rows": [
|
|
6509
|
+
[
|
|
6510
|
+
{"name": "项目名称", "type": "text", "data_title": True},
|
|
6511
|
+
{"name": "项目封面", "type": "attachment", "data_cover": True},
|
|
6512
|
+
]
|
|
6513
|
+
],
|
|
6514
|
+
}
|
|
6144
6515
|
],
|
|
6145
6516
|
"update_fields": [],
|
|
6146
6517
|
"remove_fields": [],
|
|
@@ -6155,9 +6526,16 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6155
6526
|
"app_name": "员工花名册",
|
|
6156
6527
|
"icon": "business-personalcard",
|
|
6157
6528
|
"color": "emerald",
|
|
6158
|
-
"
|
|
6159
|
-
{
|
|
6160
|
-
|
|
6529
|
+
"form": [
|
|
6530
|
+
{
|
|
6531
|
+
"section": "基础信息",
|
|
6532
|
+
"rows": [
|
|
6533
|
+
[
|
|
6534
|
+
{"name": "员工名称", "type": "text", "data_title": True},
|
|
6535
|
+
{"name": "员工照片", "type": "attachment", "data_cover": True},
|
|
6536
|
+
]
|
|
6537
|
+
],
|
|
6538
|
+
}
|
|
6161
6539
|
],
|
|
6162
6540
|
},
|
|
6163
6541
|
{
|
|
@@ -6165,15 +6543,22 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6165
6543
|
"app_name": "工时表",
|
|
6166
6544
|
"icon": "clock",
|
|
6167
6545
|
"color": "blue",
|
|
6168
|
-
"
|
|
6169
|
-
{"name": "工时标题", "type": "text", "as_data_title": True},
|
|
6546
|
+
"form": [
|
|
6170
6547
|
{
|
|
6171
|
-
"
|
|
6172
|
-
"
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6548
|
+
"section": "基础信息",
|
|
6549
|
+
"rows": [
|
|
6550
|
+
[{"name": "工时标题", "type": "text", "data_title": True}],
|
|
6551
|
+
[
|
|
6552
|
+
{
|
|
6553
|
+
"name": "关联员工",
|
|
6554
|
+
"type": "relation",
|
|
6555
|
+
"target_app_ref": "employee",
|
|
6556
|
+
"display_field": {"name": "员工名称"},
|
|
6557
|
+
"visible_fields": [{"name": "员工名称"}],
|
|
6558
|
+
}
|
|
6559
|
+
],
|
|
6560
|
+
],
|
|
6561
|
+
}
|
|
6177
6562
|
],
|
|
6178
6563
|
},
|
|
6179
6564
|
],
|
|
@@ -6460,11 +6845,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6460
6845
|
"upsert_views[].columns",
|
|
6461
6846
|
"upsert_views[].filters",
|
|
6462
6847
|
"upsert_views[].buttons",
|
|
6848
|
+
"upsert_views[].action_buttons",
|
|
6849
|
+
"upsert_views[].action_buttons_mode",
|
|
6463
6850
|
"upsert_views[].visibility",
|
|
6464
6851
|
"upsert_views[].query_conditions",
|
|
6465
6852
|
"patch_views[].view_key",
|
|
6466
6853
|
"patch_views[].name",
|
|
6467
6854
|
"patch_views[].set",
|
|
6855
|
+
"patch_views[].set.action_buttons",
|
|
6856
|
+
"patch_views[].set.action_buttons_mode",
|
|
6468
6857
|
"patch_views[].unset",
|
|
6469
6858
|
],
|
|
6470
6859
|
"aliases": {
|
|
@@ -6492,6 +6881,14 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6492
6881
|
"buttons[].buttonFormula": "buttons[].button_formula",
|
|
6493
6882
|
"buttons[].buttonFormulaType": "buttons[].button_formula_type",
|
|
6494
6883
|
"buttons[].printTpls": "buttons[].print_tpls",
|
|
6884
|
+
"actionButtons": "action_buttons",
|
|
6885
|
+
"actionButtonsMode": "action_buttons_mode",
|
|
6886
|
+
"action_buttons[].button_text": "action_buttons[].text",
|
|
6887
|
+
"action_buttons[].trigger_action": "action_buttons[].action",
|
|
6888
|
+
"action_buttons[].trigger_link_url": "action_buttons[].url",
|
|
6889
|
+
"action_buttons[].visibleWhen": "action_buttons[].visible_when",
|
|
6890
|
+
"action_buttons[].fieldMappings": "action_buttons[].field_mappings",
|
|
6891
|
+
"action_buttons[].defaultValues": "action_buttons[].default_values",
|
|
6495
6892
|
},
|
|
6496
6893
|
"allowed_values": {
|
|
6497
6894
|
"preset": [member.value for member in ViewsPreset],
|
|
@@ -6499,6 +6896,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6499
6896
|
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
6500
6897
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
6501
6898
|
"view.buttons.config_type": ["TOP", "DETAIL", "INSIDE"],
|
|
6899
|
+
"view.action_buttons.action": ["add_data", "link", "qRobot", "wings"],
|
|
6900
|
+
"view.action_buttons.placement": [member.value for member in PublicButtonPlacement],
|
|
6901
|
+
"view.action_buttons_mode": ["merge", "replace"],
|
|
6502
6902
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
6503
6903
|
},
|
|
6504
6904
|
"execution_notes": [
|
|
@@ -6508,6 +6908,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6508
6908
|
"upsert_views[].query_conditions.rows is a layout matrix of query-panel supported field names; it is compiled to backend queryCondition queIds",
|
|
6509
6909
|
"do not put relation/attachment/subtable/code_block/q_linker/address fields in query_conditions; use filters for fixed filters or app_associated_resources_apply.match_mappings for current-record relation/report matching",
|
|
6510
6910
|
"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",
|
|
6911
|
+
"business buttons should be declared with action_buttons on the target view; app_views_apply writes the view first, then compiles action_buttons through the custom button writer and binds them to the resulting view_key",
|
|
6912
|
+
"action_buttons_mode defaults to merge; replace rewrites this view's custom button bindings, and replace plus action_buttons=[] clears this view's custom button bindings without deleting button bodies",
|
|
6913
|
+
"legacy upsert_views[].buttons is a compatibility path for raw full replacement of existing view button config; do not use it as the normal agent path",
|
|
6511
6914
|
"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",
|
|
6512
6915
|
"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",
|
|
6513
6916
|
"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",
|
|
@@ -6538,6 +6941,27 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6538
6941
|
],
|
|
6539
6942
|
"remove_views": [],
|
|
6540
6943
|
},
|
|
6944
|
+
"action_buttons_example": {
|
|
6945
|
+
"profile": "default",
|
|
6946
|
+
"app_key": "APP_KEY",
|
|
6947
|
+
"upsert_views": [
|
|
6948
|
+
{
|
|
6949
|
+
"name": "生产工单执行视图",
|
|
6950
|
+
"type": "table",
|
|
6951
|
+
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
6952
|
+
"action_buttons": [
|
|
6953
|
+
{
|
|
6954
|
+
"text": "开始生产",
|
|
6955
|
+
"action": "link",
|
|
6956
|
+
"url": "https://example.com/start",
|
|
6957
|
+
"placement": "list",
|
|
6958
|
+
"visible_when": [{"field_name": "状态", "operator": "eq", "value": "待排产"}],
|
|
6959
|
+
}
|
|
6960
|
+
],
|
|
6961
|
+
}
|
|
6962
|
+
],
|
|
6963
|
+
"remove_views": [],
|
|
6964
|
+
},
|
|
6541
6965
|
"full_upsert_query_conditions_example": {
|
|
6542
6966
|
"profile": "default",
|
|
6543
6967
|
"app_key": "APP_KEY",
|
|
@@ -6595,11 +7019,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6595
7019
|
"upsert_views[].columns",
|
|
6596
7020
|
"upsert_views[].filters",
|
|
6597
7021
|
"upsert_views[].buttons",
|
|
7022
|
+
"upsert_views[].action_buttons",
|
|
7023
|
+
"upsert_views[].action_buttons_mode",
|
|
6598
7024
|
"upsert_views[].visibility",
|
|
6599
7025
|
"upsert_views[].query_conditions",
|
|
6600
7026
|
"patch_views[].view_key",
|
|
6601
7027
|
"patch_views[].name",
|
|
6602
7028
|
"patch_views[].set",
|
|
7029
|
+
"patch_views[].set.action_buttons",
|
|
7030
|
+
"patch_views[].set.action_buttons_mode",
|
|
6603
7031
|
"patch_views[].unset",
|
|
6604
7032
|
],
|
|
6605
7033
|
"aliases": {
|
|
@@ -6627,12 +7055,23 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6627
7055
|
"buttons[].buttonFormula": "buttons[].button_formula",
|
|
6628
7056
|
"buttons[].buttonFormulaType": "buttons[].button_formula_type",
|
|
6629
7057
|
"buttons[].printTpls": "buttons[].print_tpls",
|
|
7058
|
+
"actionButtons": "action_buttons",
|
|
7059
|
+
"actionButtonsMode": "action_buttons_mode",
|
|
7060
|
+
"action_buttons[].button_text": "action_buttons[].text",
|
|
7061
|
+
"action_buttons[].trigger_action": "action_buttons[].action",
|
|
7062
|
+
"action_buttons[].trigger_link_url": "action_buttons[].url",
|
|
7063
|
+
"action_buttons[].visibleWhen": "action_buttons[].visible_when",
|
|
7064
|
+
"action_buttons[].fieldMappings": "action_buttons[].field_mappings",
|
|
7065
|
+
"action_buttons[].defaultValues": "action_buttons[].default_values",
|
|
6630
7066
|
},
|
|
6631
7067
|
"allowed_values": {
|
|
6632
7068
|
"view.type": [member.value for member in PublicViewType],
|
|
6633
7069
|
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
6634
7070
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
6635
7071
|
"view.buttons.config_type": ["TOP", "DETAIL", "INSIDE"],
|
|
7072
|
+
"view.action_buttons.action": ["add_data", "link", "qRobot", "wings"],
|
|
7073
|
+
"view.action_buttons.placement": [member.value for member in PublicButtonPlacement],
|
|
7074
|
+
"view.action_buttons_mode": ["merge", "replace"],
|
|
6636
7075
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
6637
7076
|
},
|
|
6638
7077
|
"execution_notes": [
|
|
@@ -6642,6 +7081,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6642
7081
|
"read back app_get after any failed or partial view apply",
|
|
6643
7082
|
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
6644
7083
|
"buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
|
|
7084
|
+
"business buttons should be declared with action_buttons on the target view; app_views_apply writes the view first, then compiles action_buttons through the custom button writer and binds them to the resulting view_key",
|
|
7085
|
+
"patch_views[].set.action_buttons can add buttons to an existing view without rewriting columns/filters; action_buttons_mode defaults to merge, replace rewrites this view's custom button bindings, and replace plus action_buttons=[] clears bindings without deleting button bodies",
|
|
7086
|
+
"publish=false with action_buttons is blocked as VIEW_ACTION_BUTTONS_REQUIRE_PUBLISH because the underlying custom button writer may publish after successful button writes",
|
|
7087
|
+
"legacy upsert_views[].buttons is a compatibility path for raw full replacement of existing view button config; do not use it as the normal agent path",
|
|
6645
7088
|
"upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
|
|
6646
7089
|
"filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
|
|
6647
7090
|
"upsert_views[].query_conditions.rows is a layout matrix of query-panel supported field names; it is compiled to backend queryCondition queIds",
|
|
@@ -6679,6 +7122,51 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6679
7122
|
],
|
|
6680
7123
|
"remove_views": [],
|
|
6681
7124
|
},
|
|
7125
|
+
"action_buttons_example": {
|
|
7126
|
+
"profile": "default",
|
|
7127
|
+
"app_key": "APP_KEY",
|
|
7128
|
+
"publish": True,
|
|
7129
|
+
"upsert_views": [
|
|
7130
|
+
{
|
|
7131
|
+
"name": "生产工单执行视图",
|
|
7132
|
+
"type": "table",
|
|
7133
|
+
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
7134
|
+
"action_buttons": [
|
|
7135
|
+
{
|
|
7136
|
+
"text": "开始生产",
|
|
7137
|
+
"action": "link",
|
|
7138
|
+
"url": "https://example.com/start",
|
|
7139
|
+
"placement": "list",
|
|
7140
|
+
"visible_when": [{"field_name": "状态", "operator": "eq", "value": "待排产"}],
|
|
7141
|
+
}
|
|
7142
|
+
],
|
|
7143
|
+
}
|
|
7144
|
+
],
|
|
7145
|
+
"remove_views": [],
|
|
7146
|
+
},
|
|
7147
|
+
"action_buttons_patch_example": {
|
|
7148
|
+
"profile": "default",
|
|
7149
|
+
"app_key": "APP_KEY",
|
|
7150
|
+
"publish": True,
|
|
7151
|
+
"patch_views": [
|
|
7152
|
+
{
|
|
7153
|
+
"view_key": "RAW_VIEW_KEY",
|
|
7154
|
+
"set": {
|
|
7155
|
+
"action_buttons": [
|
|
7156
|
+
{
|
|
7157
|
+
"text": "创建验收单",
|
|
7158
|
+
"action": "add_data",
|
|
7159
|
+
"target_app_key": "ARRIVAL_APP",
|
|
7160
|
+
"field_mappings": [{"source_field": "数据ID", "target_field": "关联工单"}],
|
|
7161
|
+
"default_values": {"状态": "待验收"},
|
|
7162
|
+
"placement": "detail",
|
|
7163
|
+
}
|
|
7164
|
+
]
|
|
7165
|
+
},
|
|
7166
|
+
}
|
|
7167
|
+
],
|
|
7168
|
+
"remove_views": [],
|
|
7169
|
+
},
|
|
6682
7170
|
"full_upsert_query_conditions_example": {
|
|
6683
7171
|
"profile": "default",
|
|
6684
7172
|
"app_key": "APP_KEY",
|
|
@@ -6954,15 +7442,38 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6954
7442
|
"name": "dash_name",
|
|
6955
7443
|
"sourceType": "source_type",
|
|
6956
7444
|
"chartRef": "chart_ref",
|
|
7445
|
+
"inlineChart": "inline_chart",
|
|
7446
|
+
"createChart": "inline_chart",
|
|
6957
7447
|
"viewRef": "view_ref",
|
|
6958
7448
|
"dashStyleConfigBO": "dash_style_config",
|
|
6959
7449
|
},
|
|
6960
|
-
"section_allowed_keys": [
|
|
7450
|
+
"section_allowed_keys": [
|
|
7451
|
+
"title",
|
|
7452
|
+
"source_type",
|
|
7453
|
+
"role",
|
|
7454
|
+
"position",
|
|
7455
|
+
"dash_style_config",
|
|
7456
|
+
"config",
|
|
7457
|
+
"chart_ref",
|
|
7458
|
+
"inline_chart",
|
|
7459
|
+
"inline_chart.app_key",
|
|
7460
|
+
"inline_chart.chart_name",
|
|
7461
|
+
"inline_chart.chart_type",
|
|
7462
|
+
"inline_chart.metric",
|
|
7463
|
+
"inline_chart.metrics",
|
|
7464
|
+
"inline_chart.group_by",
|
|
7465
|
+
"inline_chart.where",
|
|
7466
|
+
"view_ref",
|
|
7467
|
+
"text",
|
|
7468
|
+
"url",
|
|
7469
|
+
],
|
|
6961
7470
|
"section_aliases": {
|
|
6962
7471
|
"sourceType": "source_type",
|
|
6963
7472
|
"zone": "role",
|
|
6964
7473
|
"sectionRole": "role",
|
|
6965
7474
|
"chartRef": "chart_ref",
|
|
7475
|
+
"inlineChart": "inline_chart",
|
|
7476
|
+
"createChart": "inline_chart",
|
|
6966
7477
|
"viewRef": "view_ref",
|
|
6967
7478
|
"dashStyleConfigBO": "dash_style_config",
|
|
6968
7479
|
},
|
|
@@ -6988,6 +7499,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
6988
7499
|
"package_id is required when creating a new portal",
|
|
6989
7500
|
"publish=false only guarantees draft and base-info updates; it does not claim live has changed",
|
|
6990
7501
|
"chart_ref resolves by chart_id first, then exact unique chart_name",
|
|
7502
|
+
"inline_chart internally creates or updates an app-source QingBI report, then uses the returned chart_id for this portal section",
|
|
7503
|
+
"use chart_ref when an existing report already matches; use inline_chart when the portal needs a new or adjusted report",
|
|
6991
7504
|
"view_ref resolves by view_key first, then exact unique view_name",
|
|
6992
7505
|
"pc layout uses a 24-column grid; mobile layout uses a 6-column grid",
|
|
6993
7506
|
"if unsure about layout, omit position or use layout_preset=auto/dashboard_2col/dashboard_3col",
|
|
@@ -7015,7 +7528,12 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
7015
7528
|
{
|
|
7016
7529
|
"title": "经营概览",
|
|
7017
7530
|
"source_type": "chart",
|
|
7018
|
-
"
|
|
7531
|
+
"inline_chart": {
|
|
7532
|
+
"app_key": "APP_KEY",
|
|
7533
|
+
"chart_name": "数据总量",
|
|
7534
|
+
"chart_type": "summary",
|
|
7535
|
+
"metric": {"field": "数据ID", "agg": "count"},
|
|
7536
|
+
},
|
|
7019
7537
|
"position": {
|
|
7020
7538
|
"pc": {"x": 0, "y": 0, "cols": 12, "rows": 6},
|
|
7021
7539
|
"mobile": {"x": 0, "y": 0, "cols": 6, "rows": 6},
|