@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
@@ -0,0 +1,139 @@
1
+ ---
2
+ version: "1.0.0"
3
+ framework_version: "8.0.1"
4
+ source_doc: "标品报表案例 05、08"
5
+ last_updated: "2026-10-19"
6
+ status: "approved"
7
+ scene_name: "报表小计与合计行"
8
+ related_apis: ["DataSet.addField", "DataSet.copy", "DataSet.groupBy", "DataSet.select", "DataSet.union", "DataSet.orderBy", "DataSet.removeFields", "GroupbyDataSet.sum", "GroupbyDataSet.avg", "GroupbyDataSet.finish", "PackageDataEvent.getRowData", "PackageDataEvent.setFormatValue", "PackageDataEvent.getNoMergeKey", "ReportColumn.getFieldKey", "DynamicObject.getInt", "QueryServiceHelper.queryDataSet"]
9
+ topic: "报表数据查询"
10
+ ---
11
+
12
+ # 场景:报表小计与合计行
13
+
14
+ ## 适用情况
15
+ 报表在数据中间显示小计行——比如每个组织的明细之后——并显示一行带标签的合计,如"小计""合计"。`reportlist` 插件在 `query()` 里构造这些行:每个汇总层级一次聚合,每一行带 `sumlevel` 标记,合计行没有值的列用占位补齐,最后一次 `union`、一次 `orderBy`。`reportform` 插件在 `packageData` 中给带标记的行打上标签,并把在这些行上没有意义的列置空。
16
+
17
+ 列的 `Summary` 属性是另一种机制:它给列表加上自带的底部合计行。同一列二者只用其一——列表自带的合计行会把它拿到的每一行都加进去,小计行也不例外。
18
+
19
+ ## 事件/方法说明
20
+
21
+ - `DataSet.addField("0", "sumlevel")`:标记明细行;`select` 里的 `"1 as sumlevel"` / `"2 as sumlevel"` 标记小计行和合计行
22
+ - `DataSet.groupBy(keys)` + `sum` / `avg` + `finish()`:每个汇总层级一次聚合;不带参数的 `groupBy()` 把所有行聚合成一行
23
+ - `DataSet.select(exprs)`:把每个层级整理成同样的列名、同样的顺序,没有值的列用常量补齐(`"0 as sourcetype"`、`"'' as billno"`)
24
+ - `DataSet.union(other)` 后接 `orderBy(keys)`:各层级按显示顺序交错排列
25
+ - `packageData(PackageDataEvent)`(reportform):每个单元格触发一次;`getRowData().getInt("sumlevel")` 给出行的层级,`setFormatValue(value)` 替换单元格的显示值,`getNoMergeKey().add(fieldKey)` 让该单元格不参与行融合
26
+
27
+ ## 方法签名
28
+ ```java
29
+ // reportlist 插件
30
+ @Override
31
+ public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable
32
+
33
+ // reportform 插件
34
+ @Override
35
+ public void packageData(PackageDataEvent packageDataEvent)
36
+ ```
37
+
38
+ ## 代码示例
39
+ `extracted` `needs-verification`
40
+
41
+ 取数插件:明细、每个组织一行小计、一行合计,按显示顺序排列:
42
+
43
+ ```java
44
+ package kd.bos.plugin.sample.report.bizcase;
45
+
46
+ import java.util.ArrayList;
47
+ import java.util.List;
48
+
49
+ import kd.bos.algo.DataSet;
50
+ import kd.bos.entity.report.AbstractReportListDataPlugin;
51
+ import kd.bos.entity.report.ReportQueryParam;
52
+ import kd.bos.orm.query.QFilter;
53
+ import kd.bos.servicehelper.QueryServiceHelper;
54
+
55
+ public class ReserveSumReportListDataPlugin extends AbstractReportListDataPlugin {
56
+
57
+ private static final String ALGO_NAME = "xxx.report.reservesum";
58
+ private static final String ENTITY_NUMBER = "xxx_baddebtreserve";
59
+
60
+ @Override
61
+ public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {
62
+ List<QFilter> filters = new ArrayList<>(queryParam.getFilter().getQFilters());
63
+ // ……过滤面板条件照常在这里追加
64
+
65
+ // 明细:层级 0,排序分组与小计同为 0
66
+ DataSet detail = QueryServiceHelper.queryDataSet(ALGO_NAME, ENTITY_NUMBER,
67
+ "org.id as org, sourcetype.id as sourcetype, accrualrate, baseamount, accruedamt",
68
+ filters.toArray(new QFilter[filters.size()]), null)
69
+ .addField("0", "sumlevel")
70
+ .addField("0", "sortgroup");
71
+
72
+ // 每个组织一行小计。比率不能求和:取平均值带上,界面上置空
73
+ DataSet subtotal = detail.copy().groupBy(new String[]{"org"})
74
+ .avg("accrualrate").sum("baseamount").sum("accruedamt").finish()
75
+ .select(new String[]{"org", "0 as sourcetype", "accrualrate", "baseamount", "accruedamt",
76
+ "1 as sumlevel", "0 as sortgroup"});
77
+
78
+ // 全部行的合计;它的占位组织会让它排到最前面,所以排序分组为 1
79
+ DataSet total = detail.copy().groupBy()
80
+ .avg("accrualrate").sum("baseamount").sum("accruedamt").finish()
81
+ .select(new String[]{"0 as org", "0 as sourcetype", "accrualrate", "baseamount", "accruedamt",
82
+ "2 as sumlevel", "1 as sortgroup"});
83
+
84
+ // 各部分的列相同、顺序相同;每行小计跟在它所属组织的明细之后。
85
+ // 排序键不是报表列,行排好序后就移除
86
+ return detail.union(subtotal).union(total)
87
+ .orderBy(new String[]{"sortgroup", "org", "sumlevel", "sourcetype"})
88
+ .removeFields(new String[]{"sortgroup"});
89
+ }
90
+ }
91
+ ```
92
+
93
+ 表单插件:给带标记的行打标签,置空在这些行上没有意义的列:
94
+
95
+ ```java
96
+ package kd.bos.plugin.sample.report.bizcase;
97
+
98
+ import kd.bos.dataentity.resource.ResManager;
99
+ import kd.bos.entity.datamodel.events.PackageDataEvent;
100
+ import kd.bos.entity.report.ReportColumn;
101
+ import kd.bos.report.plugin.AbstractReportFormPlugin;
102
+
103
+ public class ReserveSumReportFormPlugin extends AbstractReportFormPlugin {
104
+
105
+ @Override
106
+ public void packageData(PackageDataEvent packageDataEvent) {
107
+ super.packageData(packageDataEvent);
108
+ if (!(packageDataEvent.getSource() instanceof ReportColumn)) {
109
+ return;
110
+ }
111
+ String fieldKey = ((ReportColumn) packageDataEvent.getSource()).getFieldKey();
112
+ int sumLevel = packageDataEvent.getRowData().getInt("sumlevel");
113
+ if (sumLevel == 0) {
114
+ return;
115
+ }
116
+ if (sumLevel == 1 && "sourcetype".equals(fieldKey)) {
117
+ packageDataEvent.setFormatValue(ResManager.loadKDString("小计", "ReserveSumReportFormPlugin_0", "xxx-report"));
118
+ } else if (sumLevel == 2 && "org".equals(fieldKey)) {
119
+ packageDataEvent.setFormatValue(ResManager.loadKDString("合计", "ReserveSumReportFormPlugin_1", "xxx-report"));
120
+ } else if ("accrualrate".equals(fieldKey)) {
121
+ // 平均出来的比率对合计行的读者没有意义
122
+ packageDataEvent.setFormatValue(null);
123
+ }
124
+ // 合计类的行永远不与周围的明细行融合
125
+ packageDataEvent.getNoMergeKey().add(fieldKey);
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## 注意事项
131
+ - `union` 要求各部分的列名相同、顺序相同:给每个层级一个列出全部列的 `select`,没有值的列用常量补齐——数字或 id 用 `"0 as x"`,文本用 `"'' as x"`
132
+ - 先按维度、再按 `sumlevel` 排序,每行小计才会跟在它那一组的明细之后。合计行的占位维度(`0`)会让它排到最前面,所以在维度之前给它一个专属排序键(上面的 `sortgroup`),排序后再移除:返回的列就是已声明的报表列
133
+ - `''` 常量没有类型(`UnknownType`),在这样的列上排序会失败;不要把占位文本列放进 `orderBy`,或者先声明它的类型(`report-dataset-assembly.md`)
134
+ - 每个层级都会消费它的输入:在第一次聚合之前,每多一个层级就 `copy()` 一次明细 DataSet
135
+ - 比率取平均(`avg`),不要求和;平均值对读者没有意义时,像标品那样在合计类的行上置空
136
+ - 把 `sumlevel` 也声明为报表列——隐藏(`hide: true`)、`integer`、`fieldKey` 等于它的 `dateIndex`——`packageData` 才能读到它:`reportform` 插件看到的行数据是按已声明的列打包的
137
+ - 用数字标记层级,不要用标签文本:在另一个插件里比较翻译后的标签,翻译不一致就会失效,而 `sumlevel` 的 0 / 1 / 2 不会
138
+ - 合计类行的标签是在 `packageData` 里设置的显示值;数据里仍是占位值。图表等其他消费方先用 `filter("sumlevel=0")` 去掉合计类的行
139
+ - 大数据量下多次 union 和排序都会重新执行算子;给算法键起好名字,让耗时在性能监控里看得出来
@@ -5,14 +5,16 @@ source_doc: "bos-runtime-plugin 报表示例"
5
5
  last_updated: "2026-04-03"
6
6
  status: "approved"
7
7
  scene_name: "报表树数据插件"
8
- related_apis: ["AbstractReportTreeDataPlugin", "TreeNode", "query", "ReportQueryParam", "addChild"]
8
+ related_apis: ["AbstractReportTreeDataPlugin.query", "TreeNode.addChild", "TreeNode.setId", "TreeNode.setParentid", "TreeNode.setText", "TreeNode.setLeaf", "TreeNode.setIsOpened", "TreeNode.setChildren", "ReportQueryParam.getFilter", "FilterInfo.getDynamicObjectCollection", "QueryServiceHelper.queryDataSet"]
9
9
  topic: "报表树数据"
10
10
  ---
11
11
 
12
12
  # 场景:报表树数据插件
13
13
 
14
14
  ## 适用情况
15
- 实现报表左侧树形导航的数据构造,用于左树右表布局的报表。通过重写 `query` 方法返回树节点列表,用户点击树节点后,节点数据会传递给右侧报表数据查询插件的 `query` 方法的 `selectedObj` 参数。适用于按组织、部门、分类等维度展示树形导航的报表场景。
15
+ 实现报表左侧树形导航的数据构造,用于左树右表布局的报表。通过重写 `query` 方法返回树节点列表,用户点击树节点后,节点 id 会传递给右侧报表数据查询插件的 `query` 方法的 `selectedObj` 参数。适用于按组织、部门、分类等维度展示树形导航的报表场景。
16
+
17
+ 没有任何公共报表模板预置左树(`bos_grouprpttpl` 是分组报表):只有用户在设计器里新增「报表树」控件(`ReportTreeAp`)后报表才有左树。本插件通过 `setReportProperties --target tree` 注册在该左树自己的位置 `ReportTreeAp.ReportPlugin`——绝不作为 `reportPlugins` 条目,那是右侧列表的取数插件。
16
18
 
17
19
  ## 事件/方法说明
18
20
 
@@ -77,12 +79,14 @@ import kd.bos.algo.Row;
77
79
  import kd.bos.entity.report.AbstractReportTreeDataPlugin;
78
80
  import kd.bos.entity.report.ReportQueryParam;
79
81
  import kd.bos.entity.tree.TreeNode;
80
- import kd.bos.orm.ORM;
81
82
  import kd.bos.orm.query.QCP;
82
83
  import kd.bos.orm.query.QFilter;
84
+ import kd.bos.servicehelper.QueryServiceHelper;
83
85
 
84
86
  public class ReportTreeDataDynamicSample extends AbstractReportTreeDataPlugin {
85
87
 
88
+ private static final String PARENT = "parent";
89
+
86
90
  @Override
87
91
  public List<TreeNode> query(ReportQueryParam queryParam, String nodeId) throws Throwable {
88
92
  List<TreeNode> nodes = new ArrayList<>();
@@ -90,22 +94,21 @@ public class ReportTreeDataDynamicSample extends AbstractReportTreeDataPlugin {
90
94
  // 构建查询条件:首次加载查根节点,展开时查子节点
91
95
  List<QFilter> filters = new ArrayList<>();
92
96
  if (nodeId == null || "0".equals(nodeId)) {
93
- filters.add(new QFilter("parent", QCP.equals, 0L));
97
+ filters.add(new QFilter(PARENT, QCP.equals, 0L));
94
98
  } else {
95
- filters.add(new QFilter("parent", QCP.equals, Long.parseLong(nodeId)));
99
+ filters.add(new QFilter(PARENT, QCP.equals, Long.parseLong(nodeId)));
96
100
  }
97
101
 
98
- ORM orm = ORM.create();
99
- DataSet ds = orm.queryDataSet(getClass().getName(),
102
+ DataSet ds = QueryServiceHelper.queryDataSet(getClass().getName(),
100
103
  "bos_org", "id,number,name,parent,isleaf",
101
- filters.toArray(new QFilter[0]), "number");
104
+ filters.toArray(new QFilter[filters.size()]), "number");
102
105
  try {
103
106
  while (ds.hasNext()) {
104
107
  Row row = ds.next();
105
108
  TreeNode node = new TreeNode();
106
109
  node.setId(String.valueOf(row.getLong("id")));
107
110
  node.setText(row.getString("number") + " " + row.getString("name"));
108
- node.setParentid(String.valueOf(row.getLong("parent")));
111
+ node.setParentid(String.valueOf(row.getLong(PARENT)));
109
112
  node.setLeaf(row.getBoolean("isleaf"));
110
113
  node.setIsOpened(false);
111
114
  nodes.add(node);
@@ -119,11 +122,104 @@ public class ReportTreeDataDynamicSample extends AbstractReportTreeDataPlugin {
119
122
  }
120
123
  ```
121
124
 
125
+ 一次构建整棵树,跟随过滤面板,挂在一个固定的根节点下:
126
+
127
+ ```java
128
+ package kd.bos.plugin.sample.report.bizcase;
129
+
130
+ import java.util.ArrayList;
131
+ import java.util.HashSet;
132
+ import java.util.List;
133
+ import java.util.Set;
134
+
135
+ import kd.bos.algo.DataSet;
136
+ import kd.bos.algo.Row;
137
+ import kd.bos.dataentity.entity.DynamicObject;
138
+ import kd.bos.dataentity.entity.DynamicObjectCollection;
139
+ import kd.bos.dataentity.resource.ResManager;
140
+ import kd.bos.entity.report.AbstractReportTreeDataPlugin;
141
+ import kd.bos.entity.report.FilterInfo;
142
+ import kd.bos.entity.report.ReportQueryParam;
143
+ import kd.bos.entity.tree.TreeNode;
144
+ import kd.bos.orm.query.QCP;
145
+ import kd.bos.orm.query.QFilter;
146
+ import kd.bos.servicehelper.QueryServiceHelper;
147
+
148
+ public class CustomerTreeReportPlugin extends AbstractReportTreeDataPlugin {
149
+
150
+ private static final String ALGO_NAME = "xxx.report.customertree";
151
+ // 取数插件把这个 id 理解为"不收窄"
152
+ private static final String ROOT_ID = "all";
153
+
154
+ @Override
155
+ public List<TreeNode> query(ReportQueryParam queryParam, String nodeId) throws Throwable {
156
+ // 树与右侧的行使用同样的面板条件
157
+ FilterInfo filter = queryParam.getFilter();
158
+ List<QFilter> filters = new ArrayList<>(2);
159
+ filters.add(new QFilter("billstatus", QCP.equals, "C"));
160
+ DynamicObjectCollection orgs = filter.getDynamicObjectCollection("org");
161
+ if (orgs != null && !orgs.isEmpty()) {
162
+ List<Object> orgIds = new ArrayList<>(orgs.size());
163
+ for (DynamicObject org : orgs) {
164
+ orgIds.add(org.getPkValue());
165
+ }
166
+ filters.add(new QFilter("org", QCP.in, orgIds));
167
+ }
168
+ // 一次查出所有节点,每个客户一行
169
+ DataSet customers = QueryServiceHelper.queryDataSet(ALGO_NAME, "xxx_salesorder",
170
+ "customer.id as customer, customer.name as name, customer.masterid as masterid",
171
+ filters.toArray(new QFilter[filters.size()]), null)
172
+ .groupBy(new String[]{"customer", "name", "masterid"}).finish();
173
+ List<TreeNode> children = new ArrayList<>(16);
174
+ Set<Long> seenMasters = new HashSet<>(16);
175
+ try {
176
+ while (customers.hasNext()) {
177
+ Row row = customers.next();
178
+ Long masterId = row.getLong("masterid");
179
+ // 同一个逻辑客户可能有多个 id:每个 masterid 只保留一个节点
180
+ if (masterId != null && masterId != 0L && !seenMasters.add(masterId)) {
181
+ continue;
182
+ }
183
+ TreeNode node = new TreeNode();
184
+ node.setId(String.valueOf(row.getLong("customer")));
185
+ node.setParentid(ROOT_ID);
186
+ node.setText(row.getString("name"));
187
+ node.setLeaf(true);
188
+ children.add(node);
189
+ }
190
+ } finally {
191
+ customers.close();
192
+ }
193
+ TreeNode root = new TreeNode();
194
+ root.setId(ROOT_ID);
195
+ root.setParentid("");
196
+ root.setText(ResManager.loadKDString("全部", "CustomerTreeReportPlugin_0", "xxx-report"));
197
+ root.setIsOpened(true);
198
+ root.setChildren(children);
199
+ List<TreeNode> nodes = new ArrayList<>(1);
200
+ nodes.add(root);
201
+ return nodes;
202
+ }
203
+ }
204
+ ```
205
+
206
+ 右侧的 `reportlist` 插件按被点击节点的 id 收窄它的行:
207
+
208
+ ```java
209
+ // selectedObj 是被点击节点的 id;根节点表示所有客户
210
+ if (selectedObj != null && !"all".equals(String.valueOf(selectedObj))) {
211
+ filters.add(new QFilter("customer", QCP.equals, Long.valueOf(String.valueOf(selectedObj))));
212
+ }
213
+ ```
214
+
122
215
  ## 注意事项
123
216
  - `query` 为抽象方法,必须实现,返回 `List<TreeNode>` 不可为 null
124
217
  - 此插件不继承 AbstractFormPlugin,不能调用 `getView()`、`getModel()` 等表单方法
125
218
  - 首次加载时 `nodeId` 为 null,必须处理此情况并返回根节点列表
126
- - `TreeNode` 的 ID 必须唯一,否则树节点展示异常
127
- - 用户点击树节点后,节点对应的数据对象会传递给 reportlistdata 插件的 `query` 方法的 `selectedObj` 参数
128
- - 使用 ORM 查询构建树节点时,DataSet 必须在遍历完成后关闭
219
+ - `TreeNode` 的 ID 必须唯一,否则树节点展示异常——并且跨次查询保持稳定,否则选中态与它的行会脱钩
220
+ - 用户点击树节点后,节点的 id(`TreeNode.getId()`,`String`)会传递给 reportlistdata 插件的 `query` 方法的 `selectedObj` 参数;想清楚根节点的 id 对取数插件意味着什么(这里的 "all":不收窄),把它构造成固定值,不要拼接子节点 id
221
+ - 树跟随过滤面板:从 `queryParam.getFilter()` 读取取数插件同样读取的条件,用它们限定节点范围或选择实体(标品按往来方向过滤在应付单与应收单之间切换)
222
+ - 基础资料做过个性化处理后,同一个逻辑记录可能有多个物理 id;按 `masterid` 去重,否则同一个客户会出现两次,它的行在根节点下会被重复计算
223
+ - 一次批量查出节点;逐行加载记录(循环里 `loadSingle`)只适合少量节点
224
+ - 使用 `QueryServiceHelper` 查询构建树节点时,DataSet 必须在遍历完成后关闭
129
225
  - `TreeNode.setLeaf(false)` 使节点显示展开箭头,叶子节点使用 `setLeaf(true)`。不存在 `setIsParent` 设置方法 —— 惰加载场景用 `setChildren(new ArrayList<>())` 表示子节点尚未加载
@@ -0,0 +1,181 @@
1
+ ---
2
+ version: "1.0.0"
3
+ framework_version: "8.0.1"
4
+ source_doc: "报表插件示例 示例5、示例8;标品报表案例 11"
5
+ last_updated: "2026-10-19"
6
+ status: "approved"
7
+ scene_name: "折叠树形报表列表"
8
+ related_apis: ["AbstractReportFormPlugin.setTreeReportList", "TreeReportListEvent.setTreeReportList", "TreeReportListEvent.setTreeExpandColId", "ReportQueryParam.setTreeReportList", "ReportList.expandAllNode", "ReportList.collapseAllNode", "Algo.create", "Algo.createDataSet", "RowMeta.getField", "Field.setDataType", "DataSet.getRowMeta", "QueryServiceHelper.queryDataSet"]
9
+ topic: "报表树数据"
10
+ ---
11
+
12
+ # 场景:折叠树形报表列表
13
+
14
+ ## 适用情况
15
+ 同一个报表行集以可折叠的树形展示:分组行可以展开成它的明细行,可能有多层——成本项下挂单据、期间下挂单据。这里不涉及树插件,也不涉及报表树控件。由 `reportform` 插件开启树形展示,`reportlist` 取数插件把层级关系作为普通行返回,每行带三列:`rowid`、`pid`、`isgroupnode`。
16
+
17
+ 这不是左树导航。列表旁边的左树是另一个 `reporttreelist` 插件,用在用户给报表新增的报表树控件上,注册在 `ReportTreeAp.ReportPlugin`——见 `report-tree-data-query.md`。
18
+
19
+ ## 事件/方法说明
20
+
21
+ - `setTreeReportList(TreeReportListEvent)`(reportform):`event.setTreeReportList(true)` 让列表以树形渲染,`event.setTreeExpandColId(columnKey)` 把展开控件放在该列上。在 `beforeQuery` 中调用 `ReportQueryParam.setTreeReportList(true)` 效果相同
22
+ - `ReportList.expandAllNode()` / `collapseAllNode()`(7.0.1 及以后):一键展开或收起所有节点,通常由两个工具栏按钮触发
23
+ - 取数插件返回的行列:
24
+
25
+ | 列 | 类型 | 值 |
26
+ |---|---|---|
27
+ | `rowid` | 文本 | 行自身的 id,在结果中唯一 |
28
+ | `pid` | 文本 | 父行的 `rowid`;顶层行为 `"0"` |
29
+ | `isgroupnode` | 整数 | 可折叠的分组行为 `1`,明细行为 `0` |
30
+
31
+ ## 方法签名
32
+ ```java
33
+ // reportform 插件
34
+ @Override
35
+ public void setTreeReportList(TreeReportListEvent event)
36
+
37
+ // reportlist 插件
38
+ @Override
39
+ public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable
40
+ ```
41
+
42
+ ## 代码示例
43
+ `extracted` `needs-verification`
44
+
45
+ 表单插件:开启树形展示,并从工具栏一键展开 / 收起:
46
+
47
+ ```java
48
+ package kd.bos.plugin.sample.report.bizcase;
49
+
50
+ import java.util.EventObject;
51
+
52
+ import kd.bos.form.control.events.ItemClickEvent;
53
+ import kd.bos.report.ReportList;
54
+ import kd.bos.report.events.TreeReportListEvent;
55
+ import kd.bos.report.plugin.AbstractReportFormPlugin;
56
+
57
+ public class CostTreeReportFormPlugin extends AbstractReportFormPlugin {
58
+
59
+ @Override
60
+ public void registerListener(EventObject e) {
61
+ super.registerListener(e);
62
+ this.addItemClickListeners("toolbarap");
63
+ }
64
+
65
+ @Override
66
+ public void setTreeReportList(TreeReportListEvent event) {
67
+ super.setTreeReportList(event);
68
+ event.setTreeReportList(true);
69
+ // 承载展开控件的列
70
+ event.setTreeExpandColId("itemname");
71
+ }
72
+
73
+ @Override
74
+ public void itemClick(ItemClickEvent evt) {
75
+ super.itemClick(evt);
76
+ ReportList reportList = this.getControl("reportlistap");
77
+ if (reportList == null) {
78
+ return;
79
+ }
80
+ // 两个工具栏项,各自绑定一个 donothing 操作
81
+ if ("expand".equals(evt.getItemKey())) {
82
+ reportList.expandAllNode();
83
+ } else if ("collapse".equals(evt.getItemKey())) {
84
+ reportList.collapseAllNode();
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ 取数插件:每个成本项一行分组行,后面跟着它的明细行:
91
+
92
+ ```java
93
+ package kd.bos.plugin.sample.report.bizcase;
94
+
95
+ import java.math.BigDecimal;
96
+ import java.util.ArrayList;
97
+ import java.util.List;
98
+ import java.util.Objects;
99
+
100
+ import kd.bos.algo.Algo;
101
+ import kd.bos.algo.DataSet;
102
+ import kd.bos.algo.DataType;
103
+ import kd.bos.algo.Field;
104
+ import kd.bos.algo.Row;
105
+ import kd.bos.algo.RowMeta;
106
+ import kd.bos.entity.report.AbstractReportListDataPlugin;
107
+ import kd.bos.entity.report.ReportQueryParam;
108
+ import kd.bos.orm.query.QFilter;
109
+ import kd.bos.servicehelper.QueryServiceHelper;
110
+
111
+ public class CostTreeReportListDataPlugin extends AbstractReportListDataPlugin {
112
+
113
+ private static final String ALGO_NAME = "xxx.report.costtree";
114
+ private static final String ENTITY_NUMBER = "xxx_costrecord";
115
+ private static final String TOP_PID = "0";
116
+ private static final int AMOUNT = 5;
117
+
118
+ @Override
119
+ public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {
120
+ List<QFilter> filters = new ArrayList<>(queryParam.getFilter().getQFilters());
121
+ // ……过滤面板条件照常在这里追加
122
+
123
+ // 按分组键排序,同一组的明细才会相邻
124
+ DataSet detail = QueryServiceHelper.queryDataSet(ALGO_NAME, ENTITY_NUMBER,
125
+ "costitem.name as itemname, billno, amount",
126
+ filters.toArray(new QFilter[filters.size()]), "costitem.name, billno");
127
+
128
+ RowMeta rowMeta = new RowMeta(new Field[]{
129
+ new Field("rowid", DataType.StringType),
130
+ new Field("pid", DataType.StringType),
131
+ new Field("isgroupnode", DataType.IntegerType),
132
+ new Field("itemname", DataType.StringType),
133
+ new Field("billno", DataType.StringType),
134
+ new Field("amount", DataType.BigDecimalType)});
135
+ List<Object[]> rows = new ArrayList<>(16);
136
+ Object[] group = null;
137
+ int groupNo = 0;
138
+ int detailNo = 0;
139
+ try {
140
+ while (detail.hasNext()) {
141
+ Row row = detail.next();
142
+ String item = row.getString("itemname");
143
+ BigDecimal amount = row.getBigDecimal("amount") == null ? BigDecimal.ZERO : row.getBigDecimal("amount");
144
+ if (group == null || !Objects.equals(item, group[3])) {
145
+ // 分组行排在它的明细行之前;它的金额是明细金额之和
146
+ groupNo++;
147
+ detailNo = 0;
148
+ group = new Object[]{"g" + groupNo, TOP_PID, 1, item, "", BigDecimal.ZERO};
149
+ rows.add(group);
150
+ }
151
+ detailNo++;
152
+ group[AMOUNT] = ((BigDecimal) group[AMOUNT]).add(amount);
153
+ // 明细行的 pid 是它所属分组行的 rowid
154
+ rows.add(new Object[]{"g" + groupNo + "-" + detailNo, group[0], 0, item, row.getString("billno"), amount});
155
+ }
156
+ } finally {
157
+ detail.close();
158
+ }
159
+ return Algo.create(ALGO_NAME).createDataSet(rows, rowMeta);
160
+ }
161
+ }
162
+ ```
163
+
164
+ 如果这几列来自查询(`"'' as rowid, '' as pid, 0 as isgroupnode"`),`''` 常量没有类型;像标品那样,在任何 `orderBy` 之前先声明类型:
165
+
166
+ ```java
167
+ for (String name : new String[]{"rowid", "pid"}) {
168
+ dataSet.getRowMeta().getField(name).setDataType(kd.bos.algo.DataType.StringType);
169
+ }
170
+ ```
171
+
172
+ ## 注意事项
173
+ - 这三列是 `rowid`、`pid`、`isgroupnode`,与标品树形报表的构造方式一致。行自身的 id 用 `rowid`,不是 `id`:不要用 `id` / `pid` 代替这三列
174
+ - `rowid` 在结果中唯一,对同样的数据保持稳定;用分组的位置或键来构造(`g1`、`g1-1`)。明细行的 `pid` 是它所属分组行的 `rowid`,顶层行的 `pid` 为 `"0"`
175
+ - 每个分组行先于它的子行输出,与标品一致:标品深度优先遍历行组,先写父行,再写子分组或明细行
176
+ - 分组行的数值来自取数插件——读明细时累加,或用 `groupBy` 聚合——标品也是自己计算每组的汇总行
177
+ - 把 `rowid`、`pid`、`isgroupnode` 像插件读取的其他列一样声明为隐藏报表列(`hide: true`):行数据按已声明的列打包
178
+ - 某一层由其他层计算得出时(期末 = 累计 − 发出),用内存中的分组模型比用 union 更容易:先收集每组的汇总行和子项,再一次性写出所有行,标品的成本报表就是这样做的
179
+ - 一键展开 / 收起需要两个绑定 donothing 操作的工具栏项,并在 `registerListener` 中注册工具栏监听。报表示例还在 `initialize` 中再注册了一次;这第二次调用不起作用——`Toolbar.addItemClickListener` 会忽略已持有的监听——而且编译边界会拒绝在 `initialize` 中添加的监听(`PLUGIN_SOURCE_LIFECYCLE_MISUSE`),所以只在 `registerListener` 中注册
180
+ - 树形列表上表头过滤默认返回树形结果;报表示例在 `setTreeReportList` 中用 `event.setCol2ndResultMode(TreeDataMode.FLAT)` 设为平铺。该方法和 `TreeDataMode` 在 8.0 SDK 中都不存在——其 `lib/bos` 与 `lib/trd` 的任何类都没有提到这两个名字——所以在 8.0 上不要写;目标是更新的版本时,只有 `references/sdk-inspection.md` 在其 SDK 上找到它后才使用
181
+ - 遍历过的 DataSet 已被消费:需要再次读取时,在第一次遍历之前 `copy()`,并像上面那样关闭插件自己持有的 DataSet
@@ -154,3 +154,6 @@ public class ReportFormPluginSample extends AbstractReportFormPlugin {
154
154
  - `processRowData` 和 `preProcessExportData` 中不要循环取数,导出时可能有性能问题
155
155
  - `packageData` 在数据发送到前端时触发,每个单元格进入一次
156
156
  - `setExcelName` 只取 list 的第一个元素作为文件名
157
+ - `setSortAndFilter` 只负责开启表头过滤:报表数据查询插件必须在 `query` 中应用 `getFilter().getTableHeadFilterItems()`(或 `getHeadFilters()`),否则表头过滤不起任何作用——见 `report/report-head-filter.md`
158
+ - `MergeColumnRule`:一个合并字段只能属于一条规则,合并跟随分组字段——只要分组字段的值相同,合并字段就合并,不会比较合并字段自己相邻行的值(`report/report-merge-column.md`)
159
+ - `report/` 下的其他报表场景覆盖了工具栏按钮与行数据、钻取联查、小计合计行、折叠树形列表、查询开关、图表以及报表打印
@@ -7,7 +7,7 @@ status: "approved"
7
7
  scene_name: "树形基础资料级联操作确认"
8
8
  trigger_event: "beforeItemClick → confirmCallBack"
9
9
  scene_keywords: ["beforeItemClick", "confirmCallBack", "MessageBoxResult", "树形基础资料", "级联删除", "级联禁用", "ConfirmCallBackListener", "invokeOperation"]
10
- related_apis: ["BeforeItemClickEvent.setCancel", "IFormView.showConfirm", "ConfirmCallBackListener", "MessageBoxClosedEvent", "MessageBoxResult", "IFormView.invokeOperation", "ORM.exists"]
10
+ related_apis: ["BeforeItemClickEvent.setCancel", "IFormView.showConfirm", "ConfirmCallBackListener", "MessageBoxClosedEvent", "MessageBoxResult", "IFormView.invokeOperation", "QueryServiceHelper.query", "QueryServiceHelper.exists"]
11
11
  ---
12
12
 
13
13
  # 场景:树形基础资料级联操作确认
@@ -25,7 +25,7 @@ related_apis: ["BeforeItemClickEvent.setCancel", "IFormView.showConfirm", "Confi
25
25
  | ConfirmCallBackListener | 确认框回调监听器 | — |
26
26
  | MessageBoxClosedEvent | 确认框关闭事件,获取用户选择结果 | — |
27
27
  | MessageBoxResult | 用户选择结果枚举(Yes/No/Cancel) | — |
28
- | ORM | 查询子节点是否存在 | — |
28
+ | QueryServiceHelper | 查询子节点是否存在 | — |
29
29
 
30
30
  ## 方法签名
31
31
  ```java
@@ -57,35 +57,38 @@ import kd.bos.form.MessageBoxOptions;
57
57
  import kd.bos.form.MessageBoxResult;
58
58
  import kd.bos.form.control.events.BeforeItemClickEvent;
59
59
  import kd.bos.form.events.MessageBoxClosedEvent;
60
- import kd.bos.orm.ORM;
61
60
  import kd.bos.orm.query.QCP;
62
61
  import kd.bos.orm.query.QFilter;
62
+ import kd.bos.servicehelper.QueryServiceHelper;
63
63
 
64
64
  public class XxxTreeBaseDataPlugin extends AbstractBasePlugIn {
65
65
 
66
+ private static final String ENTITY_NUMBER = "target_entity";
67
+ private static final String LONG_NUMBER = "longnumber";
68
+ private static final String BAR_DISABLE = "bar_disable";
69
+
66
70
  @Override
67
71
  public void beforeItemClick(BeforeItemClickEvent evt) {
68
72
  super.beforeItemClick(evt);
69
- if ("bar_disable".equals(evt.getItemKey())
73
+ if (BAR_DISABLE.equals(evt.getItemKey())
70
74
  || "bar_delete".equals(evt.getItemKey())) {
71
75
  // 查询当前节点的长编码
72
- ORM orm = ORM.create();
73
76
  Object pkValue = this.getModel().getDataEntity().getPkValue();
74
77
  QFilter q = new QFilter("id", QCP.equals, pkValue);
75
- DynamicObjectCollection col = orm.query(
76
- "target_entity", "id,longnumber", new QFilter[]{q});
78
+ DynamicObjectCollection col = QueryServiceHelper.query(
79
+ ENTITY_NUMBER, "id,longnumber", new QFilter[]{q});
77
80
 
78
81
  if (col.isEmpty()) {
79
82
  return;
80
83
  }
81
84
  // 检查是否存在以当前长编码为前缀的子节点
82
- String longNumber = col.get(0).getString("longnumber");
83
- QFilter childFilter = new QFilter("longnumber", QCP.like,
85
+ String longNumber = col.get(0).getString(LONG_NUMBER);
86
+ QFilter childFilter = new QFilter(LONG_NUMBER, QCP.like,
84
87
  longNumber + ".%");
85
- if (orm.exists("target_entity", new QFilter[]{childFilter})) {
88
+ if (QueryServiceHelper.exists(ENTITY_NUMBER, new QFilter[]{childFilter})) {
86
89
  // 存在子节点,拦截操作并弹出确认框
87
90
  evt.setCancel(true);
88
- if ("bar_disable".equals(evt.getItemKey())) {
91
+ if (BAR_DISABLE.equals(evt.getItemKey())) {
89
92
  this.getView().showConfirm("禁用提示",
90
93
  "当前节点有下级,此次禁用将禁用所有下级,是否继续?",
91
94
  MessageBoxOptions.YesNo, null,
@@ -96,7 +96,7 @@ public class XxxTreeListPlugin extends StandardTreeListPlugin {
96
96
  super.beforeItemClick(evt);
97
97
  if ("tbldel".equals(evt.getItemKey())) {
98
98
  // 校验是否选中了数据
99
- if (((IListView) getView()).getSelectedRows().size() > 0) {
99
+ if (!((IListView) getView()).getSelectedRows().isEmpty()) {
100
100
  // 弹出二次确认框
101
101
  ConfirmCallBackListener listener =
102
102
  new ConfirmCallBackListener(DELETE_CONFIRM, this);
@@ -99,7 +99,7 @@ public class XxxTreeListPlugin extends AbstractTreeListPlugin {
99
99
  } else if (matchIds.size() == 1) {
100
100
  return new QFilter(fieldKey, QCP.equals, matchIds.get(0));
101
101
  } else {
102
- return new QFilter(fieldKey, QCP.in, matchIds.toArray(new String[0]));
102
+ return new QFilter(fieldKey, QCP.in, matchIds.toArray(new String[matchIds.size()]));
103
103
  }
104
104
  }
105
105
  }