@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
@@ -1,8 +1,12 @@
1
1
  # 黄金示例:成本分析报表(左树右表)
2
2
 
3
- > 「树形报表」指的是两种不同的功能。本示例是左树右表那一种,它需要**两个**插件:一个
4
- > `reporttreelist` 插件构建左侧导航,一个 `reportlist` 插件产出所选节点的数据行。把同一批数据按
5
- > 层级折叠展示是另一种功能 —— 见最后一节。
3
+ > 「树形报表」指的是两种不同的功能。本示例是左树右表那一种,它需要**两个**插件、注册在**两个**
4
+ > 槽位:一个 `reporttreelist` 插件在左树上构建导航,一个 `reportlist` 插件在报表列表上产出所选
5
+ > 节点的数据行。把同一批数据按层级折叠展示是另一种功能 —— 见最后一节。
6
+ >
7
+ > **当前写前拒绝**:`REPORT_PRECISION_REFERENCE_UNVERIFIED`。
8
+ > 预算、实际及差异金额绑定隐藏 `currency_id: bigint`,其 Long 主键不是 DynamicObject 精度引用。
9
+ > 此 Artifact 是保留左树和动态币别需求的历史反例,不是当前可写输入;不能用固定精度或删绑定代替完成。
6
10
 
7
11
  ## 用户需求
8
12
 
@@ -17,9 +21,16 @@
17
21
  - `reporttreelist` —— `AbstractReportTreeDataPlugin.query(ReportQueryParam, String nodeId)`
18
22
  返回 `List<TreeNode>`;只负责构建组织树
19
23
  - `reportlist` —— `AbstractReportListDataPlugin.query(ReportQueryParam, Object selectedObj)`;
20
- 被点击的节点作为 `selectedObj` 传入,用来收窄数据行
24
+ 被点击节点的 id 作为 `selectedObj` 传入,用来收窄数据行
21
25
 
22
- 元数据对这两者一无所知。Artifact 声明展现,两个插件都注册为报表插件。
26
+ 两者注册在不同的位置,而且创建时只注册其中一个。没有任何公共模板预置左树:`bos_grouprpttpl` 是
27
+ 分组报表 —— 报表分组面板里放分组列表,旁边是报表数据面板 —— 不是左树报表,`bos_rpttpl` 也没有左树。
28
+ 只有用户在设计器里给报表新增「报表树」控件后,报表才有左树(`ReportTreeAp`),CLI 也没有发布添加该
29
+ 控件的命令。所以报表由 `bos_rpttpl` 创建,取数插件放进 `reportPlugins`,即报表列表的
30
+ `ReportQueryPlugin`;树插件等控件存在之后再放进 `ReportTreeAp.ReportPlugin`,即左树自己的槽位。
31
+ 在 Artifact 里声明 `reportProperties.tree` 会被 `R-property-tree` 拒绝:在本次 run 创建的页面上,
32
+ 该写入会在 `createPage` 之后失败。把树插件类当作第二个 `reportPlugins` 条目注册,会把它放到列表的
33
+ 取数槽位上,在那里它构建不出任何树 —— `R-plugin-slot` 正是针对这种写法给出警告。
23
34
 
24
35
  ## 预期 Artifact
25
36
 
@@ -100,12 +111,6 @@
100
111
  { "fieldKey": "cost_item", "direction": "asc" }
101
112
  ],
102
113
  "reportPlugins": [
103
- {
104
- "type": "Java",
105
- "className": "kd.kdtest.report.CostAnalysisTreePlugin",
106
- "description": "左侧组织树导航",
107
- "enabled": true
108
- },
109
114
  {
110
115
  "type": "Java",
111
116
  "className": "kd.kdtest.report.CostAnalysisReportPlugin",
@@ -119,21 +124,45 @@
119
124
  ```
120
125
 
121
126
  这里没有 `org` 列:组织是导航维度,不是行的列。期间过滤 key 是 `flt_period`,与每个列 key 都不同。
127
+ 这里也没有 `reportProperties.tree`:本 Artifact 执行时左树还不存在。
122
128
 
123
129
  ## 执行流
124
130
 
125
- 1. `createPage` → 由默认模板创建报表
131
+ 0. 当前在 `createPage` 前拒绝上述输入。先验证平台币别引用字段、旧 BigInt 迁移及格式化前批量
132
+ DynamicObject 物化,再开放技能契约。原始/历史契约的每个金额列保留 `precisionSource`:
133
+ `requiredRuntimeValueType=DynamicObject`、`runtimeVerified=false`、`rawIdIsReference=false`、
134
+ `metadataWriteBlocked=true`;`requiredEvidence` 还要求混合币别、分组、合计、分页和导出验收。
135
+ 以下步骤仅是能力验证后的目标流程,未执行不代表已交付。
136
+
137
+ 1. `createPage` → 由 `bos_rpttpl` 创建报表;没有任何公共模板带左树
126
138
  2. `setReportColumns` → 5 个列,其中 3 个在「金额」之下、1 个隐藏:三个金额列都绑定
127
- `currency_id`,所以那一列必须存在
139
+ `currency_id`,保留共享依赖。仅声明隐藏列、名称一致或调用 getColumns() 工厂不能证明引用已物化
128
140
  3. `setReportFilters` → 一个期间控件
129
- 4. `setReportSorts` → `cost_item asc`
130
- 5. `registerReportPlugin` × 2 → 树插件与取数插件。`registerReportPlugin` 按 `className` 幂等更新,
131
- 所以两次调用注册两个插件,而不是替换掉一个。
132
- 6. Java 交付 → 两个类
141
+ 4. 保留 `reportSorts` → 将 `cost_item asc` 编译为 `defaultSorts`;不调用已阻断的 `setReportSorts`
142
+ 5. `registerReportPlugin` → 取数插件,写入报表列表的 `ReportQueryPlugin`
143
+ 6. Java 交付 → `CostAnalysisReportPlugin` 按类型 `reportlist` 交付。编译出的插件契约在
144
+ `pluginClasses` 里列出它、在 `pluginTypes` 里给出其类型;源码归档之前,出卡会以
145
+ `REPORT_PLUGIN_SOURCE_MISSING` 被拒绝。左树存在之前 `selectedObj` 为 null,报表显示不收窄的数据行。
146
+ 在分页/导出前的最终 DataSet 中消费 `defaultSorts` 和 `defaultSortPolicy`,交互排序优先;
147
+ 分别验证默认排序和交互排序。
148
+ 7. 用户在设计器里给报表新增「报表树」控件(`ReportTreeAp`)。下面的命令按这个类型定位它,
149
+ 不论该控件的 key 是什么
150
+ 8. `setReportProperties --target tree` 把树插件类名写入 `ReportTreeAp.ReportPlugin`;报表没有该控件时
151
+ 这一写入会以 `REPORT_PROPERTY_ELEMENT_MISMATCH` 被拒绝:
152
+
153
+ ```bash
154
+ {python} "{cosmic_api}" setReportProperties \
155
+ --app-root "{appRoot}" --formNumber kdtest_cost_analysis_rpt \
156
+ --target tree --input '{"ReportPlugin": "kd.kdtest.report.CostAnalysisTreePlugin"}'
157
+ ```
158
+
159
+ 9. Java 交付 → `CostAnalysisTreePlugin` 按类型 `reporttreelist` 交付,针对已有表单编码准备,而不是
160
+ 针对 Artifact —— Artifact 里从未出现过它
133
161
 
134
162
  ## 插件形态
135
163
 
136
- 树插件返回导航节点。首次加载时 `nodeId` 为 null,这种情况必须返回根节点列表:
164
+ 树插件返回导航节点。首次加载时 `nodeId` 为 null,这种情况必须返回根节点列表;`setLeaf(false)` 的
165
+ 节点会显示展开箭头,展开时框架会带着该节点的 id 再调一次 `query`:
137
166
 
138
167
  ```java
139
168
  public class CostAnalysisTreePlugin extends AbstractReportTreeDataPlugin {
@@ -145,7 +174,7 @@ public class CostAnalysisTreePlugin extends AbstractReportTreeDataPlugin {
145
174
  List<TreeNode> nodes = new ArrayList<>();
146
175
  QFilter parent = new QFilter("parent", QCP.equals,
147
176
  nodeId == null || "0".equals(nodeId) ? 0L : Long.parseLong(nodeId));
148
- DataSet ds = ORM.create().queryDataSet(ALGO_NAME, "bos_org",
177
+ DataSet ds = QueryServiceHelper.queryDataSet(ALGO_NAME, "bos_org",
149
178
  "id,number,name,parent,isleaf", new QFilter[]{parent}, "number");
150
179
  try {
151
180
  while (ds.hasNext()) {
@@ -153,8 +182,8 @@ public class CostAnalysisTreePlugin extends AbstractReportTreeDataPlugin {
153
182
  TreeNode node = new TreeNode();
154
183
  node.setId(String.valueOf(row.getLong("id")));
155
184
  node.setText(row.getString("number") + " " + row.getString("name"));
156
- node.setpId(String.valueOf(row.getLong("parent")));
157
- node.setIsParent(!row.getBoolean("isleaf"));
185
+ node.setParentid(String.valueOf(row.getLong("parent")));
186
+ node.setLeaf(row.getBoolean("isleaf"));
158
187
  nodes.add(node);
159
188
  }
160
189
  } finally {
@@ -165,8 +194,12 @@ public class CostAnalysisTreePlugin extends AbstractReportTreeDataPlugin {
165
194
  }
166
195
  ```
167
196
 
168
- 取数插件按拿到的节点收窄。这里最经典的 bug 就是忽略 `selectedObj`:那样无论用户怎么导航,报表都会
169
- 显示所有组织的数据行。
197
+ 取数插件按拿到的节点收窄:`selectedObj` 是被点击节点的 id,即树插件用 `setId` 设置的那个字符串。
198
+ 这里最经典的 bug 就是忽略它:那样无论用户怎么导航,报表都会显示所有组织的数据行。`selectedObj`
199
+ 为 null(还没有点击节点)时返回不收窄的数据行。
200
+
201
+ 以下 Java 片段只展示导航条件和 DataSet 别名。`currency as currency_id` 返回 Long 主键,
202
+ 尚未实现格式化前批量引用物化,不能作为动态精度根治交付;期间必录还须使用共用空值规则并在取数前验证。
170
203
 
171
204
  ```java
172
205
  public class CostAnalysisReportPlugin extends AbstractReportListDataPlugin {
@@ -178,7 +211,7 @@ public class CostAnalysisReportPlugin extends AbstractReportListDataPlugin {
178
211
  public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {
179
212
  FilterInfo filter = queryParam.getFilter();
180
213
  List<QFilter> filters = new ArrayList<>(filter.getQFilters());
181
- // 期间过滤的必填不会被元数据落地,所以在这里强制。
214
+ // 配置必录与查询校验不同;取数前必须再次校验。
182
215
  FilterItemInfo period = filter.getFilterItem("flt_period");
183
216
  if (period == null || period.getValue() == null) {
184
217
  throw new KDBizException("期间必填。");
@@ -188,20 +221,25 @@ public class CostAnalysisReportPlugin extends AbstractReportListDataPlugin {
188
221
  filters.add(new QFilter("org", QCP.equals, Long.parseLong(selectedObj.toString())));
189
222
  }
190
223
  // 列名必须与每个 dateIndex 相等,包括 currency_id。
191
- return ORM.create().queryDataSet(ALGO_NAME, ENTITY_NUMBER,
224
+ return QueryServiceHelper.queryDataSet(ALGO_NAME, ENTITY_NUMBER,
192
225
  "costitem.name as cost_item, budget_amt, actual_amt,"
193
226
  + " actual_amt - budget_amt as variance_amt, currency as currency_id",
194
- filters.toArray(new QFilter[0]), "cost_item");
227
+ filters.toArray(new QFilter[filters.size()]), "cost_item");
195
228
  }
196
229
  }
197
230
  ```
198
231
 
199
- 参考:`{ROOT_DIR}/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md` 与
232
+ 如果改为一次性构建整棵树 —— 一个合成的「全部」根节点,通过 `setChildren` 挂上全部节点 —— 根节点
233
+ 的 id 也要同样处理:先约定根 id 对取数插件意味着什么(不收窄,或子节点 id 集合),并让每个 id
234
+ 在多次查询之间唯一且稳定,否则选中节点与其缓存的数据行会对不上。
235
+
236
+ 参考:`{ROOT_DIR}/knowledge/cosmic/platform/reporttreedata-plugin.md`、
237
+ `{ROOT_DIR}/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md` 与
200
238
  `{ROOT_DIR}/knowledge/cosmic/platform/reportlistdata-plugin.md`。
201
239
 
202
240
  ## 如果需求其实是折叠式的行
203
241
 
204
- 把同一批数据按层级折叠展示是另一种功能,不需要树插件。它属于 `reportform` 插件的职责:
242
+ 把同一批数据按层级折叠展示是另一种功能,既不需要树插件,也不需要报表树控件。由 `reportform` 插件开启:
205
243
 
206
244
  ```java
207
245
  @Override
@@ -211,13 +249,22 @@ public void setTreeReportList(TreeReportListEvent event) {
211
249
  }
212
250
  ```
213
251
 
214
- 在 `beforeQuery` 里调 `ReportQueryParam.setTreeReportList(true)` 等价。取数插件照常返回普通行;
215
- 关于 `id` / `pid` 作为 DataSet 列没有任何文档约定,不要臆造。
252
+ 在 `beforeQuery` 里调 `ReportQueryParam.setTreeReportList(true)` 等价。取数插件随后以数据行的形式
253
+ 返回层级:每一行都带 `rowid`(本行唯一 id,String)、`pid`(父行的 `rowid`,顶层行为 `"0"`)和
254
+ `isgroupnode`(分组行为 1,明细行为 0),这与标准产品树形报表的构造方式一致。不要用 `id` / `pid`
255
+ 代替它们。`reportform` 插件不是 `reportPlugins` 条目:那个槽位是报表列表的取数插件。它在同一个表单编码上
256
+ 单独走一次 Java 交付,由 `register_plugins.py --verify` 以 `targetType` `BILL_FORM` 注册,工具栏监听写在
257
+ `registerListener` 里。见
258
+ `{ROOT_DIR}/knowledge/cosmic/platform/scenes/report/report-tree-list.md`。
216
259
 
217
260
  ## 预期结果
218
261
 
262
+ 当前输入被写前拒绝,不创建报表、不写元数据。以下仅为引用链路和技能契约开放后的待验收目标;
263
+ 源码、编译或保存回执不代表左树交互及混合币别精度已验收。
264
+
219
265
  - 报表表单 `kdtest_cost_analysis_rpt` 由 `bos_rpttpl` 创建
220
266
  - 5 个列,3 个分组在「金额」之下,1 个隐藏币别列,组织有意不作为列
221
267
  - 一个期间过滤,由插件强制必填
222
268
  - 默认排序 `cost_item asc`
223
- - `CostAnalysisTreePlugin` 与 `CostAnalysisReportPlugin` 均已注册
269
+ - `CostAnalysisReportPlugin` 位于报表取数插件槽位;用户新增报表树控件之后,`CostAnalysisTreePlugin`
270
+ 位于左树。两者都作为 Java 源码交付
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "templateNumber": {
14
14
  "enum": ["bos_rpttpl", "bos_rptqingtpl", "bos_grouprpttpl", "bos_commfilterrpttpl"],
15
- "default": "bos_rpttpl"
15
+ "default": "bos_rpttpl",
16
+ "description": "bos_rpttpl is the default report. bos_grouprpttpl is the grouped report: a report group panel holding a group list (itself a report list with its own query plugin) beside the report data panel and its report list. No public template presets a left tree (ReportTreeAp): one exists only after a user adds the report tree control to the report in the designer, so a creation Artifact never declares reportProperties.tree (R-property-tree)."
16
17
  },
17
18
  "entities": {
18
19
  "type": "array",
@@ -7,6 +7,9 @@
7
7
 
8
8
  `QtyField.unitFieldRef` 使用同一实体内 `UnitField` 的 authoring `bizWord`:
9
9
 
10
+ 保留需求中的配对关系:各自独立的数量/单位需要分别定义 `UnitField` 并对应引用;业务明确共用
11
+ 单位时才复用一个单位字段。引用合法不代表这种业务选择正确。
12
+
10
13
  ```json
11
14
  [
12
15
  {"bizWord": "unit", "displayName": "单位", "dataType": "UnitField"},
@@ -29,3 +29,5 @@ emitter 会把自建字段引用解析为最终持久化编码,并依据 `getE
29
29
 
30
30
  只写需求明确提出的行为;未指定时省略 `order` 和 `hyperlink`。不得手写
31
31
  `listcol_*`、控件类型、父容器或 MetaOps;`runBatch` 会在一个列表配置步骤中完成编译和回读。
32
+
33
+ 列也接受 `properties`,从 fieldRef 绑定到真实 fieldKey 时完整保留,再由 configureList 按实际列类型校验。整数汇总示例:`{"fieldRef":"count","properties":{"Summary":"合计","ZeroShow":true}}`。未要求的属性一律省略。
@@ -1085,7 +1085,9 @@ def validate_authoring_contract(
1085
1085
  for boolean_key in ("hyperlink",):
1086
1086
  if boolean_key in column and not isinstance(column[boolean_key], bool):
1087
1087
  _issue(issues, "DM_SCHEMA_TYPE", f"{column_path}.{boolean_key}", "must be a boolean")
1088
- for key in sorted(set(column) - {"fieldRef", "order", "hyperlink"}):
1088
+ if "properties" in column and (not isinstance(column["properties"], dict) or not column["properties"]):
1089
+ _issue(issues, "DM_SCHEMA_TYPE", f"{column_path}.properties", "must be a non-empty property map")
1090
+ for key in sorted(set(column) - {"fieldRef", "order", "hyperlink", "properties"}):
1089
1091
  _issue(issues, "DM_SCHEMA_FORBIDDEN", f"{column_path}.{key}", "unknown list column configuration key")
1090
1092
  for collection_key in ("quickSearch", "commonFilters"):
1091
1093
  values = list_config.get(collection_key)
@@ -193,7 +193,7 @@ def _run_cosmic_api(
193
193
  if proc.returncode != 0:
194
194
  detail = (proc.stdout or proc.stderr or "").strip()
195
195
  print(f"[cosmic-api] {command} failed (exit={proc.returncode}): {detail}", file=sys.stderr)
196
- return None
196
+ return json.loads(proc.stdout) if proc.stdout.strip() else None
197
197
  output = (proc.stdout or "").strip()
198
198
  if not output:
199
199
  return None
@@ -1897,6 +1897,10 @@ def _merge_template_fields(
1897
1897
  etype = str(ent.get("type") or "")
1898
1898
  template_key = str(ent.get("templateKey") or "").strip()
1899
1899
 
1900
+ if etype == "BillEntity" and template_key.casefold() in {"blank", "blankpage"}:
1901
+ # The platform's blank bill has no preset fields to merge.
1902
+ continue
1903
+
1900
1904
  if not template_key:
1901
1905
  if etype in _CHILD_ENTITY_TYPES:
1902
1906
  # EntryEntity does not need its own templateKey; inherits parent entity template via parentEntityRef
@@ -1912,6 +1916,9 @@ def _merge_template_fields(
1912
1916
  if not parent_ent:
1913
1917
  continue
1914
1918
  parent_tk = str(parent_ent.get("templateKey") or "").strip()
1919
+ if (str(parent_ent.get("type") or "") == "BillEntity"
1920
+ and parent_tk.casefold() in {"blank", "blankpage"}):
1921
+ continue
1915
1922
  if not parent_tk:
1916
1923
  continue
1917
1924
  if parent_tk not in api_cache:
@@ -2211,6 +2218,10 @@ def _validate_template_keys(
2211
2218
  result = _run_cosmic_api(
2212
2219
  cosmic_api_path, command, arguments, app_root=bound_root,
2213
2220
  )
2221
+ if isinstance(result, dict) and result.get("errorCategory") in {"environment", "authentication"}:
2222
+ logs.err(result.get("errorCode") or "TEMPLATE-LOOKUP-UNAVAILABLE",
2223
+ result.get("message") or "Template lookup environment is unavailable")
2224
+ return False
2214
2225
  data = result.get("data") if isinstance(result, dict) else None
2215
2226
  templates = data.get("templates") if isinstance(data, dict) else None
2216
2227
  if (not isinstance(result, dict) or result.get("status") is not True
@@ -2240,6 +2251,8 @@ def _validate_template_keys(
2240
2251
  en=f"Entity '{ent.get('bizName')}' (type={etype}) missing templateKey, please specify")
2241
2252
  ok = False
2242
2253
  continue
2254
+ if etype == "BillEntity" and tk.casefold() in {"blank", "blankpage"}:
2255
+ tk = "blankPage"
2243
2256
  expected_model = model_type_by_entity.get(etype)
2244
2257
  if not expected_model:
2245
2258
  continue
@@ -2943,9 +2956,9 @@ def _compile_list_configuration(
2943
2956
  if not isinstance(column, dict):
2944
2957
  continue
2945
2958
  item: dict[str, Any] = {"fieldKey": resolve(column.get("fieldRef"))}
2946
- for key in ("order", "hyperlink"):
2959
+ for key in ("order", "hyperlink", "properties"):
2947
2960
  if key in column:
2948
- item[key] = column[key]
2961
+ item[key] = copy.deepcopy(column[key])
2949
2962
  columns.append(item)
2950
2963
  if columns:
2951
2964
  compiled["columns"] = columns
@@ -4894,6 +4907,16 @@ def main(argv: list[str] | None = None) -> int:
4894
4907
  else:
4895
4908
  print(f"DATAMODEL_SECTION_NOT_FOUND: app-root does not exist {app_root}", file=sys.stderr)
4896
4909
  return EXIT_DATAMODEL_SECTION_NOT_FOUND
4910
+ from execution_binding import bound_entrypoint, validate_execution_binding
4911
+ try:
4912
+ selected = bound_entrypoint(app_root, "skills/cosmic-meta-modeling/scripts/data_model_emit.py")
4913
+ if selected:
4914
+ return subprocess.call([sys.executable, str(selected), *arguments])
4915
+ validate_execution_binding(app_root)
4916
+ except ValueError as error:
4917
+ print(json.dumps({"status": False, "errorCode": str(error).split(":", 1)[0],
4918
+ "message": str(error), "writeAttempted": False}), file=sys.stderr)
4919
+ return EXIT_EMIT_IO_FAILED
4897
4920
  ensure_app_runtime_ignore(app_root)
4898
4921
 
4899
4922
  authoring_path_override = Path(args.authoring_file).resolve()
@@ -5152,7 +5175,7 @@ def main(argv: list[str] | None = None) -> int:
5152
5175
  # filesystem read or model-authored command reconstruction.
5153
5176
  root = Path(__file__).resolve().parents[3]
5154
5177
  next_argv = [sys.executable, str(root / "skills" / "cosmic-meta-api" / "scripts" / "cosmic_api.py"),
5155
- "runBatch", "--app-root", str(app_root), "--emit-manifest", str(output_dir / "_emit_manifest.json"), "--verify"]
5178
+ "--stdout", "runBatch", "--compact", "--app-root", str(app_root), "--emit-manifest", str(output_dir / "_emit_manifest.json"), "--verify"]
5156
5179
  print("NEXT_COMMAND=" + json.dumps(next_argv, ensure_ascii=False))
5157
5180
  print(f"EMIT_SUCCESS: {len(written)} artifact written to {output_dir}")
5158
5181
  return EXIT_SUCCESS
@@ -37,12 +37,15 @@ if str(API_SCRIPTS) not in sys.path:
37
37
 
38
38
  from _core.report_ops import ( # noqa: E402
39
39
  _PRESENTATION_BINDING_KEY,
40
+ _REPORT_FILTER_TYPE_ALIASES,
41
+ REPORT_FILTER_MULTI_VALUE_TYPES,
40
42
  normalize_report_columns_input,
41
43
  normalize_report_filters_input,
42
44
  normalize_report_plugin_input,
43
45
  normalize_report_properties_artifact,
44
46
  normalize_report_query_data_source_input,
45
47
  normalize_report_sorts_input,
48
+ compile_report_default_sorts,
46
49
  )
47
50
 
48
51
 
@@ -80,11 +83,13 @@ def _requests_unpersisted(filt, key):
80
83
 
81
84
  # Java fully-qualified class name pattern
82
85
  # e.g. kd.custom.report.SalesReportPlugin
86
+ # The simple name admits no underscore because the plugin handoff resolves a registered class by
87
+ # that exact shape (prepare_plugin_handoff CLASS_RE): a wider pattern here let an artifact pass
88
+ # validation, register its class, and then fail Java delivery with PLUGIN_CLASS_NOT_QUALIFIED.
83
89
  JAVA_CLASS_PATTERN = re.compile(
84
- r'^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*\.[A-Z][A-Za-z0-9_]*$'
90
+ r'^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*\.[A-Z][A-Za-z0-9]*$'
85
91
  )
86
92
 
87
-
88
93
  # ═══════════════════════════════════════════════════════════════════════════════
89
94
  # Data Structures
90
95
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -200,6 +205,7 @@ def validate_report_artifact(artifact):
200
205
  normalize_report_sorts_input(report_sorts)
201
206
  for item in report_plugins:
202
207
  normalize_report_plugin_input(item)
208
+ compile_report_default_sorts(artifact)
203
209
  if query_data_source:
204
210
  normalize_report_query_data_source_input(query_data_source)
205
211
  if artifact.get("reportBehavior"):
@@ -312,19 +318,25 @@ def validate_report_artifact(artifact):
312
318
  path="reportProperties",
313
319
  ))
314
320
 
315
- # ── R-property-tree: left-tree properties need a template that has a left tree ──
316
- # ReportTreeAp exists only on a tree-report template. The write is refused before it is
317
- # sent, so the run fails rather than silently doing nothing — but saying so here saves
318
- # the createPage.
321
+ # ── R-property-tree: a report this run creates has no left tree ──
322
+ # No public report template presets a ReportTreeAp — bos_grouprpttpl is the grouped
323
+ # report (a report group panel with its group list beside the report data panel), not a
324
+ # left tree. A left tree exists only after a user adds the report tree control to the
325
+ # report in the designer, so on the page this run creates the write is refused with
326
+ # REPORT_PROPERTY_ELEMENT_MISMATCH after createPage. Refusing here keeps the page from
327
+ # being created for a run that cannot finish.
319
328
  if isinstance(report_properties, dict) and report_properties.get("tree"):
320
- warnings.append(Issue(
329
+ errors.append(Issue(
321
330
  rule="R-property-tree",
322
- severity="warning",
331
+ severity="error",
323
332
  message=(
324
- "reportProperties.tree applies to ReportTreeAp, which only a left-tree "
325
- "report template carries. On a plain report the write is refused with "
326
- "REPORT_PROPERTY_ELEMENT_MISMATCH and the run fails; confirm the selected "
327
- "templateNumber builds a report with a left tree."
333
+ "reportProperties.tree applies to ReportTreeAp, which no public report "
334
+ "template carries (bos_grouprpttpl is the grouped report, not a left tree): "
335
+ "a report exists with a left tree only after a user adds the report tree "
336
+ "control in the designer. On the report this run creates the write is refused "
337
+ "with REPORT_PROPERTY_ELEMENT_MISMATCH after createPage. Omit it here; once "
338
+ "the report carries a ReportTreeAp, write it with setReportProperties "
339
+ "--target tree."
328
340
  ),
329
341
  path="reportProperties.tree",
330
342
  ))
@@ -386,6 +398,36 @@ def validate_report_artifact(artifact):
386
398
  # setReportFilters says it once per write, the plugin contract carries each comparison under
387
399
  # filterEnforcement, and the module SKILL states it as a capability boundary.
388
400
 
401
+ # ── R-filter-type-compare: a typed editor whose arity disagrees with its comparison ──
402
+ # Both readings still run, so it warns: `in` on a single-select editor lets the user pick one
403
+ # value only, and `=` on a multi-select editor hands query() several values to compare.
404
+ # (between against a single-value editor is refused outright by the input contract.)
405
+ for i, filt in enumerate(report_filters):
406
+ editor = _REPORT_FILTER_TYPE_ALIASES.get(str(filt.get("fieldType") or "").strip().lower())
407
+ compare = str(filt.get("compare") or "").strip().lower()
408
+ if not editor or compare == "between":
409
+ continue
410
+ multi = editor in REPORT_FILTER_MULTI_VALUE_TYPES
411
+ if compare == "in" and not multi:
412
+ message = (
413
+ f"Filter '{filt.get('fieldPath', '?')}' compares with in but its {editor} editor "
414
+ "takes one value. Use MulBasedataField, MulComboField or CheckBoxGroupField when "
415
+ "the user should pick several."
416
+ )
417
+ elif compare == "=" and multi:
418
+ message = (
419
+ f"Filter '{filt.get('fieldPath', '?')}' compares with = but its {editor} editor "
420
+ "returns several values; query() has to apply them as in."
421
+ )
422
+ else:
423
+ continue
424
+ warnings.append(Issue(
425
+ rule="R-filter-type-compare",
426
+ severity="warning",
427
+ message=message,
428
+ path=f"reportFilters[{i}].fieldType",
429
+ ))
430
+
389
431
  # ── R-presentation-fetch: a declared presentation is a job for the plugin ──
390
432
  # presentation and the non-persisted filter properties are compiled into the plugin
391
433
  # contract, so declaring either without registering a plugin means the requirement has
@@ -401,7 +443,7 @@ def validate_report_artifact(artifact):
401
443
  message=(
402
444
  "A column declares presentation (currency / measure unit / base data / "
403
445
  "scale), which only the data-fetch plugin can bind, but the artifact "
404
- "registers no reportPlugins. Register the plugin or drop the presentation."
446
+ "registers no reportPlugins. 注册所需插件并保留呈现要求,不能删除需求来通过校验。"
405
447
  ),
406
448
  path="reportPlugins",
407
449
  ))
@@ -435,10 +477,8 @@ def validate_report_artifact(artifact):
435
477
  severity="error",
436
478
  message=(
437
479
  f"presentation.{key}='{bound}' is not the fieldKey of any declared "
438
- "report column. It names the column carrying that id, not a loose "
439
- f"DataSet column: declare it, normally as {{\"kind\": \"column\", "
440
- f"\"fieldKey\": \"{bound}\", \"dateIndex\": \"{bound}\", \"fieldType\": "
441
- "\"bigint\", \"hide\": true}, and have query() return it."
480
+ "report column. 必须先确认平台引用字段和格式化前批量物化能力;"
481
+ "补一列隐藏 bigint 只会得到 Long,不能修复 DynamicObject 精度引用。"
442
482
  ),
443
483
  path=f"{col_path}.presentation.{key}",
444
484
  ))
@@ -457,33 +497,18 @@ def validate_report_artifact(artifact):
457
497
  path=f"{col_path}.presentation.{key}",
458
498
  ))
459
499
  continue
460
- # Both remaining checks are about the shape of a healthy target, not about whether
461
- # the binding resolves, so they warn. The type one matters more than it looks: the
462
- # target's declared fieldType is what the plugin contract turns into a Row accessor,
463
- # so a text target tells the plugin to read the id with getString — which is how
464
- # "operator does not exist: bigint = character varying" happened in the first place.
465
- if not target.get("hide"):
466
- warnings.append(Issue(
467
- rule="R-presentation-binding-target",
468
- severity="warning",
469
- message=(
470
- f"presentation.{key}='{bound}' binds a visible column, so the report "
471
- "shows the raw id next to the formatted value. Set hide: true on it "
472
- "unless the id is wanted as its own column."
473
- ),
474
- path=f"{col_path}.presentation.{key}",
475
- ))
476
- if str(target.get("fieldType") or "").strip().lower() != "bigint":
477
- warnings.append(Issue(
478
- rule="R-presentation-binding-target",
479
- severity="warning",
480
- message=(
481
- f"presentation.{key}='{bound}' binds a column declared as "
482
- f"'{target.get('fieldType')}', but it carries a record id. Declare it "
483
- "bigint so the plugin contract asks for a Long."
484
- ),
485
- path=f"{col_path}.presentation.{key}",
486
- ))
500
+ # 名称相同和隐藏都不能将标量升级成引用;当前列契约未开放已验证引用类型。
501
+ errors.append(Issue(
502
+ rule="R-presentation-binding-target",
503
+ severity="error",
504
+ message=(
505
+ f"presentation.{key}='{bound}' 的目标 fieldType={target.get('fieldType')!r} "
506
+ "尚无 DynamicObject 精度引用证据。隐藏 bigint 的 Long 主键及 getColumns() "
507
+ "工厂不能改变既有 BigInt 属性;平台引用字段、迁移和批量物化验证前拒绝写入。"
508
+ "保留动态精度要求,不能以固定精度或取消绑定代替完成。"
509
+ ),
510
+ path=f"{col_path}.presentation.{key}",
511
+ ))
487
512
 
488
513
  # ── R-plugin-class: Plugin className must follow Java fully-qualified naming ──
489
514
  for i, plugin in enumerate(report_plugins):
@@ -495,6 +520,36 @@ def validate_report_artifact(artifact):
495
520
  message=f"Plugin className '{class_name}' does not match Java fully-qualified class name pattern (e.g. 'kd.custom.report.MyPlugin')",
496
521
  path=f"reportPlugins[{i}].className",
497
522
  ))
523
+ # The left tree's own data plugin is a Java class name too, written as a string property.
524
+ _tree = (report_properties or {}).get("tree") if isinstance(report_properties, dict) else None
525
+ _tree_plugin = str(_tree.get("ReportPlugin") or "").strip() if isinstance(_tree, dict) else ""
526
+ if _tree_plugin and not JAVA_CLASS_PATTERN.match(_tree_plugin):
527
+ errors.append(Issue(
528
+ rule="R-plugin-class",
529
+ severity="error",
530
+ message=f"Left-tree plugin className '{_tree_plugin}' does not match Java fully-qualified class name pattern (e.g. 'kd.custom.report.MyTreePlugin')",
531
+ path="reportProperties.tree.ReportPlugin",
532
+ ))
533
+
534
+ # ── R-plugin-slot: reportPlugins is one slot, the report list's data-fetch plugin ──
535
+ # Every reportPlugins entry is written into ReportListAp.ReportQueryPlugin, which runs an
536
+ # AbstractReportListDataPlugin. Two entries there was the shape the left-tree example used to
537
+ # teach: the tree class registered as a second data plugin, where it builds no tree. The tree's
538
+ # own slot is ReportTreeAp.ReportPlugin, and a reportform plugin belongs to neither.
539
+ if len(report_plugins) > 1:
540
+ warnings.append(Issue(
541
+ rule="R-plugin-slot",
542
+ severity="warning",
543
+ message=(
544
+ f"reportPlugins registers {len(report_plugins)} classes into the report list's "
545
+ "data-fetch slot (ReportQueryPlugin), which takes one "
546
+ "AbstractReportListDataPlugin. A left-tree plugin (AbstractReportTreeDataPlugin) "
547
+ "belongs in ReportTreeAp.ReportPlugin, written by setReportProperties --target "
548
+ "tree once the report carries a report tree control, and a reportform plugin is "
549
+ "not a reportPlugins entry at all."
550
+ ),
551
+ path="reportPlugins",
552
+ ))
498
553
 
499
554
  # ── Build result ──
500
555
  valid = len(errors) == 0
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- """Every report golden example must pass the validator the module tells callers to run.
3
+ """可执行示例必须通过校验,明确标记的历史精度反例必须被真实门禁拒绝。
4
4
 
5
5
  The examples are the shape a model copies before it has read the schema, so an example
6
6
  that the validator rejects guarantees a failed first attempt. Four examples shipped in a
7
7
  state that failed every rule they were meant to demonstrate (data-source sections after
8
8
  dataSources was removed, column fieldType outside the verified enum, and
9
9
  scale/format/currencyField/enableFuzzy/generateCode/reportBehavior keys that the input
10
- contracts reject). This test keeps documentation and contract in the same generation.
10
+ contracts reject). 三个历史动态精度反例保留需求,但不得被当作可写黄金输入;
11
+ 本测试精确检查拒绝码和每个绑定错误,其余示例仍必须通过。
11
12
 
12
13
  Usage:
13
14
  python test_report_examples.py
@@ -61,18 +62,44 @@ class TestReportGoldenExamples(unittest.TestCase):
61
62
  def test_examples_exist(self):
62
63
  self.assertTrue(_example_files(), "no report golden example found")
63
64
 
64
- def test_every_example_artifact_is_valid(self):
65
+ def test_executable_examples_pass_and_historical_precision_examples_are_refused(self):
66
+ refused = {
67
+ "golden-example-report-plugin.md": ["reportColumns[2].presentation.currencyField"],
68
+ "golden-example-report-batch.md": [
69
+ f"reportColumns[3].children[{index}].presentation.measureUnitField" for index in range(3)
70
+ ],
71
+ "golden-example-report-tree.md": [
72
+ f"reportColumns[1].children[{index}].presentation.currencyField" for index in range(3)
73
+ ],
74
+ }
65
75
  checked = 0
76
+ historical = set()
66
77
  for path in _example_files():
67
78
  for artifact in _report_artifacts(path):
68
79
  checked += 1
69
80
  result = validate_report_artifact(artifact)
81
+ if os.path.basename(path) in refused:
82
+ # 仅允许这三个已明确标记的历史反例;每个绑定必须真实被拒绝。
83
+ historical.add(os.path.basename(path))
84
+ with open(path, encoding="utf-8") as handle:
85
+ self.assertIn("REPORT_PRECISION_REFERENCE_UNVERIFIED", handle.read())
86
+ self.assertFalse(result["valid"])
87
+ self.assertEqual({row["rule"] for row in result["errors"]},
88
+ {"R-report-contract", "R-presentation-binding-target"})
89
+ self.assertTrue(any("REPORT_PRECISION_REFERENCE_UNVERIFIED" in row["message"]
90
+ for row in result["errors"]))
91
+ self.assertEqual(sorted(row["path"] for row in result["errors"]
92
+ if row["rule"] == "R-presentation-binding-target"),
93
+ refused[os.path.basename(path)])
94
+ self.assertEqual(len(result["errors"]), 1 + len(refused[os.path.basename(path)]))
95
+ continue
70
96
  self.assertTrue(
71
97
  result["valid"],
72
98
  f"{os.path.basename(path)} fails report_validator: "
73
99
  f"{json.dumps(result['errors'], ensure_ascii=False, indent=2)}",
74
100
  )
75
- self.assertGreater(checked, 0, "no report Artifact found in the examples")
101
+ self.assertGreaterEqual(checked, len(refused), "missing historical report Artifact")
102
+ self.assertEqual(historical, set(refused))
76
103
 
77
104
  def test_no_example_declares_data_sources(self):
78
105
  for path in _example_files():