@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
- package/skills/cosmic-build/SKILL.md +24 -9
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
- package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
- package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
- package/skills/cosmic-build/references/compile-guide.md +36 -1
- package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
- package/skills/cosmic-build/references/delivery.md +16 -2
- package/skills/cosmic-build/references/execution-contract.md +15 -3
- package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
- package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
- package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
- package/skills/cosmic-build/references/report-filter-contract.md +53 -15
- package/skills/cosmic-build/references/sdk-inspection.md +4 -1
- package/skills/cosmic-build/references/type-registry.json +26 -17
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
- package/skills/cosmic-build/scripts/_http_client.py +3 -0
- package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
- package/skills/cosmic-build/scripts/configure_list.py +112 -0
- package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
- package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
- package/skills/cosmic-build/scripts/execution_binding.py +25 -0
- package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
- package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
- package/skills/cosmic-build/scripts/register_plugins.py +27 -10
- package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
- package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
- package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
- package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
- package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
- package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
- package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
- package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
- package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
- package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
- package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
- package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
- package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
- package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
- package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
- package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
- package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
- package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
- package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
- package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
- package/skills/cosmic-build/scripts/test_environment.py +67 -1
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
- package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
- package/skills/cosmic-dev-best-practices/references/README.md +2 -2
- package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
- package/skills/erp-analysis/references/delivery-contract.md +2 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# 黄金示例:成本分析报表(左树右表)
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> `reporttreelist`
|
|
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
|
-
|
|
24
|
+
被点击节点的 id 作为 `selectedObj` 传入,用来收窄数据行
|
|
21
25
|
|
|
22
|
-
|
|
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
|
-
|
|
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. `
|
|
130
|
-
5. `registerReportPlugin`
|
|
131
|
-
|
|
132
|
-
|
|
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 =
|
|
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.
|
|
157
|
-
node.
|
|
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
|
-
|
|
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
|
|
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[
|
|
227
|
+
filters.toArray(new QFilter[filters.size()]), "cost_item");
|
|
195
228
|
}
|
|
196
229
|
}
|
|
197
230
|
```
|
|
198
231
|
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
269
|
+
- `CostAnalysisReportPlugin` 位于报表取数插件槽位;用户新增报表树控件之后,`CostAnalysisTreePlugin`
|
|
270
|
+
位于左树。两者都作为 Java 源码交付
|
package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json
CHANGED
|
@@ -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",
|
package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md
CHANGED
|
@@ -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
|
-
|
|
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-
|
|
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:
|
|
316
|
-
#
|
|
317
|
-
#
|
|
318
|
-
# the
|
|
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
|
-
|
|
329
|
+
errors.append(Issue(
|
|
321
330
|
rule="R-property-tree",
|
|
322
|
-
severity="
|
|
331
|
+
severity="error",
|
|
323
332
|
message=(
|
|
324
|
-
"reportProperties.tree applies to ReportTreeAp, which
|
|
325
|
-
"
|
|
326
|
-
"
|
|
327
|
-
"
|
|
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.
|
|
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.
|
|
439
|
-
|
|
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
|
-
#
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
"""
|
|
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).
|
|
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
|
|
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.
|
|
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():
|