@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22
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/package.json +1 -1
- package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
- package/skills/cosmic-build/SKILL.md +24 -9
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
- package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
- package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
- package/skills/cosmic-build/references/compile-guide.md +36 -1
- package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
- package/skills/cosmic-build/references/delivery.md +16 -2
- package/skills/cosmic-build/references/execution-contract.md +15 -3
- package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
- package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
- package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
- package/skills/cosmic-build/references/report-filter-contract.md +53 -15
- package/skills/cosmic-build/references/sdk-inspection.md +4 -1
- package/skills/cosmic-build/references/type-registry.json +26 -17
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
- package/skills/cosmic-build/scripts/_http_client.py +3 -0
- package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
- package/skills/cosmic-build/scripts/configure_list.py +112 -0
- package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
- package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
- package/skills/cosmic-build/scripts/execution_binding.py +25 -0
- package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
- package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
- package/skills/cosmic-build/scripts/register_plugins.py +27 -10
- package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
- package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
- package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
- package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
- package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
- package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
- package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
- package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
- package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
- package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
- package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
- package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
- package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
- package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
- package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
- package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
- package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
- package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
- package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
- package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
- package/skills/cosmic-build/scripts/test_environment.py +67 -1
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
- package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
- package/skills/cosmic-dev-best-practices/references/README.md +2 -2
- package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
- package/skills/erp-analysis/references/delivery-contract.md +2 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
|
@@ -8,6 +8,7 @@ cmd_operations(按钮绑定操作)、cmd_result(后端 NPE 分类)。do_
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import difflib
|
|
11
12
|
import os
|
|
12
13
|
import re as _re
|
|
13
14
|
from .cmd_forms import (
|
|
@@ -22,13 +23,16 @@ from .cmd_operations import (
|
|
|
22
23
|
do_get_operation,
|
|
23
24
|
do_list_operations,
|
|
24
25
|
)
|
|
25
|
-
from .cmd_properties import
|
|
26
|
+
from .cmd_properties import (
|
|
27
|
+
do_get_properties, do_get_property_schema, do_list_elements, do_set_properties,
|
|
28
|
+
_compile_simple_property_map, _resolve_property_declaration, _property_values_equal,
|
|
29
|
+
)
|
|
26
30
|
from .cmd_result import _is_backend_npe
|
|
27
31
|
from .config import _api_call
|
|
28
32
|
from .formatters import _extract_error_message, _is_api_success
|
|
29
33
|
from .mutation_contract import mutation_contract
|
|
30
34
|
|
|
31
|
-
# ───
|
|
35
|
+
# ─── Column type mapping constants (single-source sync with list_column/compile.py) ────────────────────────
|
|
32
36
|
|
|
33
37
|
_LISTCOLUMN_TYPE_MAP = {
|
|
34
38
|
"str": "ListColumnAp",
|
|
@@ -58,43 +62,43 @@ _LISTCOLUMN_TYPE_MAP = {
|
|
|
58
62
|
"map": "ListMapColumnAp",
|
|
59
63
|
"lookup": "ListLookupColumnAp",
|
|
60
64
|
}
|
|
61
|
-
# ───
|
|
65
|
+
# ─── Reference field sub-property display name mapping ─────────────────────────────────────────────────
|
|
62
66
|
|
|
63
67
|
_USER_REF_SUB_NAME_MAP = {
|
|
64
|
-
"number": "
|
|
65
|
-
"name": "
|
|
66
|
-
"code": "
|
|
67
|
-
"email": "
|
|
68
|
-
"phone": "
|
|
69
|
-
"position": "
|
|
70
|
-
"department": "
|
|
71
|
-
"org": "
|
|
72
|
-
"post": "
|
|
73
|
-
"gender": "
|
|
74
|
-
"idNumber": "
|
|
75
|
-
"entryDate": "
|
|
76
|
-
"leaveDate": "
|
|
68
|
+
"number": "Number",
|
|
69
|
+
"name": "Name",
|
|
70
|
+
"code": "Code",
|
|
71
|
+
"email": "Email",
|
|
72
|
+
"phone": "Phone",
|
|
73
|
+
"position": "Position",
|
|
74
|
+
"department": "Department",
|
|
75
|
+
"org": "Organization",
|
|
76
|
+
"post": "Post",
|
|
77
|
+
"gender": "Gender",
|
|
78
|
+
"idNumber": "ID Number",
|
|
79
|
+
"entryDate": "Entry Date",
|
|
80
|
+
"leaveDate": "Leave Date",
|
|
77
81
|
}
|
|
78
82
|
_BASEDATA_REF_SUB_NAME_MAP = {
|
|
79
|
-
"number": "
|
|
80
|
-
"name": "
|
|
81
|
-
"code": "
|
|
82
|
-
"status": "
|
|
83
|
-
"description": "
|
|
84
|
-
"parent": "
|
|
85
|
-
"level": "
|
|
86
|
-
"isLeaf": "
|
|
87
|
-
"enable": "
|
|
88
|
-
"createDate": "
|
|
89
|
-
"modifyDate": "
|
|
90
|
-
"remark": "
|
|
83
|
+
"number": "Number",
|
|
84
|
+
"name": "Name",
|
|
85
|
+
"code": "Code",
|
|
86
|
+
"status": "Status",
|
|
87
|
+
"description": "Description",
|
|
88
|
+
"parent": "Parent",
|
|
89
|
+
"level": "Level",
|
|
90
|
+
"isLeaf": "Is Leaf",
|
|
91
|
+
"enable": "Enabled",
|
|
92
|
+
"createDate": "Create Date",
|
|
93
|
+
"modifyDate": "Modify Date",
|
|
94
|
+
"remark": "Remark",
|
|
91
95
|
}
|
|
92
96
|
def _lookup_field_meta(elements_data, field_key, log_tag):
|
|
93
|
-
"""按准确 key
|
|
97
|
+
"""按准确 key 优先、再按唯一显示名定位字段元信息。.
|
|
94
98
|
|
|
95
|
-
先精确匹配 field_key
|
|
96
|
-
|
|
97
|
-
resolved_field_key 为回退后的平台真实 key
|
|
99
|
+
先精确匹配 field_key; No match时按平台固定长度前缀截断 (fieldKey>21 截尾)
|
|
100
|
+
做最长前缀回退, 回读平台真实 key. 返回 (field_meta_or_None, resolved_field_key),
|
|
101
|
+
resolved_field_key 为回退后的平台真实 key (无回退时即原值) .
|
|
98
102
|
"""
|
|
99
103
|
for el in elements_data:
|
|
100
104
|
if el.get("key") == field_key:
|
|
@@ -115,8 +119,8 @@ def _lookup_field_meta(elements_data, field_key, log_tag):
|
|
|
115
119
|
)
|
|
116
120
|
if truncated:
|
|
117
121
|
print(
|
|
118
|
-
f"[{log_tag}]
|
|
119
|
-
f"
|
|
122
|
+
f"[{log_tag}] Field '{field_key}' 未精确命中, "
|
|
123
|
+
f"回退 to 平台真实 key '{truncated}' (平台静默截断) ",
|
|
120
124
|
flush=True,
|
|
121
125
|
)
|
|
122
126
|
for el in elements_data:
|
|
@@ -142,13 +146,13 @@ def do_add_list_column(cfg, token, form_number, field_key, name=None,
|
|
|
142
146
|
column_type=None, column_key=None,
|
|
143
147
|
hyperlink=False, order="NotOrder",
|
|
144
148
|
after_column=None, visible=True):
|
|
145
|
-
"""
|
|
149
|
+
"""快捷在列表视图添加一列 (form+listcolumn+add) .
|
|
146
150
|
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
- column_key
|
|
151
|
-
-
|
|
151
|
+
- Auto-resolve formNumber → formId
|
|
152
|
+
- 自动查Field type并映射 columnType (DecimalField → DecimalListColumnAp 等)
|
|
153
|
+
- 自动取Field name 作为列名 (可用 --name 覆盖)
|
|
154
|
+
- column_key defaults to `listcol_{field_key}`
|
|
155
|
+
- 自动探测列表是否Already exists同 listFieldId 的列, 存在则报错 (防重复)
|
|
152
156
|
|
|
153
157
|
Returns:
|
|
154
158
|
(form_id, [op])
|
|
@@ -157,45 +161,45 @@ def do_add_list_column(cfg, token, form_number, field_key, name=None,
|
|
|
157
161
|
form_id = _resolve_form_id(cfg, token, form_number)
|
|
158
162
|
print(f"[addListColumn] formNumber={form_number} → formId={form_id}", flush=True)
|
|
159
163
|
|
|
160
|
-
# 2.
|
|
164
|
+
# 2. 查询Field列表, 定位目标Field type (用于自动映射 columnType / 回填 name)
|
|
161
165
|
elements_result = do_list_elements(cfg, token, form_number, category="field")
|
|
162
166
|
if not _is_api_success(elements_result):
|
|
163
|
-
raise ValueError(f"
|
|
167
|
+
raise ValueError(f"查询Field列表failed: {_extract_error_message(elements_result)}")
|
|
164
168
|
elements_data = elements_result.get("data") or []
|
|
165
169
|
field_meta, field_key = _lookup_field_meta(elements_data, field_key, "addListColumn")
|
|
166
170
|
if field_meta is None:
|
|
167
171
|
raise ValueError(
|
|
168
|
-
f"
|
|
172
|
+
f"Form '{form_number}' 中未找 to Field key='{field_key}'; 请先确认FieldAlready exists或先 addField"
|
|
169
173
|
)
|
|
170
174
|
|
|
171
175
|
field_type = field_meta.get("type") or ""
|
|
172
176
|
default_name = field_meta.get("name") or field_key
|
|
173
177
|
|
|
174
|
-
# 3. 推导 columnType
|
|
178
|
+
# 3. 推导 columnType (用户显式传入优先)
|
|
175
179
|
resolved_column_type = column_type or _LISTCOLUMN_TYPE_MAP.get(field_type, "ListColumnAp")
|
|
176
180
|
if column_type is None:
|
|
177
181
|
print(
|
|
178
|
-
f"[addListColumn]
|
|
182
|
+
f"[addListColumn] Field '{field_key}' type '{field_type}' → columnType={resolved_column_type}",
|
|
179
183
|
flush=True,
|
|
180
184
|
)
|
|
181
185
|
|
|
182
|
-
# 4.
|
|
186
|
+
# 4. 防重复: 检查列表是否已有同 listFieldId 的列
|
|
183
187
|
try:
|
|
184
188
|
schema_result = do_get_form_schema(cfg, token, form_id)
|
|
185
189
|
list_columns = ((schema_result.get("data") or {}).get("listColumns")) or []
|
|
186
190
|
for col in list_columns:
|
|
187
191
|
if col.get("listFieldId") == field_key:
|
|
188
192
|
raise ValueError(
|
|
189
|
-
f"
|
|
190
|
-
f"如需修改请用 modify
|
|
193
|
+
f"列表Already exists绑定Field '{field_key}' column (key='{col.get('key')}', name='{col.get('name')}'); "
|
|
194
|
+
f"如需修改请用 modify, 禁止重复 add"
|
|
191
195
|
)
|
|
192
196
|
except ValueError:
|
|
193
197
|
raise
|
|
194
198
|
except Exception as e:
|
|
195
|
-
# schema
|
|
196
|
-
print(f"[addListColumn] ⚠️
|
|
199
|
+
# schema 查询failed不阻塞 (服务端会兜底校验) , 仅告警
|
|
200
|
+
print(f"[addListColumn] ⚠️ Pre-check listColumns exception: {e} (继续执行, 由服务端兜底校验) ", flush=True)
|
|
197
201
|
|
|
198
|
-
# 5.
|
|
202
|
+
# 5. Build op
|
|
199
203
|
resolved_key = column_key or f"listcol_{field_key}"
|
|
200
204
|
resolved_name = name or default_name
|
|
201
205
|
|
|
@@ -206,7 +210,7 @@ def do_add_list_column(cfg, token, form_number, field_key, name=None,
|
|
|
206
210
|
"hyperlink": bool(hyperlink),
|
|
207
211
|
"order": order,
|
|
208
212
|
}
|
|
209
|
-
# visible 仅当 hidden=True 时显式写入 false
|
|
213
|
+
# visible 仅当 hidden=True 时显式写入 false (default可见, 无需传)
|
|
210
214
|
if not visible:
|
|
211
215
|
value["visible"] = False
|
|
212
216
|
op = {
|
|
@@ -222,13 +226,13 @@ def do_add_list_column(cfg, token, form_number, field_key, name=None,
|
|
|
222
226
|
op["extra"] = {"afterField": after_column}
|
|
223
227
|
|
|
224
228
|
return form_id, [op]
|
|
225
|
-
# ─── addButton
|
|
229
|
+
# ─── addButton 快捷命令 (加Button + 绑定操作 一条命令) ─────────────────
|
|
226
230
|
|
|
227
231
|
def _find_control_in_form_layout(form_layout, target_key):
|
|
228
|
-
"""在 formLayout 树里递归查找 key == target_key
|
|
232
|
+
"""在 formLayout 树里递归查找 key == target_key 的节点.
|
|
229
233
|
|
|
230
|
-
返回 (node, parent_key) 或 None
|
|
231
|
-
用于 addButton / addWidget
|
|
234
|
+
返回 (node, parent_key) 或 None; 命中时 parent_key 可能为 None (根节点下直挂) .
|
|
235
|
+
用于 addButton / addWidget 的前置查重, 避免服务端 add 非幂等导致的重复记录.
|
|
232
236
|
"""
|
|
233
237
|
if not isinstance(form_layout, dict):
|
|
234
238
|
return None
|
|
@@ -246,14 +250,14 @@ def _find_control_in_form_layout(form_layout, target_key):
|
|
|
246
250
|
|
|
247
251
|
return _walk(form_layout, None)
|
|
248
252
|
def _detect_list_toolbar_key(schema_result):
|
|
249
|
-
"""在 getFormSchema 返回中探测 listLayout 顶层 ToolbarAp 的 key
|
|
253
|
+
"""在 getFormSchema 返回中探测 listLayout 顶层 ToolbarAp 的 key.
|
|
250
254
|
|
|
251
|
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
255
|
+
兼容两种苍穹列表模板:
|
|
256
|
+
- 新式: listLayout.children 里含 key=tbmain, type=ToolbarAp
|
|
257
|
+
- 旧式: listLayout.children 里含 key=_toolbar_, type=ToolbarAp (单据/基础资料模板常见)
|
|
254
258
|
|
|
255
|
-
返回首个命中的 key
|
|
256
|
-
供 do_add_button 在 target_view=list 且未显式 parentScope
|
|
259
|
+
返回首个命中的 key; No match返回 None.
|
|
260
|
+
供 do_add_button 在 target_view=list 且未显式 parentScope 时自动挂载容器定位.
|
|
257
261
|
"""
|
|
258
262
|
if not isinstance(schema_result, dict):
|
|
259
263
|
return None
|
|
@@ -270,13 +274,15 @@ def _detect_list_toolbar_key(schema_result):
|
|
|
270
274
|
return None
|
|
271
275
|
|
|
272
276
|
def _platform_element_key(element_key):
|
|
273
|
-
"""
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
"""Return the element key in the casing the platform actually stores.
|
|
278
|
+
|
|
279
|
+
The platform lower-cases element keys on write. addButton used to write the
|
|
280
|
+
button with the caller's casing and then call setProperties(OperationKey)
|
|
281
|
+
with that same casing, which the platform could not resolve: it had stored
|
|
282
|
+
bar_oa_callphone while the CLI asked for bar_oa_callPhone. The button was
|
|
283
|
+
created and left bound to nothing (OperationKey null), and the command
|
|
284
|
+
reported failure while leaving that orphan behind. Deriving the key in the
|
|
285
|
+
stored casing keeps the write and the follow-up binding on the same name.
|
|
280
286
|
"""
|
|
281
287
|
if not isinstance(element_key, str):
|
|
282
288
|
return element_key
|
|
@@ -284,7 +290,7 @@ def _platform_element_key(element_key):
|
|
|
284
290
|
|
|
285
291
|
|
|
286
292
|
def _resolve_button_operation_key(operations, selector):
|
|
287
|
-
"""
|
|
293
|
+
"""Resolve one existing operation key/name, or preserve a new semantic key."""
|
|
288
294
|
wanted = str(selector or "").strip()
|
|
289
295
|
rows = operations if isinstance(operations, list) else []
|
|
290
296
|
key_matches = [
|
|
@@ -316,15 +322,17 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
316
322
|
index=None, log_enable=True, skip_operation=False,
|
|
317
323
|
target_view=None, template_number=None,
|
|
318
324
|
verified_target=None):
|
|
319
|
-
"""
|
|
325
|
+
"""一条命令completed加Button+绑定操作 (编排 addOperation + modifyMeta(add ) + setProperties(OperationKey)) .
|
|
320
326
|
|
|
321
|
-
|
|
327
|
+
返回: dict (success/formId/modelType/buttonType/buttonKey/parentScope/operationKey/modifyMeta/setProperties)
|
|
322
328
|
"""
|
|
323
329
|
# 延迟导入:do_modify_meta 在 cmd_metaops(上层),模块级互相 import 会成环。
|
|
324
330
|
from .cmd_metaops import do_modify_meta
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
331
|
+
# The platform stores operationKey lower-cased, so resolve the identity to
|
|
332
|
+
# the stored casing before it is used to derive the button key, to bind the
|
|
333
|
+
# operation and to assert the operation exists. Without this, binding a
|
|
334
|
+
# mixed-case key produced a button bound to nothing and an operationPresent
|
|
335
|
+
# check that could never succeed.
|
|
328
336
|
operations_result = None
|
|
329
337
|
raw_operation_selector = str(operation_key or "").strip()
|
|
330
338
|
if _re.fullmatch(r"[A-Za-z0-9_.-]+", raw_operation_selector):
|
|
@@ -337,22 +345,23 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
337
345
|
if isinstance(operations_data, dict):
|
|
338
346
|
operations_data = operations_data.get("operations") or []
|
|
339
347
|
operation_key = _resolve_button_operation_key(operations_data, raw_operation_selector)
|
|
340
|
-
#
|
|
348
|
+
# Normalize target_view
|
|
341
349
|
tv = (target_view or "form").strip().lower()
|
|
342
350
|
if tv not in ("form", "list"):
|
|
343
|
-
raise ValueError(f"target_view 只支持 'form' 或 'list'
|
|
344
|
-
#
|
|
351
|
+
raise ValueError(f"target_view 只支持 'form' 或 'list', Current value={target_view}")
|
|
352
|
+
# Public writes pass the exact form/app tuple already proved by the CLI.
|
|
353
|
+
# The fallback remains only for internal callers.
|
|
345
354
|
form_rec = verified_target
|
|
346
355
|
if form_rec is None:
|
|
347
356
|
forms_result = do_query_forms(cfg, token, keyword=form_number)
|
|
348
357
|
if not _is_api_success(forms_result):
|
|
349
|
-
raise ValueError(f"
|
|
358
|
+
raise ValueError(f"查询Formfailed: {_extract_error_message(forms_result)}")
|
|
350
359
|
for f in (forms_result.get("data") or []):
|
|
351
360
|
if f.get("formNumber") == form_number:
|
|
352
361
|
form_rec = f
|
|
353
362
|
break
|
|
354
363
|
if not form_rec:
|
|
355
|
-
raise ValueError(f"
|
|
364
|
+
raise ValueError(f"未找 to Code为 '{form_number}' form")
|
|
356
365
|
form_id = form_rec.get("formId")
|
|
357
366
|
model_type = form_rec.get("modelType") or ""
|
|
358
367
|
# Ported from beta 0b495ae1: the dialog template puts its buttons in a different
|
|
@@ -362,19 +371,19 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
362
371
|
print(f"[addButton] formNumber={form_number} → formId={form_id}, modelType={model_type}",
|
|
363
372
|
flush=True)
|
|
364
373
|
|
|
365
|
-
# 2.
|
|
374
|
+
# 2. 判定Button承载方式 (button_type 显式传入优先)
|
|
366
375
|
bar_types = {"BillFormModel", "BaseFormModel"}
|
|
367
376
|
dyn_types = {"DynamicFormModel", "ReportFormModel", "PrintModel",
|
|
368
377
|
"ParameterFormModel", "QingAnalysisModel"}
|
|
369
378
|
if tv == "list":
|
|
370
|
-
# 列表视图 tbmain 下只有 BarItemAp
|
|
379
|
+
# 列表视图 tbmain 下只有 BarItemAp, 强制 BarItem; 仅 BillFormModel / BaseFormModel 具备 listMeta
|
|
371
380
|
if model_type not in bar_types:
|
|
372
381
|
raise ValueError(
|
|
373
|
-
f"target_view=list 仅支持 BillFormModel / BaseFormModel
|
|
374
|
-
f"当前 modelType={model_type}
|
|
382
|
+
f"target_view=list 仅支持 BillFormModel / BaseFormModel, "
|
|
383
|
+
f"当前 modelType={model_type} no listMeta"
|
|
375
384
|
)
|
|
376
385
|
if button_type and button_type != "BarItem":
|
|
377
|
-
print(f"[addButton] ⚠️ target_view=list 时 button_type 强制为 BarItem
|
|
386
|
+
print(f"[addButton] ⚠️ target_view=list 时 button_type 强制为 BarItem (传入={button_type} 忽略) ",
|
|
378
387
|
flush=True)
|
|
379
388
|
bt = "BarItem"
|
|
380
389
|
elif button_type:
|
|
@@ -385,9 +394,9 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
385
394
|
bt = "ButtonAp"
|
|
386
395
|
else:
|
|
387
396
|
bt = "BarItem"
|
|
388
|
-
print(f"[addButton] ⚠️
|
|
397
|
+
print(f"[addButton] ⚠️ Unrecognized modelType={model_type}, default使用 BarItem", flush=True)
|
|
389
398
|
|
|
390
|
-
# 3.
|
|
399
|
+
# 3. Generate button key & parentScope
|
|
391
400
|
if key:
|
|
392
401
|
btn_key = key
|
|
393
402
|
elif bt == "BarItem":
|
|
@@ -400,18 +409,18 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
400
409
|
scope = parent_scope
|
|
401
410
|
schema_cached = None
|
|
402
411
|
elif bt == "BarItem" and tv == "list":
|
|
403
|
-
#
|
|
412
|
+
# 列表工具栏容器自动探测: 兼容 tbmain / _toolbar_ 两种模板.
|
|
404
413
|
schema_cached = do_get_form_schema(cfg, token, form_id)
|
|
405
414
|
detected = _detect_list_toolbar_key(schema_cached)
|
|
406
415
|
if detected:
|
|
407
416
|
scope = detected
|
|
408
417
|
if detected != "tbmain":
|
|
409
|
-
print(f"[addButton] ℹ️
|
|
418
|
+
print(f"[addButton] ℹ️ 自动探测 to 列表工具栏容器 key={detected} (旧式模板) , 已替代default tbmain",
|
|
410
419
|
flush=True)
|
|
411
420
|
else:
|
|
412
421
|
scope = "tbmain"
|
|
413
|
-
print(f"[addButton] ⚠️ 未在 listLayout
|
|
414
|
-
f"
|
|
422
|
+
print(f"[addButton] ⚠️ 未在 listLayout 顶层Found ToolbarAp 子节点, 回退 parentScope=tbmain; "
|
|
423
|
+
f"If positioning is wrong, manually specify real container key via --parentScope", flush=True)
|
|
415
424
|
elif bt == "BarItem":
|
|
416
425
|
scope = "tbmain"
|
|
417
426
|
schema_cached = None
|
|
@@ -420,20 +429,21 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
420
429
|
schema_cached = None
|
|
421
430
|
elif bt == "AdvConBarItem":
|
|
422
431
|
raise ValueError(
|
|
423
|
-
"AdvConBarItem
|
|
424
|
-
"
|
|
432
|
+
"AdvConBarItem (分录工具栏Button) 必须通过 --parentScope 显式指定分录工具栏 key, "
|
|
433
|
+
"please use ui_api.py getToolbars to get it first"
|
|
425
434
|
)
|
|
426
435
|
else:
|
|
427
|
-
scope = form_number # ButtonAp/Button
|
|
436
|
+
scope = form_number # ButtonAp/Button 直挂Form根
|
|
428
437
|
schema_cached = None
|
|
429
438
|
|
|
430
|
-
# 4.
|
|
439
|
+
# 4. Check the non-idempotent button write before creating an operation.
|
|
431
440
|
schema_result = schema_cached if schema_cached is not None else do_get_form_schema(cfg, token, form_id)
|
|
432
441
|
button_already_exists = False
|
|
433
442
|
existing_operation_key = None
|
|
434
443
|
if not _is_api_success(schema_result):
|
|
435
444
|
raise ValueError(
|
|
436
|
-
"BUTTON_LAYOUT_INVENTORY_UNAVAILABLE:
|
|
445
|
+
"BUTTON_LAYOUT_INVENTORY_UNAVAILABLE: cannot verify the target layout "
|
|
446
|
+
"before a non-idempotent button write"
|
|
437
447
|
)
|
|
438
448
|
schema_data = schema_result.get("data") or {}
|
|
439
449
|
layout_key = "listLayout" if tv == "list" else "formLayout"
|
|
@@ -443,8 +453,8 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
443
453
|
_existing_node, _existing_parent = hit
|
|
444
454
|
if _existing_parent != scope:
|
|
445
455
|
raise ValueError(
|
|
446
|
-
f"BUTTON_PARENT_MISMATCH:
|
|
447
|
-
f"{_existing_parent!r}
|
|
456
|
+
f"BUTTON_PARENT_MISMATCH: existing button {btn_key} is under "
|
|
457
|
+
f"{_existing_parent!r}, requested parentScope={scope!r}"
|
|
448
458
|
)
|
|
449
459
|
button_already_exists = True
|
|
450
460
|
existing_operation_key = (
|
|
@@ -454,13 +464,17 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
454
464
|
full_metadata = do_load_full_metadata(cfg, token, form_number)
|
|
455
465
|
if not _is_api_success(full_metadata):
|
|
456
466
|
raise ValueError(
|
|
457
|
-
"BUTTON_KEY_INVENTORY_UNAVAILABLE:
|
|
467
|
+
"BUTTON_KEY_INVENTORY_UNAVAILABLE: full metadata cannot be read; "
|
|
468
|
+
"no button or operation was written"
|
|
458
469
|
)
|
|
459
470
|
published_type = _find_element_type_by_key(
|
|
460
471
|
(full_metadata or {}).get("data"), btn_key
|
|
461
472
|
)
|
|
462
|
-
#
|
|
463
|
-
#
|
|
473
|
+
# A Bill form and its list share one metadata namespace. The historical
|
|
474
|
+
# default ``bar_<operationKey>`` therefore collides when callers publish the
|
|
475
|
+
# same semantic operation on both toolbars. Keep the legacy key when it is
|
|
476
|
+
# free, but choose a deterministic list-only key when the implicit key is
|
|
477
|
+
# already owned by the form layout.
|
|
464
478
|
if (
|
|
465
479
|
published_type is not None
|
|
466
480
|
and not button_already_exists
|
|
@@ -473,8 +487,8 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
473
487
|
_existing_node, _existing_parent = fallback_hit
|
|
474
488
|
if _existing_parent != scope:
|
|
475
489
|
raise ValueError(
|
|
476
|
-
f"BUTTON_PARENT_MISMATCH:
|
|
477
|
-
f"{_existing_parent!r}
|
|
490
|
+
f"BUTTON_PARENT_MISMATCH: existing button {btn_key} is under "
|
|
491
|
+
f"{_existing_parent!r}, requested parentScope={scope!r}"
|
|
478
492
|
)
|
|
479
493
|
button_already_exists = True
|
|
480
494
|
existing_operation_key = (
|
|
@@ -486,28 +500,31 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
486
500
|
)
|
|
487
501
|
if published_type is not None and not button_already_exists:
|
|
488
502
|
raise ValueError(
|
|
489
|
-
f"BUTTON_KEY_ALREADY_USED: key {btn_key!r}
|
|
490
|
-
f"{published_type!r}
|
|
503
|
+
f"BUTTON_KEY_ALREADY_USED: key {btn_key!r} is already published as "
|
|
504
|
+
f"{published_type!r}; no button or operation was written"
|
|
491
505
|
)
|
|
492
506
|
|
|
493
|
-
# T_META_ENTITY.FKey
|
|
494
|
-
# addButton
|
|
495
|
-
# operationKey
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
#
|
|
507
|
+
# T_META_ENTITY.FKey is character varying(36) and stores button keys as well
|
|
508
|
+
# as operation keys. addButton derives the button key from the operation key
|
|
509
|
+
# ("bar_"/"tbl_"/"btn"/"tbl_" prefix), so an operationKey that is itself
|
|
510
|
+
# legal can still produce an over-long button key. A DB-level failure leaves
|
|
511
|
+
# an unsavable row that bricks every later metadata write on the form.
|
|
512
|
+
# This sits before the skip_operation branch on purpose: --skipOperation
|
|
513
|
+
# skips only the operation write, never the button write, so it must not
|
|
514
|
+
# skip this check either.
|
|
499
515
|
if len(btn_key) > _OPERATION_KEY_MAX_LENGTH:
|
|
500
516
|
raise ValueError(
|
|
501
|
-
"BUTTON_KEY_TOO_LONG:
|
|
502
|
-
f"{btn_key!r}
|
|
503
|
-
f"{_OPERATION_KEY_MAX_LENGTH}
|
|
517
|
+
"BUTTON_KEY_TOO_LONG: derived button key "
|
|
518
|
+
f"{btn_key!r} is {len(btn_key)} characters, exceeding the "
|
|
519
|
+
f"platform storage limit of {_OPERATION_KEY_MAX_LENGTH}; "
|
|
520
|
+
"shorten operationKey or pass an explicit shorter --key"
|
|
504
521
|
)
|
|
505
522
|
|
|
506
|
-
# 5. operationKey
|
|
523
|
+
# 5. operationKey pre-check → addOperation if needed
|
|
507
524
|
added_operation = False
|
|
508
525
|
if skip_operation:
|
|
509
|
-
print(f"[addButton] --skipOperation
|
|
510
|
-
f"
|
|
526
|
+
print(f"[addButton] --skipOperation 指定, 跳过 addOperation"
|
|
527
|
+
f" (must ensure operationKey={operation_key} Already exists) ", flush=True)
|
|
511
528
|
else:
|
|
512
529
|
ops_result = operations_result or do_list_operations(cfg, token, form_number)
|
|
513
530
|
ops_data = ops_result.get("data") if _is_api_success(ops_result) else None
|
|
@@ -519,8 +536,10 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
519
536
|
existing = []
|
|
520
537
|
existing_keys = {o.get("operationKey") for o in existing if isinstance(o, dict)}
|
|
521
538
|
operation_exists = operation_key in existing_keys
|
|
522
|
-
|
|
523
|
-
#
|
|
539
|
+
|
|
540
|
+
# listOperations can lag immediately after addOperation. One exact read
|
|
541
|
+
# prevents a stale inventory from turning a bind-only request into a
|
|
542
|
+
# duplicate operation write.
|
|
524
543
|
if not operation_exists:
|
|
525
544
|
exact_operation = do_get_operation(
|
|
526
545
|
cfg, token, form_number, operation_key
|
|
@@ -536,7 +555,7 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
536
555
|
and exact_data.get("operationKey") == operation_key
|
|
537
556
|
)
|
|
538
557
|
if operation_exists:
|
|
539
|
-
print(f"[addButton] operationKey={operation_key}
|
|
558
|
+
print(f"[addButton] operationKey={operation_key} Already exists, 跳过 addOperation",
|
|
540
559
|
flush=True)
|
|
541
560
|
else:
|
|
542
561
|
op_config = {
|
|
@@ -547,8 +566,8 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
547
566
|
}
|
|
548
567
|
add_op_result = do_add_operation(cfg, token, form_number, op_config)
|
|
549
568
|
if not _is_api_success(add_op_result):
|
|
550
|
-
# 已知后端 addOperation NPE
|
|
551
|
-
# 通过 listOperations
|
|
569
|
+
# 已知后端 addOperation NPE 缺陷: 响应抛 NPE 但实际written.
|
|
570
|
+
# 通过 listOperations 后置回查, operationKey Already exists则视为succeeded.
|
|
552
571
|
err_msg = _extract_error_message(add_op_result) or ""
|
|
553
572
|
looks_like_npe = _is_backend_npe(err_msg)
|
|
554
573
|
recovered = False
|
|
@@ -567,8 +586,8 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
567
586
|
recovered = True
|
|
568
587
|
added_operation = True
|
|
569
588
|
print(
|
|
570
|
-
f"[addButton] ⚠️ addOperation 响应 NPE
|
|
571
|
-
f"
|
|
589
|
+
f"[addButton] ⚠️ addOperation 响应 NPE, 但回查 listOperations "
|
|
590
|
+
f"Already exists operationKey={operation_key}, 视为written (已知后端缺陷容忍) ",
|
|
572
591
|
flush=True,
|
|
573
592
|
)
|
|
574
593
|
if not recovered and "操作Key已存在" in err_msg:
|
|
@@ -587,18 +606,18 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
587
606
|
):
|
|
588
607
|
recovered = True
|
|
589
608
|
print(
|
|
590
|
-
f"[addButton] operationKey={operation_key}
|
|
609
|
+
f"[addButton] operationKey={operation_key} 已由精确回读确认存在, "
|
|
591
610
|
"按绑定既有操作继续",
|
|
592
611
|
flush=True,
|
|
593
612
|
)
|
|
594
613
|
if not recovered:
|
|
595
|
-
raise ValueError(f"addOperation
|
|
614
|
+
raise ValueError(f"addOperation failed: {err_msg}")
|
|
596
615
|
else:
|
|
597
616
|
added_operation = True
|
|
598
|
-
print(f"[addButton] ✅
|
|
617
|
+
print(f"[addButton] ✅ New operation {operation_key} "
|
|
599
618
|
f"(operationType={operation_type})", flush=True)
|
|
600
619
|
|
|
601
|
-
# 6. modifyMeta
|
|
620
|
+
# 6. modifyMeta 加Button/widget
|
|
602
621
|
if tv == "list":
|
|
603
622
|
value = {"controlType": "BarItemAp", "buttonType": bt, "name": name, "operationKey": operation_key}
|
|
604
623
|
element_type = "listbutton"
|
|
@@ -636,8 +655,8 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
636
655
|
form_number=form_number)
|
|
637
656
|
mm = modify_result.get("modifyMeta") if isinstance(modify_result, dict) else None
|
|
638
657
|
if mm is not None and not _is_api_success(mm):
|
|
639
|
-
# 已知后端 modifyMeta NPE
|
|
640
|
-
# 回查 form layout
|
|
658
|
+
# 已知后端 modifyMeta NPE 缺陷: 响应抛 NPE 但实际written.
|
|
659
|
+
# 回查 form layout, 若 btn_key 已出现在 parent 下则视为succeeded.
|
|
641
660
|
err_msg = _extract_error_message(mm) or ""
|
|
642
661
|
looks_like_npe = _is_backend_npe(err_msg)
|
|
643
662
|
recovered = False
|
|
@@ -650,30 +669,30 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
650
669
|
if _find_control_in_form_layout(layout_tree, btn_key) is not None:
|
|
651
670
|
recovered = True
|
|
652
671
|
print(
|
|
653
|
-
f"[addButton] ⚠️ modifyMeta(add
|
|
654
|
-
f"{layout_key}
|
|
672
|
+
f"[addButton] ⚠️ modifyMeta(add {element_type}) 响应 NPE, 但回查 "
|
|
673
|
+
f"{layout_key} 已Found {btn_key}, 视为written (已知后端缺陷容忍) ",
|
|
655
674
|
flush=True,
|
|
656
675
|
)
|
|
657
676
|
if not recovered:
|
|
658
677
|
raise ValueError(
|
|
659
|
-
f"modifyMeta(add
|
|
678
|
+
f"modifyMeta(add {element_type}) failed: {err_msg}"
|
|
660
679
|
)
|
|
661
680
|
else:
|
|
662
|
-
print(f"[addButton] ✅
|
|
681
|
+
print(f"[addButton] ✅ Button {btn_key} ({element_type}/{bt}) 已挂 to parentScope={scope}",
|
|
663
682
|
flush=True)
|
|
664
683
|
|
|
665
|
-
# 7. setProperties
|
|
684
|
+
# 7. setProperties bind OperationKey
|
|
666
685
|
if tv == "list":
|
|
667
686
|
bind_result = {
|
|
668
687
|
"skipped": True,
|
|
669
|
-
"reason": "target_view=list
|
|
688
|
+
"reason": "target_view=list: operationKey 已随 add op 的 value 直接绑定在 BarItemAp 上",
|
|
670
689
|
}
|
|
671
|
-
print(f"[addButton] ✅ target_view=list
|
|
690
|
+
print(f"[addButton] ✅ target_view=list Bind OperationKey={operation_key} → {btn_key} (随 add op completed)",
|
|
672
691
|
flush=True)
|
|
673
692
|
elif button_already_exists and existing_operation_key == operation_key:
|
|
674
693
|
bind_result = {
|
|
675
694
|
"skipped": True,
|
|
676
|
-
"reason": "
|
|
695
|
+
"reason": "existing button already has the requested OperationKey",
|
|
677
696
|
}
|
|
678
697
|
else:
|
|
679
698
|
bind_result = do_set_properties(
|
|
@@ -684,11 +703,12 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
684
703
|
)
|
|
685
704
|
if not _is_api_success(bind_result):
|
|
686
705
|
raise ValueError(
|
|
687
|
-
f"setProperties(OperationKey)
|
|
706
|
+
f"setProperties(OperationKey) failed: {_extract_error_message(bind_result)}"
|
|
688
707
|
)
|
|
689
|
-
print(f"[addButton] ✅
|
|
708
|
+
print(f"[addButton] ✅ Bind OperationKey={operation_key} → {btn_key}", flush=True)
|
|
690
709
|
|
|
691
|
-
# 8.
|
|
710
|
+
# 8. One independent final read proves the complete closure. This backend
|
|
711
|
+
# can return success while silently skipping a UI write.
|
|
692
712
|
verify_schema = do_get_form_schema(cfg, token, form_id)
|
|
693
713
|
verify_layout = ((verify_schema.get("data") or {}).get(
|
|
694
714
|
"listLayout" if tv == "list" else "formLayout"
|
|
@@ -736,7 +756,7 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
736
756
|
return {
|
|
737
757
|
"status": False,
|
|
738
758
|
"errorCode": "ADD_BUTTON_VERIFY_FAILED",
|
|
739
|
-
"message": "
|
|
759
|
+
"message": "button closure did not pass independent metadata read-back",
|
|
740
760
|
"data": verification,
|
|
741
761
|
}
|
|
742
762
|
|
|
@@ -754,31 +774,31 @@ def do_add_button(cfg, token, form_number, name, operation_key,
|
|
|
754
774
|
"setProperties": bind_result,
|
|
755
775
|
"verification": verification,
|
|
756
776
|
}
|
|
757
|
-
# ───
|
|
777
|
+
# ─── 过滤Field / 工具栏项 CLI (listMeta子项, 配合 FilterColumnHandler) ────
|
|
758
778
|
|
|
759
779
|
# 视图 key → elementType
|
|
760
|
-
# ⚠️ 服务端真正识别的 parentScope ID 为【无 ap
|
|
761
|
-
# 统一通过 FILTER_VIEW_ALIAS 规范化为无 ap
|
|
780
|
+
# ⚠️ 服务端真正识别的 parentScope ID 为【无 ap 后缀】版本. 带 ap 后缀的旧值仅作兼容,
|
|
781
|
+
# 统一通过 FILTER_VIEW_ALIAS 规范化为无 ap 后缀版.
|
|
762
782
|
FILTER_VIEW_PARENT_KEYS = {
|
|
763
|
-
#
|
|
764
|
-
"filtergridview": "commonfiltercolumn", #
|
|
765
|
-
"schemefilterview": "schemefiltercolumn", #
|
|
766
|
-
"fastsearchgridview": "commonfiltercolumn", #
|
|
767
|
-
#
|
|
783
|
+
# 规范值 (服务端真正识别)
|
|
784
|
+
"filtergridview": "commonfiltercolumn", # Common filter
|
|
785
|
+
"schemefilterview": "schemefiltercolumn", # Scheme filter
|
|
786
|
+
"fastsearchgridview": "commonfiltercolumn", # Quick search
|
|
787
|
+
# 旧别名 (向后兼容; 内部统一映射 to 无 ap 后缀版)
|
|
768
788
|
"filtergridviewap": "commonfiltercolumn",
|
|
769
789
|
"schemefilterviewap": "schemefiltercolumn",
|
|
770
790
|
"fastsearchgridviewap": "commonfiltercolumn",
|
|
771
791
|
}
|
|
772
|
-
# 旧别名 →
|
|
792
|
+
# 旧别名 → 服务端规范值 (用于内部强制规范化)
|
|
773
793
|
FILTER_VIEW_ALIAS = {
|
|
774
794
|
"filtergridviewap": "filtergridview",
|
|
775
795
|
"schemefilterviewap": "schemefilterview",
|
|
776
796
|
"fastsearchgridviewap": "fastsearchgridview",
|
|
777
797
|
}
|
|
778
798
|
def _normalize_filter_parent_scope(parent_scope):
|
|
779
|
-
"""把带 ap 后缀的旧 parentScope 规范化为服务端真正识别的无 ap
|
|
799
|
+
"""把带 ap 后缀的旧 parentScope 规范化为服务端真正识别的无 ap 后缀版. """
|
|
780
800
|
return FILTER_VIEW_ALIAS.get(parent_scope, parent_scope)
|
|
781
|
-
# 按 FieldType 推导 Common*
|
|
801
|
+
# 按 FieldType 推导 Common* 子类 (与服务端 inferControlType 规则一致)
|
|
782
802
|
_COMMON_FILTER_TYPE_MAP = {
|
|
783
803
|
"DateField": "CommonDateFilterColumnAp",
|
|
784
804
|
"DateTimeField": "CommonDateFilterColumnAp",
|
|
@@ -804,32 +824,32 @@ _COMMON_FILTER_TYPE_MAP = {
|
|
|
804
824
|
"CheckBoxField": "CommonCheckBoxFilterColumnAp",
|
|
805
825
|
"BooleanField": "CommonCheckBoxFilterColumnAp",
|
|
806
826
|
}
|
|
807
|
-
# 引用属性子 key →
|
|
827
|
+
# 引用属性子 key → 中文Display名映射
|
|
808
828
|
_USER_REF_SUB_NAME_MAP = {
|
|
809
|
-
"number": "
|
|
810
|
-
"name": "
|
|
811
|
-
"phone": "
|
|
812
|
-
"email": "
|
|
813
|
-
"department": "
|
|
814
|
-
"position": "
|
|
829
|
+
"number": "Employee ID",
|
|
830
|
+
"name": "Full Name",
|
|
831
|
+
"phone": "Phone",
|
|
832
|
+
"email": "Email",
|
|
833
|
+
"department": "Department",
|
|
834
|
+
"position": "Position",
|
|
815
835
|
"id": "ID",
|
|
816
|
-
"picturefield":"
|
|
836
|
+
"picturefield":"Avatar",
|
|
817
837
|
}
|
|
818
838
|
_BASEDATA_REF_SUB_NAME_MAP = {
|
|
819
|
-
"number": "
|
|
820
|
-
"name": "
|
|
821
|
-
"description": "
|
|
839
|
+
"number": "Number",
|
|
840
|
+
"name": "Name",
|
|
841
|
+
"description": "Description",
|
|
822
842
|
"id": "ID",
|
|
823
843
|
}
|
|
824
844
|
_USER_FIELD_TYPES = {"UserField", "CreaterField", "ModifierField", "AuditorField"}
|
|
825
845
|
def _derive_ref_prop_sub_name(parent_field_type, sub_key):
|
|
826
|
-
"""
|
|
846
|
+
"""按父Field type返回子属性的中文Display名, No match则返回原 sub_key. """
|
|
827
847
|
if not sub_key:
|
|
828
848
|
return sub_key
|
|
829
849
|
if parent_field_type in _USER_FIELD_TYPES:
|
|
830
850
|
return _USER_REF_SUB_NAME_MAP.get(sub_key, sub_key)
|
|
831
851
|
return _BASEDATA_REF_SUB_NAME_MAP.get(sub_key, sub_key)
|
|
832
|
-
#
|
|
852
|
+
# Derive Scheme* subclass by FieldType
|
|
833
853
|
_SCHEME_FILTER_TYPE_MAP = {
|
|
834
854
|
"DateField": "SchemeDateFilterColumnAp",
|
|
835
855
|
"DateTimeField": "SchemeDateFilterColumnAp",
|
|
@@ -855,7 +875,7 @@ _SCHEME_FILTER_TYPE_MAP = {
|
|
|
855
875
|
}
|
|
856
876
|
def do_can_add_filter_column(cfg, token, form_number, parent_scope,
|
|
857
877
|
control_type=None, field_name=None, element_type=None):
|
|
858
|
-
"""
|
|
878
|
+
"""过滤Field添加预校验. 调用服务端 FilterColumnHandler.canAddFilterColumn.
|
|
859
879
|
|
|
860
880
|
Returns:
|
|
861
881
|
dict: { allowed: bool, reason?: str }
|
|
@@ -870,8 +890,8 @@ def do_can_add_filter_column(cfg, token, form_number, parent_scope,
|
|
|
870
890
|
def do_list_asst_types(cfg, token, form_number=None, element_key=None):
|
|
871
891
|
"""List assistant data categories globally or for an existing field context.
|
|
872
892
|
|
|
873
|
-
|
|
874
|
-
elementKey
|
|
893
|
+
With no target it returns the global category catalog for AssistantField creation.
|
|
894
|
+
Supplying both formNumber and elementKey preserves field-context parent filtering.
|
|
875
895
|
"""
|
|
876
896
|
payload = {}
|
|
877
897
|
if form_number:
|
|
@@ -932,7 +952,7 @@ def do_list_ref_layouts(cfg, token, form_number=None, element_key=None):
|
|
|
932
952
|
|
|
933
953
|
|
|
934
954
|
def _find_element_type_by_key(metadata, key):
|
|
935
|
-
"""
|
|
955
|
+
"""Return the type of any element published under an exact key."""
|
|
936
956
|
target = str(key or "").strip()
|
|
937
957
|
if not target:
|
|
938
958
|
return None
|
|
@@ -1151,7 +1171,7 @@ def do_get_form_config(cfg, token, form_id):
|
|
|
1151
1171
|
"""
|
|
1152
1172
|
return _api_call(cfg, token, "getFormConfig", {"formId": form_id}, method="GET")
|
|
1153
1173
|
def do_can_add_toolbar_item(cfg, token, form_number, toolbar_key=None, control_type=None):
|
|
1154
|
-
"""
|
|
1174
|
+
"""工具栏项添加预校验. 调用服务端 FilterColumnHandler.canAddToolbarItem.
|
|
1155
1175
|
|
|
1156
1176
|
Returns:
|
|
1157
1177
|
dict: { allowed: bool, reason?: str }
|
|
@@ -1163,17 +1183,17 @@ def do_can_add_toolbar_item(cfg, token, form_number, toolbar_key=None, control_t
|
|
|
1163
1183
|
def do_add_filter_column(cfg, token, form_number, field_key, parent_scope,
|
|
1164
1184
|
control_type=None, name=None, column_key=None,
|
|
1165
1185
|
after_column=None):
|
|
1166
|
-
"""
|
|
1186
|
+
"""快捷在过滤视图添加Field (form + commonfiltercolumn/schemefiltercolumn + add) .
|
|
1167
1187
|
|
|
1168
1188
|
Returns:
|
|
1169
1189
|
(form_id, [op])
|
|
1170
1190
|
"""
|
|
1171
1191
|
if parent_scope not in FILTER_VIEW_PARENT_KEYS:
|
|
1172
1192
|
raise ValueError(
|
|
1173
|
-
f"parentScope
|
|
1193
|
+
f"parentScope Invalid: '{parent_scope}', must be "
|
|
1174
1194
|
f"{sorted(FILTER_VIEW_PARENT_KEYS.keys())}"
|
|
1175
1195
|
)
|
|
1176
|
-
#
|
|
1196
|
+
# 强制规范化: 服务端真正识别无 ap 后缀版
|
|
1177
1197
|
parent_scope = _normalize_filter_parent_scope(parent_scope)
|
|
1178
1198
|
element_type = "schemefiltercolumn" if parent_scope == "schemefilterview" else "commonfiltercolumn"
|
|
1179
1199
|
|
|
@@ -1182,16 +1202,16 @@ def do_add_filter_column(cfg, token, form_number, field_key, parent_scope,
|
|
|
1182
1202
|
print(f"[addFilterColumn] formNumber={form_number} → formId={form_id}, parentScope={parent_scope}",
|
|
1183
1203
|
flush=True)
|
|
1184
1204
|
|
|
1185
|
-
# 2.
|
|
1205
|
+
# 2. 查Field以回填 name / 推导 controlType
|
|
1186
1206
|
field_meta = None
|
|
1187
|
-
parent_meta = None #
|
|
1188
|
-
sub_key = None #
|
|
1207
|
+
parent_meta = None # 引用属性场景下所属的父Field元信息
|
|
1208
|
+
sub_key = None # Sub key in reference attribute scenario
|
|
1189
1209
|
if field_key:
|
|
1190
1210
|
elements_result = do_list_elements(cfg, token, form_number, category="field")
|
|
1191
1211
|
if _is_api_success(elements_result):
|
|
1192
1212
|
elements_data = elements_result.get("data") or []
|
|
1193
1213
|
if "." in field_key:
|
|
1194
|
-
#
|
|
1214
|
+
# 引用属性场景 (parent.sub) : 定位父Field元信息
|
|
1195
1215
|
parent_selector, sub_key = field_key.split(".", 1)
|
|
1196
1216
|
parent_meta, parent_key = _lookup_field_meta(
|
|
1197
1217
|
elements_data, parent_selector, "addFilterColumn"
|
|
@@ -1224,7 +1244,7 @@ def do_add_filter_column(cfg, token, form_number, field_key, parent_scope,
|
|
|
1224
1244
|
default_name = field_key
|
|
1225
1245
|
field_type = ""
|
|
1226
1246
|
|
|
1227
|
-
# 3. 推导 controlType
|
|
1247
|
+
# 3. 推导 controlType (显式传入优先)
|
|
1228
1248
|
if not control_type:
|
|
1229
1249
|
if parent_scope == "schemefilterview":
|
|
1230
1250
|
control_type = _SCHEME_FILTER_TYPE_MAP.get(field_type, "SchemeFilterColumnAp")
|
|
@@ -1232,10 +1252,10 @@ def do_add_filter_column(cfg, token, form_number, field_key, parent_scope,
|
|
|
1232
1252
|
control_type = "FilterColumnAp"
|
|
1233
1253
|
else:
|
|
1234
1254
|
control_type = _COMMON_FILTER_TYPE_MAP.get(field_type, "CommonFilterColumnAp")
|
|
1235
|
-
print(f"[addFilterColumn]
|
|
1255
|
+
print(f"[addFilterColumn] Field '{field_key}' type '{field_type}' → controlType={control_type}",
|
|
1236
1256
|
flush=True)
|
|
1237
1257
|
|
|
1238
|
-
# 4.
|
|
1258
|
+
# 4. Build op
|
|
1239
1259
|
resolved_key = column_key or field_key
|
|
1240
1260
|
resolved_name = name or default_name
|
|
1241
1261
|
value = {
|
|
@@ -1271,7 +1291,7 @@ def _metadata_nodes(value, parent_key=""):
|
|
|
1271
1291
|
|
|
1272
1292
|
|
|
1273
1293
|
def _runtime_entity_property_keys(entity_type_result):
|
|
1274
|
-
"""
|
|
1294
|
+
"""Return queryable property keys from the full runtime entity graph."""
|
|
1275
1295
|
if not _is_api_success(entity_type_result):
|
|
1276
1296
|
return set()
|
|
1277
1297
|
document = entity_type_result.get("data")
|
|
@@ -1301,11 +1321,12 @@ def _runtime_entity_property_keys(entity_type_result):
|
|
|
1301
1321
|
|
|
1302
1322
|
|
|
1303
1323
|
def _composite_field_projections(metadata):
|
|
1304
|
-
"""
|
|
1324
|
+
"""Compile authored composite controls to their runtime-query properties.
|
|
1305
1325
|
|
|
1306
|
-
DateRangeField
|
|
1307
|
-
|
|
1308
|
-
|
|
1326
|
+
DateRangeField is one authored field but persists two DateProp values. List
|
|
1327
|
+
metadata is evaluated against the runtime entity, so binding the authored key
|
|
1328
|
+
produces FIELDNOTEXISTS000005. Keep the mapping here, beside list compilation,
|
|
1329
|
+
instead of asking the model to rediscover platform storage semantics.
|
|
1309
1330
|
"""
|
|
1310
1331
|
projections = {}
|
|
1311
1332
|
for node, _ in _metadata_nodes(metadata):
|
|
@@ -1324,14 +1345,14 @@ def _composite_field_projections(metadata):
|
|
|
1324
1345
|
else "header"
|
|
1325
1346
|
)
|
|
1326
1347
|
projections[logical_key] = [
|
|
1327
|
-
{"key": start_key, "name": f"{name}
|
|
1328
|
-
{"key": end_key, "name": f"{name}
|
|
1348
|
+
{"key": start_key, "name": f"{name}.Start", "type": "DateField", "ownerScope": owner_scope},
|
|
1349
|
+
{"key": end_key, "name": f"{name}.End", "type": "DateField", "ownerScope": owner_scope},
|
|
1329
1350
|
]
|
|
1330
1351
|
return projections
|
|
1331
1352
|
|
|
1332
1353
|
|
|
1333
1354
|
def _runtime_property_supports(field_key, runtime_keys):
|
|
1334
|
-
"""
|
|
1355
|
+
"""Accept exact runtime properties and reference sub-properties."""
|
|
1335
1356
|
key = str(field_key or "").strip()
|
|
1336
1357
|
return key in runtime_keys or ("." in key and key.split(".", 1)[0] in runtime_keys)
|
|
1337
1358
|
def _normalise_metadata_parent(value):
|
|
@@ -1365,6 +1386,74 @@ def _list_column_properties(column):
|
|
|
1365
1386
|
return properties
|
|
1366
1387
|
|
|
1367
1388
|
|
|
1389
|
+
def _compile_list_column_properties(rows, column):
|
|
1390
|
+
"""Use the actual column declaration, preserving the field binding and identity."""
|
|
1391
|
+
compiled, declarations, errors = {}, {}, []
|
|
1392
|
+
aliases = {"wrap": "AutoTextWrap", "自动换行": "AutoTextWrap", "换行": "AutoTextWrap",
|
|
1393
|
+
"align": "TextAlign", "文本对齐": "TextAlign", "对齐": "TextAlign",
|
|
1394
|
+
"汇总": "Summary", "零值显示": "ZeroShow", "列宽": "Width"}
|
|
1395
|
+
known = {"Summary", "ZeroShow", "AutoTextWrap", "TextAlign", "Width", "DisplayFormatString",
|
|
1396
|
+
"ColumnOrderAndFilter", "Hyperlink", "Visible", "Mask", "ShowStyle"}
|
|
1397
|
+
for requested in (_list_column_properties(column), column.get("properties") or {}):
|
|
1398
|
+
for selector, value in requested.items():
|
|
1399
|
+
original = selector
|
|
1400
|
+
declaration, error = _resolve_property_declaration(rows, selector)
|
|
1401
|
+
if error and str(selector).casefold() in aliases:
|
|
1402
|
+
selector = aliases[str(selector).casefold()]
|
|
1403
|
+
declaration, error = _resolve_property_declaration(rows, selector)
|
|
1404
|
+
if error:
|
|
1405
|
+
names = {str(row.get("propertyName") or ""): row for row in rows}
|
|
1406
|
+
near = difflib.get_close_matches(str(selector).casefold(), [key.casefold() for key in names], n=3, cutoff=0.45)
|
|
1407
|
+
error["data"] = {**error.get("data", {}), "requestedSelector": original,
|
|
1408
|
+
"suggestions": [{key: row.get(key) for key in ("propertyName", "displayName", "options")}
|
|
1409
|
+
for name, row in names.items() if name.casefold() in near],
|
|
1410
|
+
"capabilityLimitConfirmed": selector in known}
|
|
1411
|
+
errors.append(error)
|
|
1412
|
+
continue
|
|
1413
|
+
name = declaration["propertyName"]
|
|
1414
|
+
if name == "TextAlign" and isinstance(value, str):
|
|
1415
|
+
value = {"左": "left", "左对齐": "left", "右": "right", "右对齐": "right", "居中": "center"}.get(value, value)
|
|
1416
|
+
if name in {"Key", "Id", "_Type_", "ListFieldId", "ParentId", "Index", "DataType", "SeqColumnType"}:
|
|
1417
|
+
errors.append(_list_configuration_preflight_failure(
|
|
1418
|
+
"LIST_COLUMN_IDENTITY_PROPERTY_FORBIDDEN", "Use fieldKey/entity for binding; column identity is not a presentation property", propertyName=name))
|
|
1419
|
+
continue
|
|
1420
|
+
if isinstance(value, bool) and declaration.get("editorType") != "checkbox" and any(
|
|
1421
|
+
isinstance(option.get("id"), (int, float)) and not isinstance(option.get("id"), bool)
|
|
1422
|
+
for option in declaration.get("options") or []):
|
|
1423
|
+
errors.append(_list_configuration_preflight_failure(
|
|
1424
|
+
"PROPERTY_VALUE_INVALID", "A boolean is not a numeric enum option", propertyName=name))
|
|
1425
|
+
continue
|
|
1426
|
+
if isinstance(value, (dict, list)) and declaration.get("editorType") not in {"dimension", "mcombo"}:
|
|
1427
|
+
errors.append(_list_configuration_preflight_failure(
|
|
1428
|
+
"PROPERTY_REQUIRES_COMPLEX_SETTER", "Use the dedicated complex-property setter for structured values", propertyName=name))
|
|
1429
|
+
continue
|
|
1430
|
+
# The generic mcombo declaration omits list-only flags that are present
|
|
1431
|
+
# in the persisted value (notably default/lookup). Reuse only observed IDs.
|
|
1432
|
+
if name == "Visible" and declaration.get("editorType") == "mcombo":
|
|
1433
|
+
options = declaration.get("options") or []
|
|
1434
|
+
existing_ids = {str(option.get("id")) for option in options}
|
|
1435
|
+
observed = str(declaration.get("currentValue") or "").split(",")
|
|
1436
|
+
declaration = {**declaration, "options": [*options, *[
|
|
1437
|
+
{"id": flag.strip(), "name": flag.strip()} for flag in observed
|
|
1438
|
+
if flag.strip() and flag.strip() not in existing_ids]]}
|
|
1439
|
+
values, schema, error = _compile_simple_property_map([declaration], {selector: value})
|
|
1440
|
+
if error:
|
|
1441
|
+
errors.extend((error.get("data") or {}).get("errors") or [error])
|
|
1442
|
+
continue
|
|
1443
|
+
if name in compiled and not _property_values_equal(compiled[name], values[name], schema[name]):
|
|
1444
|
+
errors.append(_list_configuration_preflight_failure(
|
|
1445
|
+
"LIST_COLUMN_PROPERTY_CONFLICT", "Conflicting column options and properties", propertyName=name))
|
|
1446
|
+
continue
|
|
1447
|
+
compiled.update(values)
|
|
1448
|
+
declarations.update(schema)
|
|
1449
|
+
if errors:
|
|
1450
|
+
return None, None, {"status": False,
|
|
1451
|
+
"errorCode": errors[0]["errorCode"] if len(errors) == 1 else "PROPERTY_PREFLIGHT_FAILED",
|
|
1452
|
+
"message": "Column property validation failed; all invalid selectors are listed",
|
|
1453
|
+
"data": {"writeAttempted": False, "errors": errors}}
|
|
1454
|
+
return compiled, declarations, None
|
|
1455
|
+
|
|
1456
|
+
|
|
1368
1457
|
def _list_configuration_preflight_failure(code, message, **data):
|
|
1369
1458
|
return {
|
|
1370
1459
|
"status": False,
|
|
@@ -1414,7 +1503,10 @@ def do_configure_list(
|
|
|
1414
1503
|
for index, column in enumerate(configuration.get("columns") or []):
|
|
1415
1504
|
if (
|
|
1416
1505
|
not isinstance(column, dict)
|
|
1417
|
-
or set(column) - {"fieldKey", "entity", "order", "hyperlink", "displayName"}
|
|
1506
|
+
or set(column) - {"fieldKey", "entity", "order", "hyperlink", "displayName", "properties"}
|
|
1507
|
+
or "properties" in column and (not isinstance(column["properties"], dict) or not column["properties"])
|
|
1508
|
+
or "hyperlink" in column and not isinstance(column["hyperlink"], bool)
|
|
1509
|
+
or "order" in column and column["order"] not in {"ASC", "DESC", "NotOrder"}
|
|
1418
1510
|
or not isinstance(column.get("fieldKey"), str)
|
|
1419
1511
|
or not column["fieldKey"].strip()
|
|
1420
1512
|
or "displayName" in column and (
|
|
@@ -1423,7 +1515,7 @@ def do_configure_list(
|
|
|
1423
1515
|
):
|
|
1424
1516
|
return _list_configuration_preflight_failure(
|
|
1425
1517
|
"LIST_CONFIGURATION_INVALID",
|
|
1426
|
-
f"columns[{index}] requires fieldKey and accepts
|
|
1518
|
+
f"columns[{index}] requires fieldKey and accepts entity, order, boolean hyperlink, non-empty displayName and a non-empty properties map",
|
|
1427
1519
|
)
|
|
1428
1520
|
columns = [
|
|
1429
1521
|
dict(column) for column in configuration.get("columns") or []
|
|
@@ -1487,7 +1579,7 @@ def do_configure_list(
|
|
|
1487
1579
|
):
|
|
1488
1580
|
return _list_configuration_preflight_failure(
|
|
1489
1581
|
"LIST_CONFIGURATION_BASELINE_FAILED",
|
|
1490
|
-
"
|
|
1582
|
+
"could not read design and runtime metadata before list mutation",
|
|
1491
1583
|
failedReads={
|
|
1492
1584
|
name: item
|
|
1493
1585
|
for name, item in (
|
|
@@ -1509,7 +1601,7 @@ def do_configure_list(
|
|
|
1509
1601
|
if not runtime_keys:
|
|
1510
1602
|
return _list_configuration_preflight_failure(
|
|
1511
1603
|
"LIST_CONFIGURATION_RUNTIME_SCHEMA_INVALID",
|
|
1512
|
-
"getEntityType
|
|
1604
|
+
"getEntityType returned no queryable runtime properties",
|
|
1513
1605
|
formNumber=form_number,
|
|
1514
1606
|
)
|
|
1515
1607
|
fields = {
|
|
@@ -1557,6 +1649,11 @@ def do_configure_list(
|
|
|
1557
1649
|
|
|
1558
1650
|
def _resolve_field_selector(raw_selector, entity_selector=""):
|
|
1559
1651
|
selector = str(raw_selector or "").strip()
|
|
1652
|
+
# Preserve exact field captions first; otherwise honor an explicit header prefix.
|
|
1653
|
+
if not entity_selector and selector.startswith("表头") and not any(
|
|
1654
|
+
str(row.get("name") or "").strip() == selector for row in fields.values()):
|
|
1655
|
+
selector = selector[2:].lstrip(" .::")
|
|
1656
|
+
entity_selector = "header"
|
|
1560
1657
|
entity_keys = _entity_candidates(entity_selector) if entity_selector else None
|
|
1561
1658
|
if entity_selector and not entity_keys:
|
|
1562
1659
|
return None
|
|
@@ -1678,7 +1775,7 @@ def do_configure_list(
|
|
|
1678
1775
|
if unresolved_selectors:
|
|
1679
1776
|
return _list_configuration_preflight_failure(
|
|
1680
1777
|
"LIST_CONFIGURATION_FIELD_AMBIGUOUS_OR_NOT_FOUND",
|
|
1681
|
-
"
|
|
1778
|
+
"list field selectors did not resolve to one exact field and entity",
|
|
1682
1779
|
unresolvedSelectors=sorted(set(unresolved_selectors)),
|
|
1683
1780
|
candidates=[
|
|
1684
1781
|
{
|
|
@@ -1690,6 +1787,11 @@ def do_configure_list(
|
|
|
1690
1787
|
],
|
|
1691
1788
|
)
|
|
1692
1789
|
|
|
1790
|
+
resolved_keys = [column["fieldKey"] for column in normalized_columns]
|
|
1791
|
+
if len(resolved_keys) != len(set(resolved_keys)):
|
|
1792
|
+
return _list_configuration_preflight_failure(
|
|
1793
|
+
"LIST_COLUMN_DUPLICATE", "Use one entry per field and combine its properties", fieldKeys=resolved_keys)
|
|
1794
|
+
|
|
1693
1795
|
composite_mappings = {}
|
|
1694
1796
|
expanded_columns = []
|
|
1695
1797
|
unsupported_composites = []
|
|
@@ -1743,7 +1845,7 @@ def do_configure_list(
|
|
|
1743
1845
|
if unsupported_composites:
|
|
1744
1846
|
return _list_configuration_preflight_failure(
|
|
1745
1847
|
"LIST_CONFIGURATION_COMPOSITE_OPTION_UNSUPPORTED",
|
|
1746
|
-
"
|
|
1848
|
+
"composite fields cannot have one displayName or be quick-search, hyperlink, or sort targets",
|
|
1747
1849
|
fields=sorted(set(unsupported_composites)),
|
|
1748
1850
|
)
|
|
1749
1851
|
configuration = {
|
|
@@ -1774,7 +1876,7 @@ def do_configure_list(
|
|
|
1774
1876
|
if runtime_missing:
|
|
1775
1877
|
return _list_configuration_preflight_failure(
|
|
1776
1878
|
"LIST_CONFIGURATION_RUNTIME_FIELD_UNRESOLVED",
|
|
1777
|
-
"
|
|
1879
|
+
"list fields are absent from the runtime entity schema",
|
|
1778
1880
|
missingRuntimeFields=runtime_missing,
|
|
1779
1881
|
compositeMappings=composite_mappings,
|
|
1780
1882
|
)
|
|
@@ -1786,8 +1888,15 @@ def do_configure_list(
|
|
|
1786
1888
|
}
|
|
1787
1889
|
structural_ops = []
|
|
1788
1890
|
property_targets = []
|
|
1891
|
+
expected_columns = {}
|
|
1892
|
+
column_errors = []
|
|
1893
|
+
column_declarations = {}
|
|
1894
|
+
property_schemas = {}
|
|
1895
|
+
metadata_columns = {str(node.get("Key") or ""): node for node, _ in _metadata_nodes(metadata_document)
|
|
1896
|
+
if str(node.get("_Type_") or "").endswith("ListColumnAp")}
|
|
1789
1897
|
|
|
1790
|
-
|
|
1898
|
+
|
|
1899
|
+
# Repair legacy list bindings that used an authored composite key directly.
|
|
1791
1900
|
for logical_key in composite_mappings:
|
|
1792
1901
|
existing = by_field.get(logical_key)
|
|
1793
1902
|
if existing is not None:
|
|
@@ -1816,6 +1925,30 @@ def do_configure_list(
|
|
|
1816
1925
|
field_key = str(column.get("fieldKey") or "").strip()
|
|
1817
1926
|
existing = by_field.get(field_key)
|
|
1818
1927
|
requested_props = _list_column_properties(column)
|
|
1928
|
+
if column.get("properties"):
|
|
1929
|
+
schema_key = str((existing or {}).get("key") or "")
|
|
1930
|
+
if not schema_key:
|
|
1931
|
+
wanted_type = _LISTCOLUMN_TYPE_MAP.get(str(fields[field_key].get("type") or ""), "ListColumnAp")
|
|
1932
|
+
schema_key = next((key for key, node in metadata_columns.items() if node.get("_Type_") == wanted_type), "")
|
|
1933
|
+
if schema_key:
|
|
1934
|
+
if schema_key not in property_schemas:
|
|
1935
|
+
property_schemas[schema_key] = do_get_property_schema(cfg, token, form_number, schema_key)
|
|
1936
|
+
schema = property_schemas[schema_key]
|
|
1937
|
+
if not _is_api_success(schema):
|
|
1938
|
+
return _list_configuration_preflight_failure("LIST_COLUMN_SCHEMA_FAILED", "Cannot read the column property contract", fieldKey=field_key, schema=schema)
|
|
1939
|
+
requested_props, declarations, rejection = _compile_list_column_properties(schema.get("data") or [], column)
|
|
1940
|
+
if rejection:
|
|
1941
|
+
column_errors.append(_list_configuration_preflight_failure("LIST_COLUMN_PROPERTY_INVALID", "Column properties failed preflight",
|
|
1942
|
+
fieldKey=field_key, columnKey=schema_key, propertyError=rejection,
|
|
1943
|
+
recovery={"command": "getPropertySchema", "arguments": ["--formNumber", form_number, "--elementKey", schema_key],
|
|
1944
|
+
"scope": "this column only; do not query form structure or global help"},
|
|
1945
|
+
completion={"outcome": "failed_preflight", "requiresArtifactFinalization": False,
|
|
1946
|
+
"nextAction": "respond_unmet_property" if any((item.get("data") or {}).get("capabilityLimitConfirmed")
|
|
1947
|
+
for item in (rejection.get("data") or {}).get("errors", [])) else "repair_only_reported_input",
|
|
1948
|
+
"message": "Zero writes. Do not render or reuse a card, launch source sync, or call output_check."}))
|
|
1949
|
+
continue
|
|
1950
|
+
column_declarations[field_key] = declarations
|
|
1951
|
+
expected_columns[field_key] = requested_props
|
|
1819
1952
|
if existing is None:
|
|
1820
1953
|
field_meta = fields[field_key]
|
|
1821
1954
|
column_type = _LISTCOLUMN_TYPE_MAP.get(
|
|
@@ -1837,8 +1970,8 @@ def do_configure_list(
|
|
|
1837
1970
|
},
|
|
1838
1971
|
"value": value,
|
|
1839
1972
|
})
|
|
1840
|
-
if requested_props:
|
|
1841
|
-
property_targets.append((f"listcol_{field_key}", requested_props, True))
|
|
1973
|
+
if requested_props or column.get("properties"):
|
|
1974
|
+
property_targets.append((f"listcol_{field_key}", requested_props, True, column))
|
|
1842
1975
|
else:
|
|
1843
1976
|
column_key = str(existing.get("key") or f"listcol_{field_key}").strip()
|
|
1844
1977
|
if requested_props:
|
|
@@ -1848,13 +1981,20 @@ def do_configure_list(
|
|
|
1848
1981
|
current_data = (current.get("data") or {}) if _is_api_success(current) else {}
|
|
1849
1982
|
pending = {
|
|
1850
1983
|
name: value for name, value in requested_props.items()
|
|
1851
|
-
if
|
|
1852
|
-
name,
|
|
1853
|
-
|
|
1854
|
-
) != value
|
|
1984
|
+
if not _property_values_equal(
|
|
1985
|
+
value, current_data.get(name, current_data.get(name[:1].lower() + name[1:])),
|
|
1986
|
+
column_declarations.get(field_key, {}).get(name))
|
|
1855
1987
|
}
|
|
1856
1988
|
if pending:
|
|
1857
|
-
property_targets.append((column_key, pending, False))
|
|
1989
|
+
property_targets.append((column_key, pending, False, column))
|
|
1990
|
+
|
|
1991
|
+
if column_errors:
|
|
1992
|
+
details = [{**item["data"]} for item in column_errors]
|
|
1993
|
+
result = column_errors[0]
|
|
1994
|
+
result["data"]["columnErrors"] = details
|
|
1995
|
+
if any((item.get("completion") or {}).get("nextAction") == "respond_unmet_property" for item in details):
|
|
1996
|
+
result["data"]["completion"]["nextAction"] = "respond_unmet_property"
|
|
1997
|
+
return result
|
|
1858
1998
|
|
|
1859
1999
|
filter_specs = (
|
|
1860
2000
|
[(key, "fastsearchgridview") for key in configuration.get("quickSearch") or []]
|
|
@@ -1907,10 +2047,32 @@ def do_configure_list(
|
|
|
1907
2047
|
"data": {"structuralResult": structural_result},
|
|
1908
2048
|
}
|
|
1909
2049
|
|
|
2050
|
+
created_columns = {}
|
|
2051
|
+
if any(created and column.get("properties") for _key, _props, created, column in property_targets):
|
|
2052
|
+
created_schema = do_get_form_schema(cfg, token, form_id)
|
|
2053
|
+
if not _is_api_success(created_schema):
|
|
2054
|
+
return {"status": False, "errorCode": "LIST_COLUMN_SCHEMA_FAILED", "message": "Cannot resolve created column identities",
|
|
2055
|
+
"data": {"writeAttempted": True, "structuralResult": structural_result}}
|
|
2056
|
+
created_columns = {str(item.get("listFieldId") or ""): item for item in (created_schema.get("data") or {}).get("listColumns") or []}
|
|
1910
2057
|
property_results = []
|
|
1911
|
-
for element_key, properties, created in property_targets:
|
|
1912
|
-
if created:
|
|
2058
|
+
for element_key, properties, created, column in property_targets:
|
|
2059
|
+
if created and not column.get("properties"):
|
|
1913
2060
|
continue
|
|
2061
|
+
if created:
|
|
2062
|
+
element_key = str((created_columns.get(column["fieldKey"]) or {}).get("key") or "")
|
|
2063
|
+
if not element_key:
|
|
2064
|
+
return {"status": False, "errorCode": "LIST_COLUMN_IDENTITY_UNRESOLVED", "message": "Created column has no verified persisted key",
|
|
2065
|
+
"data": {"writeAttempted": True, "structuralResult": structural_result, "fieldKey": column["fieldKey"]}}
|
|
2066
|
+
schema = do_get_property_schema(cfg, token, form_number, element_key)
|
|
2067
|
+
if not _is_api_success(schema):
|
|
2068
|
+
return {"status": False, "errorCode": "LIST_COLUMN_SCHEMA_FAILED", "message": "Column created but its property contract could not be read",
|
|
2069
|
+
"data": {"writeAttempted": True, "structuralResult": structural_result, "elementKey": element_key}}
|
|
2070
|
+
properties, declarations, rejection = _compile_list_column_properties(schema.get("data") or [], column)
|
|
2071
|
+
if rejection:
|
|
2072
|
+
return {"status": False, "errorCode": "LIST_COLUMN_PROPERTY_INVALID", "message": "Column created; unsupported properties were not written",
|
|
2073
|
+
"data": {"writeAttempted": True, "structuralResult": structural_result, "elementKey": element_key, "propertyError": rejection}}
|
|
2074
|
+
expected_columns[column["fieldKey"]] = properties
|
|
2075
|
+
column_declarations[column["fieldKey"]] = declarations
|
|
1914
2076
|
result = do_set_properties(cfg, token, form_number, element_key, properties)
|
|
1915
2077
|
property_results.append({"elementKey": element_key, "result": result})
|
|
1916
2078
|
if not _is_api_success(result):
|
|
@@ -1939,7 +2101,7 @@ def do_configure_list(
|
|
|
1939
2101
|
errors.append(f"list column missing for field {field_key}")
|
|
1940
2102
|
continue
|
|
1941
2103
|
element_key = str(actual_column.get("key") or f"listcol_{field_key}").strip()
|
|
1942
|
-
expected = _list_column_properties(column)
|
|
2104
|
+
expected = expected_columns.get(field_key, _list_column_properties(column))
|
|
1943
2105
|
evidence = {"fieldKey": field_key, "columnKey": element_key, "properties": {}}
|
|
1944
2106
|
column_evidence.append(evidence)
|
|
1945
2107
|
if not expected:
|
|
@@ -1955,9 +2117,9 @@ def do_configure_list(
|
|
|
1955
2117
|
actual_value = actual.get(prop_name)
|
|
1956
2118
|
if actual_value is None:
|
|
1957
2119
|
actual_value = actual.get(prop_name[:1].lower() + prop_name[1:])
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
if
|
|
2120
|
+
matched = _property_values_equal(expected_value, actual_value, column_declarations.get(field_key, {}).get(prop_name))
|
|
2121
|
+
evidence["properties"][prop_name] = {"requested": expected_value, "actual": actual_value, "matched": matched}
|
|
2122
|
+
if not matched:
|
|
1961
2123
|
errors.append(
|
|
1962
2124
|
f"{element_key}.{prop_name}: expected {expected_value!r}, got {actual_value!r}"
|
|
1963
2125
|
)
|
|
@@ -1970,18 +2132,22 @@ def do_configure_list(
|
|
|
1970
2132
|
errors.append(f"{parent_scope} missing field {field_key}")
|
|
1971
2133
|
for logical_key in composite_mappings:
|
|
1972
2134
|
if logical_key in after_by_field:
|
|
1973
|
-
errors.append(f"
|
|
2135
|
+
errors.append(f"legacy composite list binding still present for {logical_key}")
|
|
1974
2136
|
if _filter_column_present(final_metadata, logical_key, "filtergridview"):
|
|
1975
|
-
errors.append(f"
|
|
2137
|
+
errors.append(f"legacy composite filter binding still present for {logical_key}")
|
|
1976
2138
|
|
|
1977
2139
|
configured_runtime_keys = set(after_by_field).intersection(requested_keys)
|
|
1978
|
-
configured_runtime_keys.update(
|
|
2140
|
+
configured_runtime_keys.update(
|
|
2141
|
+
field_key for field_key, _ in filter_specs
|
|
2142
|
+
)
|
|
1979
2143
|
invalid_runtime_bindings = sorted(
|
|
1980
2144
|
key for key in configured_runtime_keys
|
|
1981
2145
|
if not _runtime_property_supports(key, runtime_keys)
|
|
1982
2146
|
)
|
|
1983
2147
|
if invalid_runtime_bindings:
|
|
1984
|
-
errors.append(
|
|
2148
|
+
errors.append(
|
|
2149
|
+
f"runtime entity does not expose list fields {invalid_runtime_bindings}"
|
|
2150
|
+
)
|
|
1985
2151
|
|
|
1986
2152
|
verified = not errors and _is_api_success(schema_after) and _is_api_success(metadata_after)
|
|
1987
2153
|
return {
|
|
@@ -2013,21 +2179,22 @@ def do_configure_list(
|
|
|
2013
2179
|
"scope": "requested_list_configuration",
|
|
2014
2180
|
"outcome": "verified" if structural_ops or property_results else "verified_noop",
|
|
2015
2181
|
"requiresArtifactFinalization": bool(structural_ops or property_results),
|
|
2016
|
-
"
|
|
2017
|
-
|
|
2018
|
-
|
|
2182
|
+
"nextAction": "deliver_verified_change" if structural_ops or property_results else "respond_no_change",
|
|
2183
|
+
"message": ("The changed configuration is independently verified. Complete its returned delivery once; do not rediscover or reverify."
|
|
2184
|
+
if structural_ops or property_results else
|
|
2185
|
+
"No change: all requested values are independently verified. Respond directly; do not render or reuse cards, launch source sync, or call output_check."),
|
|
2019
2186
|
}} if verified else {}),
|
|
2020
2187
|
},
|
|
2021
2188
|
}
|
|
2022
2189
|
def _filter_column_key_candidates(column_key):
|
|
2023
|
-
"""生成过滤列 columnKey
|
|
2190
|
+
"""生成过滤列 columnKey 的候选集, 覆盖两套命名规则:
|
|
2024
2191
|
|
|
2025
|
-
- addFilter
|
|
2026
|
-
- buildMeta
|
|
2192
|
+
- addFilter 新建列: columnKey == fieldKey (无前缀, 如 billno / kdtest_wens_recname)
|
|
2193
|
+
- buildMeta 继承/模板列: columnKey == 'col' + 字段标识 (如 colname / colnumber / colbillno)
|
|
2027
2194
|
|
|
2028
|
-
上层 Agent
|
|
2029
|
-
与真实继承列 key
|
|
2030
|
-
|
|
2195
|
+
上层 Agent 删除继承字段时通常只知道字段标识 (name/number/billno), 传入裸 key,
|
|
2196
|
+
与真实继承列 key (colXXX) 不匹配, 服务端幂等 no-op 造成假成功. 这里一次性生成
|
|
2197
|
+
候选集, 由 remove 幂等无害特性覆盖两套规则. 返回去重且保序的列表.
|
|
2031
2198
|
"""
|
|
2032
2199
|
key = str(column_key or "").strip()
|
|
2033
2200
|
candidates = []
|
|
@@ -2041,29 +2208,29 @@ def _filter_column_key_candidates(column_key):
|
|
|
2041
2208
|
if key:
|
|
2042
2209
|
_add("col" + key)
|
|
2043
2210
|
if key.startswith("col"):
|
|
2044
|
-
# 上层可能已传入 col
|
|
2211
|
+
# 上层可能已传入 col 前缀形式; 兜底补裸 key
|
|
2045
2212
|
_add(key[3:])
|
|
2046
2213
|
return candidates
|
|
2047
2214
|
def do_remove_filter_column(cfg, token, form_number, column_key, parent_scope="filtergridview"):
|
|
2048
|
-
"""
|
|
2215
|
+
"""删除过滤Field (快捷命令) .
|
|
2049
2216
|
|
|
2050
|
-
对 columnKey
|
|
2051
|
-
remove op
|
|
2052
|
-
remove 对不存在的 key
|
|
2217
|
+
对 columnKey 生成多候选 (原始 / col 前缀 / 去 col 前缀), 一次 modifyMeta 提交多个
|
|
2218
|
+
remove op, 覆盖新建列 (key=fieldKey) 与继承列 (key=col+fieldKey) 两套命名规则;
|
|
2219
|
+
remove 对不存在的 key 幂等无害, 不匹配的候选为 no-op.
|
|
2053
2220
|
|
|
2054
2221
|
Returns:
|
|
2055
2222
|
(form_id, [op, ...])
|
|
2056
2223
|
"""
|
|
2057
2224
|
if parent_scope not in FILTER_VIEW_PARENT_KEYS:
|
|
2058
2225
|
raise ValueError(
|
|
2059
|
-
f"parentScope
|
|
2226
|
+
f"parentScope Invalid: '{parent_scope}', must be {sorted(FILTER_VIEW_PARENT_KEYS.keys())}"
|
|
2060
2227
|
)
|
|
2061
|
-
#
|
|
2228
|
+
# 强制规范化: 服务端识别无 ap 后缀版
|
|
2062
2229
|
parent_scope = _normalize_filter_parent_scope(parent_scope)
|
|
2063
2230
|
if parent_scope == "schemefilterview":
|
|
2064
2231
|
print(
|
|
2065
|
-
"[removeFilterColumn][WARN]
|
|
2066
|
-
"
|
|
2232
|
+
"[removeFilterColumn][WARN] 正在删除Scheme filter视图 (schemefilterview) 下的过滤Field, "
|
|
2233
|
+
"这可能破坏已保存方案的过滤条件, 请确认已与使用方沟通. ",
|
|
2067
2234
|
flush=True,
|
|
2068
2235
|
)
|
|
2069
2236
|
element_type = "schemefiltercolumn" if parent_scope == "schemefilterview" else "commonfiltercolumn"
|
|
@@ -2071,7 +2238,7 @@ def do_remove_filter_column(cfg, token, form_number, column_key, parent_scope="f
|
|
|
2071
2238
|
candidates = _filter_column_key_candidates(column_key)
|
|
2072
2239
|
print(
|
|
2073
2240
|
f"[removeFilterColumn] columnKey='{column_key}' → 候选key={candidates} "
|
|
2074
|
-
f"
|
|
2241
|
+
f"(覆盖新建列key=fieldKey与继承列key=col+fieldKey两套规则)",
|
|
2075
2242
|
flush=True,
|
|
2076
2243
|
)
|
|
2077
2244
|
ops = [
|