@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.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
  3. package/skills/cosmic-build/SKILL.md +24 -9
  4. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
  5. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
  6. package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
  7. package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
  8. package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
  9. package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
  10. package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
  11. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
  12. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
  13. package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
  14. package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
  15. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
  16. package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
  17. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
  18. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
  19. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
  20. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
  21. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
  22. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
  25. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
  26. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
  27. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
  28. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
  29. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
  30. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
  31. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
  32. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
  33. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
  34. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
  35. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
  36. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
  37. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
  38. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
  39. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
  40. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
  41. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
  42. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
  43. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
  44. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
  45. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
  46. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
  47. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
  48. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
  49. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
  50. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
  51. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
  52. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
  53. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
  54. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
  55. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
  56. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
  57. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
  58. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
  59. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
  60. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
  61. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
  62. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
  63. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
  64. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
  65. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
  66. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
  67. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
  68. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
  69. package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
  70. package/skills/cosmic-build/references/compile-guide.md +36 -1
  71. package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
  72. package/skills/cosmic-build/references/delivery.md +16 -2
  73. package/skills/cosmic-build/references/execution-contract.md +15 -3
  74. package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
  75. package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
  76. package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
  77. package/skills/cosmic-build/references/report-filter-contract.md +53 -15
  78. package/skills/cosmic-build/references/sdk-inspection.md +4 -1
  79. package/skills/cosmic-build/references/type-registry.json +26 -17
  80. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
  81. package/skills/cosmic-build/scripts/_http_client.py +3 -0
  82. package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
  83. package/skills/cosmic-build/scripts/configure_list.py +112 -0
  84. package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
  85. package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
  86. package/skills/cosmic-build/scripts/execution_binding.py +25 -0
  87. package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
  88. package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
  89. package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
  90. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
  91. package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
  92. package/skills/cosmic-build/scripts/register_plugins.py +27 -10
  93. package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
  94. package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
  95. package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
  96. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
  97. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
  98. package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
  99. package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
  100. package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
  101. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
  102. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
  103. package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
  104. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
  105. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
  106. package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
  107. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
  108. package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
  109. package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
  110. package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
  111. package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
  112. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
  113. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
  114. package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
  115. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
  116. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
  117. package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
  118. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
  119. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
  120. package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
  121. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
  122. package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
  123. package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
  124. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
  125. package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
  126. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
  127. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
  128. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
  129. package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
  130. package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
  131. package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
  132. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
  133. package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
  134. package/skills/cosmic-build/scripts/test_environment.py +67 -1
  135. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
  136. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
  138. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
  139. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
  143. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
  144. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
  145. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
  146. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
  147. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
  177. package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
  178. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
  179. package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
  180. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
  181. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
  182. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
  183. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
  184. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
  185. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
  193. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
  194. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
  196. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
  197. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
  198. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
  199. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
  200. package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
  201. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
  202. package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
  203. package/skills/cosmic-dev-best-practices/references/README.md +2 -2
  204. package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
  205. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
  206. package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
  207. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
  208. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
  209. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
  210. package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
  211. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
  212. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
  213. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
  214. package/skills/erp-analysis/references/delivery-contract.md +2 -1
  215. package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
  216. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
  217. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
  218. 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 do_get_properties, do_list_elements, do_set_properties
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
- # ─── 列类型映射常量(单源同步 list_column/compile.py) ────────────────────────
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;未命中时按平台固定长度前缀截断(fieldKey>21 截尾)
96
- 做最长前缀回退,回读平台真实 key。返回 (field_meta_or_None, resolved_field_key),
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}] 字段 '{field_key}' 未精确命中,"
119
- f"回退到平台真实 key '{truncated}'(平台静默截断)",
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
- """快捷在列表视图添加一列(form+listcolumn+add)。
149
+ """快捷在列表视图添加一列 (form+listcolumn+add) .
146
150
 
147
- - 自动解析 formNumber → formId
148
- - 自动查字段类型并映射 columnType(DecimalField → DecimalListColumnAp 等)
149
- - 自动取字段 name 作为列名(可用 --name 覆盖)
150
- - column_key 缺省为 `listcol_{field_key}`
151
- - 自动探测列表是否已存在同 listFieldId 的列,存在则报错(防重复)
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. 查询字段列表,定位目标字段类型(用于自动映射 columnType / 回填 name)
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"查询字段列表失败: {_extract_error_message(elements_result)}")
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"表单 '{form_number}' 中未找到字段 key='{field_key}';请先确认字段已存在或先 addField"
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] 字段 '{field_key}' 类型 '{field_type}' → columnType={resolved_column_type}",
182
+ f"[addListColumn] Field '{field_key}' type '{field_type}' → columnType={resolved_column_type}",
179
183
  flush=True,
180
184
  )
181
185
 
182
- # 4. 防重复:检查列表是否已有同 listFieldId 的列
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"列表已存在绑定字段 '{field_key}' 的列 (key='{col.get('key')}', name='{col.get('name')}');"
190
- f"如需修改请用 modify,禁止重复 add"
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] ⚠️ 预检 listColumns 异常: {e}(继续执行,由服务端兜底校验)", flush=True)
199
+ # schema 查询failed不阻塞 (服务端会兜底校验) , 仅告警
200
+ print(f"[addListColumn] ⚠️ Pre-check listColumns exception: {e} (继续执行, 由服务端兜底校验) ", flush=True)
197
201
 
198
- # 5. 构造 op
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;命中时 parent_key 可能为 None(根节点下直挂)。
231
- 用于 addButton / addWidget 的前置查重,避免服务端 add 非幂等导致的重复记录。
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
- - 新式:listLayout.children 里含 key=tbmain, type=ToolbarAp
253
- - 旧式:listLayout.children 里含 key=_toolbar_, type=ToolbarAp(单据/基础资料模板常见)
255
+ 兼容两种苍穹列表模板:
256
+ - 新式: listLayout.children 里含 key=tbmain, type=ToolbarAp
257
+ - 旧式: listLayout.children 里含 key=_toolbar_, type=ToolbarAp (单据/基础资料模板常见)
254
258
 
255
- 返回首个命中的 key;未命中返回 None。
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
- """返回平台实际存储的大小写形式的元素 key。
274
-
275
- 平台在写入时会把元素 key 转为小写。addButton 原先用调用方给的大小写写入按钮,
276
- 随后又用同样的大小写调 setProperties(OperationKey),而平台里存的是
277
- bar_oa_callphone,于是查找报「未找到元素」,留下一个已创建但没绑定任何操作
278
- (OperationKey 为空)的孤儿按钮,命令本身还报失败。写入与后续绑定必须用同一个
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
- """解析一个已有操作 key/显示名;无匹配时保留为新语义 key。"""
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
- """一条命令完成加按钮+绑定操作(编排 addOperation + modifyMeta(add) + setProperties(OperationKey))。
325
+ """一条命令completed加Button+绑定操作 (编排 addOperation + modifyMeta(add ) + setProperties(OperationKey)) .
320
326
 
321
- 返回:dict(success/formId/modelType/buttonType/buttonKey/parentScope/operationKey/modifyMeta/setProperties)
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
- # 平台以小写存储 operationKey,因此在用它派生按钮 key、绑定操作和断言操作存在之前
326
- # 先把身份解析成存储形式。否则绑定混合大小写的 key 会得到一个没绑上操作的按钮,
327
- # 而 operationPresent 检查永远不可能通过。
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
- # 标准化 target_view
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',当前值={target_view}")
344
- # 公开写命令复用 CLI 已证明的精确表单/应用元组;仅内部调用保留回退。
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"查询表单失败: {_extract_error_message(forms_result)}")
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"未找到编码为 '{form_number}' 的表单")
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. 判定按钮承载方式(button_type 显式传入优先)
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,强制 BarItem;仅 BillFormModel / BaseFormModel 具备 listMeta
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} 无 listMeta"
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(传入={button_type} 忽略)",
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] ⚠️ 未识别 modelType={model_type},默认使用 BarItem", flush=True)
397
+ print(f"[addButton] ⚠️ Unrecognized modelType={model_type}, default使用 BarItem", flush=True)
389
398
 
390
- # 3. 生成按钮 key & parentScope
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
- # 列表工具栏容器自动探测:兼容 tbmain / _toolbar_ 两种模板。
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] ℹ️ 自动探测到列表工具栏容器 key={detected}(旧式模板),已替代默认 tbmain",
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 顶层找到 ToolbarAp 子节点,回退 parentScope=tbmain;"
414
- f"若落位异常请手动 --parentScope 指定真实容器 key", flush=True)
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(分录工具栏按钮)必须通过 --parentScope 显式指定分录工具栏 key,"
424
- "请先用 ui_api.py getToolbars 获取"
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: 已有按钮 {btn_key} 位于 "
447
- f"{_existing_parent!r},请求的 parentScope={scope!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
- # 单据表单与列表共享同一元数据命名空间。默认 bar_<operationKey> 已被表单
463
- # 工具栏占用时,为列表使用确定性的 tbl_ 前缀,避免与表单按钮冲突。
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: 已有按钮 {btn_key} 位于 "
477
- f"{_existing_parent!r},请求的 parentScope={scope!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 是 character varying(36),按钮 key 与操作 key 共用这个上限。
494
- # addButton 会用操作 key 派生按钮 key(bar_/tbl_/btn 前缀),所以一个本身合法的
495
- # operationKey 仍可能派生出超长的按钮 key。数据库层失败会留下一条存不下去的记录,
496
- # 之后这张表单的所有元数据写入都会被它永久卡死。
497
- # 这段刻意放在 skip_operation 分支之前:--skipOperation 只跳过操作写入,
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: 派生出的按钮 key "
502
- f"{btn_key!r} 长度为 {len(btn_key)},超过平台存储上限 "
503
- f"{_OPERATION_KEY_MAX_LENGTH};请缩短 operationKey 或显式传入更短的 --key"
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 预检 → 必要时 addOperation
523
+ # 5. operationKey pre-check → addOperation if needed
507
524
  added_operation = False
508
525
  if skip_operation:
509
- print(f"[addButton] --skipOperation 指定,跳过 addOperation"
510
- f"(需确保 operationKey={operation_key} 已存在)", flush=True)
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
- # listOperations 在 addOperation 后可能短暂滞后。一次精确回读避免把只绑定
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} 已存在,跳过 addOperation",
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 缺陷:响应抛 NPE 但实际已写入。
551
- # 通过 listOperations 后置回查,operationKey 已存在则视为成功。
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,但回查 listOperations "
571
- f"已存在 operationKey={operation_key},视为已写入(已知后端缺陷容忍)",
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 失败: {err_msg}")
614
+ raise ValueError(f"addOperation failed: {err_msg}")
596
615
  else:
597
616
  added_operation = True
598
- print(f"[addButton] ✅ 新建操作 {operation_key} "
617
+ print(f"[addButton] ✅ New operation {operation_key} "
599
618
  f"(operationType={operation_type})", flush=True)
600
619
 
601
- # 6. modifyMeta 加按钮/widget
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 缺陷:响应抛 NPE 但实际已写入。
640
- # 回查 form layout,若 btn_key 已出现在 parent 下则视为成功。
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 {element_type}) 响应 NPE,但回查 "
654
- f"{layout_key} 已找到 {btn_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 {element_type}) 失败: {err_msg}"
678
+ f"modifyMeta(add {element_type}) failed: {err_msg}"
660
679
  )
661
680
  else:
662
- print(f"[addButton] ✅ 按钮 {btn_key} ({element_type}/{bt}) 已挂到 parentScope={scope}",
681
+ print(f"[addButton] ✅ Button {btn_key} ({element_type}/{bt}) 已挂 to parentScope={scope}",
663
682
  flush=True)
664
683
 
665
- # 7. setProperties 绑定 OperationKey
684
+ # 7. setProperties bind OperationKey
666
685
  if tv == "list":
667
686
  bind_result = {
668
687
  "skipped": True,
669
- "reason": "target_view=list:operationKey 已随 add op 的 value 直接绑定在 BarItemAp 上",
688
+ "reason": "target_view=list: operationKey 已随 add op 的 value 直接绑定在 BarItemAp 上",
670
689
  }
671
- print(f"[addButton] ✅ target_view=list 绑定 OperationKey={operation_key} → {btn_key} (随 add op 完成)",
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": "已有按钮已经绑定请求的 OperationKey",
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) 失败: {_extract_error_message(bind_result)}"
706
+ f"setProperties(OperationKey) failed: {_extract_error_message(bind_result)}"
688
707
  )
689
- print(f"[addButton] ✅ 绑定 OperationKey={operation_key} → {btn_key}", flush=True)
708
+ print(f"[addButton] ✅ Bind OperationKey={operation_key} → {btn_key}", flush=True)
690
709
 
691
- # 8. 独立终态回读;后端成功回执不能证明 UI 写入没有被静默跳过。
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
- # ─── 过滤字段 / 工具栏项 CLI (listMeta子项,配合 FilterColumnHandler)────
777
+ # ─── 过滤Field / 工具栏项 CLI (listMeta子项, 配合 FilterColumnHandler) ────
758
778
 
759
779
  # 视图 key → elementType
760
- # ⚠️ 服务端真正识别的 parentScope ID 为【无 ap 后缀】版本。带 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
- # 旧别名(向后兼容;内部统一映射到无 ap 后缀版)
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* 子类(与服务端 inferControlType 规则一致)
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
- """按父字段类型返回子属性的中文显示名,未命中则返回原 sub_key。"""
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
- # 按 FieldType 推导 Scheme* 子类
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
- """过滤字段添加预校验。调用服务端 FilterColumnHandler.canAddFilterColumn。
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
- 不传目标时返回全局类别目录,供新建 AssistantField;同时传 formNumber 和
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
- """返回使用精确 key 发布的任意元素类型。"""
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
- """工具栏项添加预校验。调用服务端 FilterColumnHandler.canAddToolbarItem。
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
- """快捷在过滤视图添加字段(form + commonfiltercolumn/schemefiltercolumn + add)。
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 非法: '{parent_scope}',必须为 "
1193
+ f"parentScope Invalid: '{parent_scope}', must be "
1174
1194
  f"{sorted(FILTER_VIEW_PARENT_KEYS.keys())}"
1175
1195
  )
1176
- # 强制规范化:服务端真正识别无 ap 后缀版
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. 查字段以回填 name / 推导 controlType
1205
+ # 2. 查Field以回填 name / 推导 controlType
1186
1206
  field_meta = None
1187
- parent_meta = None # 引用属性场景下所属的父字段元信息
1188
- sub_key = None # 引用属性场景下的子 key
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
- # 引用属性场景(parent.sub):定位父字段元信息
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] 字段 '{field_key}' 类型 '{field_type}' → controlType={control_type}",
1255
+ print(f"[addFilterColumn] Field '{field_key}' type '{field_type}' → controlType={control_type}",
1236
1256
  flush=True)
1237
1257
 
1238
- # 4. 构造 op
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
- """从 getEntityType 的完整实体图提取可查询运行时属性。"""
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 在设计态是一个字段,运行时却持久化为两个 DateProp。列表元数据按
1307
- 运行时实体求值,直接绑定设计态 key 会触发 FIELDNOTEXISTS000005。该映射属于
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}.开始", "type": "DateField", "ownerScope": owner_scope},
1328
- {"key": end_key, "name": f"{name}.结束", "type": "DateField", "ownerScope": owner_scope},
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 only entity, order, hyperlink, and non-empty displayName",
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
- # 收敛旧版本直接绑定设计态组合字段 key 的无效列表配置。
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 current_data.get(
1852
- name,
1853
- current_data.get(name[:1].lower() + name[1:]),
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
- evidence["properties"][prop_name] = {"requested": expected_value, "actual": actual_value,
1959
- "matched": actual_value == expected_value}
1960
- if actual_value != expected_value:
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"仍存在组合字段的旧列表绑定: {logical_key}")
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"仍存在组合字段的旧过滤绑定: {logical_key}")
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(field_key for field_key, _ in filter_specs)
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(f"运行态实体不包含列表字段 {invalid_runtime_bindings}")
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
- "message": "The requested list configuration is complete; the actual read-back is in verification. "
2017
- "Do not rediscover or reverify these same concerns. Continue only other explicit user concerns; "
2018
- "otherwise present the result using the returned appRoot and formNumber. A verified no-op does not require another card.",
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 新建列:columnKey == fieldKey(无前缀,如 billno / kdtest_wens_recname)
2026
- - buildMeta 继承/模板列:columnKey == 'col' + 字段标识(如 colname / colnumber / colbillno)
2192
+ - addFilter 新建列: columnKey == fieldKey (无前缀, 如 billno / kdtest_wens_recname)
2193
+ - buildMeta 继承/模板列: columnKey == 'col' + 字段标识 (如 colname / colnumber / colbillno)
2027
2194
 
2028
- 上层 Agent 删除继承字段时通常只知道字段标识(name/number/billno),传入裸 key,
2029
- 与真实继承列 key(colXXX)不匹配,服务端幂等 no-op 造成假成功。这里一次性生成
2030
- 候选集,由 remove 幂等无害特性覆盖两套规则。返回去重且保序的列表。
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 前缀形式;兜底补裸 key
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 生成多候选(原始 / col 前缀 / 去 col 前缀),一次 modifyMeta 提交多个
2051
- remove op,覆盖新建列(key=fieldKey)与继承列(key=col+fieldKey)两套命名规则;
2052
- remove 对不存在的 key 幂等无害,不匹配的候选为 no-op。
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 非法: '{parent_scope}',必须为 {sorted(FILTER_VIEW_PARENT_KEYS.keys())}"
2226
+ f"parentScope Invalid: '{parent_scope}', must be {sorted(FILTER_VIEW_PARENT_KEYS.keys())}"
2060
2227
  )
2061
- # 强制规范化:服务端识别无 ap 后缀版
2228
+ # 强制规范化: 服务端识别无 ap 后缀版
2062
2229
  parent_scope = _normalize_filter_parent_scope(parent_scope)
2063
2230
  if parent_scope == "schemefilterview":
2064
2231
  print(
2065
- "[removeFilterColumn][WARN] 正在删除方案过滤视图(schemefilterview)下的过滤字段,"
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"(覆盖新建列key=fieldKey与继承列key=col+fieldKey两套规则)",
2241
+ f"(覆盖新建列key=fieldKey与继承列key=col+fieldKey两套规则)",
2075
2242
  flush=True,
2076
2243
  )
2077
2244
  ops = [