@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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
package kd.bos.app.uhyl.bfy11.operate;
|
|
2
|
+
|
|
3
|
+
import java.util.List;
|
|
4
|
+
|
|
5
|
+
import kd.bos.dataentity.entity.DynamicObject;
|
|
6
|
+
import kd.bos.dataentity.entity.DynamicObjectCollection;
|
|
7
|
+
import kd.bos.entity.ExtendedDataEntity;
|
|
8
|
+
import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
|
|
9
|
+
import kd.bos.entity.plugin.AddValidatorsEventArgs;
|
|
10
|
+
import kd.bos.entity.plugin.PreparePropertysEventArgs;
|
|
11
|
+
import kd.bos.entity.validate.AbstractValidator;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 定场单提交校验操作插件。
|
|
15
|
+
*
|
|
16
|
+
* 提交时逐行校验订场明细:
|
|
17
|
+
* 1. 结束时间必须晚于开始时间
|
|
18
|
+
* 2. 时段必须在营业时间 08:00–22:00 之内
|
|
19
|
+
*
|
|
20
|
+
* 批量提交多张单时每张单独立判断,合格的照常提交,不合格的给出原因。
|
|
21
|
+
*/
|
|
22
|
+
public class UhylBookingOrderOpPlugin extends AbstractOperationServicePlugIn {
|
|
23
|
+
|
|
24
|
+
private static final String ENTRY_COLLECTION = "uhyl_book_order_detail";
|
|
25
|
+
private static final String FIELD_START_TIME = "uhyl_start_time";
|
|
26
|
+
private static final String FIELD_END_TIME = "uhyl_end_time";
|
|
27
|
+
private static final String FIELD_BILLNO = "billno";
|
|
28
|
+
|
|
29
|
+
/** 营业开始时间 08:00:00(午夜后秒数) */
|
|
30
|
+
private static final int BUSINESS_OPEN_SECONDS = 8 * 3600;
|
|
31
|
+
/** 营业结束时间 22:00:00(午夜后秒数) */
|
|
32
|
+
private static final int BUSINESS_CLOSE_SECONDS = 22 * 3600;
|
|
33
|
+
|
|
34
|
+
@Override
|
|
35
|
+
public void onPreparePropertys(PreparePropertysEventArgs e) {
|
|
36
|
+
super.onPreparePropertys(e);
|
|
37
|
+
List<String> fieldKeys = e.getFieldKeys();
|
|
38
|
+
fieldKeys.add(FIELD_BILLNO);
|
|
39
|
+
fieldKeys.add(ENTRY_COLLECTION);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Override
|
|
43
|
+
public void onAddValidators(AddValidatorsEventArgs e) {
|
|
44
|
+
super.onAddValidators(e);
|
|
45
|
+
e.addValidator(new AbstractValidator() {
|
|
46
|
+
@Override
|
|
47
|
+
public void validate() {
|
|
48
|
+
for (ExtendedDataEntity entity : this.getDataEntities()) {
|
|
49
|
+
DynamicObject dataEntity = entity.getDataEntity();
|
|
50
|
+
String billNo = dataEntity.getString(FIELD_BILLNO);
|
|
51
|
+
DynamicObjectCollection entryRows =
|
|
52
|
+
dataEntity.getDynamicObjectCollection(ENTRY_COLLECTION);
|
|
53
|
+
if (entryRows == null || entryRows.isEmpty()) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
for (int i = 0; i < entryRows.size(); i++) {
|
|
57
|
+
DynamicObject row = entryRows.get(i);
|
|
58
|
+
int rowNum = i + 1;
|
|
59
|
+
int startTime = row.getInt(FIELD_START_TIME);
|
|
60
|
+
int endTime = row.getInt(FIELD_END_TIME);
|
|
61
|
+
|
|
62
|
+
if (endTime <= startTime) {
|
|
63
|
+
this.addErrorMessage(entity,
|
|
64
|
+
"单据[" + billNo + "]第" + rowNum + "行:结束时间必须晚于开始时间");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (startTime < BUSINESS_OPEN_SECONDS
|
|
68
|
+
|| endTime > BUSINESS_CLOSE_SECONDS) {
|
|
69
|
+
this.addErrorMessage(entity,
|
|
70
|
+
"单据[" + billNo + "]第" + rowNum
|
|
71
|
+
+ "行:时段必须在营业时间 08:00–22:00 之内");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -73,3 +73,15 @@ print(json.dumps(cmd_formactions._verify_add_field_contract(
|
|
|
73
73
|
assert result["ok"] is False, scripts
|
|
74
74
|
assert "mustInput" in result["scope"], scripts
|
|
75
75
|
assert result["mustInputReadBack"]["observed"] is False, scripts
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_omitted_type_is_rejected_before_target_lookup_or_write():
|
|
79
|
+
for scripts in SCRIPT_TREES:
|
|
80
|
+
result = _run(scripts, """
|
|
81
|
+
result = cmd_formactions.do_add_field({}, None, "selected_form", "New business field", "business_field")
|
|
82
|
+
print(json.dumps(result))
|
|
83
|
+
""")
|
|
84
|
+
assert result["status"] is False
|
|
85
|
+
assert result["errorCode"] == "FIELD_SPEC_INVALID"
|
|
86
|
+
assert result["data"]["writeAttempted"] is False
|
|
87
|
+
assert "field-type" in result["message"]
|
|
@@ -11,7 +11,10 @@ APP_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def _load_sync_module():
|
|
14
|
-
|
|
14
|
+
# 同步器按设计只发布在规范树;显式运行中文镜像测试也使用同一个同步器。
|
|
15
|
+
root = next(root for root in (APP_BUILD_ROOT, *APP_BUILD_ROOT.parents)
|
|
16
|
+
if (root / "scripts" / "sync_generated_locale_assets.py").is_file())
|
|
17
|
+
path = root / "scripts" / "sync_generated_locale_assets.py"
|
|
15
18
|
spec = importlib.util.spec_from_file_location("sync_generated_locale_assets", path)
|
|
16
19
|
assert spec is not None and spec.loader is not None
|
|
17
20
|
module = importlib.util.module_from_spec(spec)
|
|
@@ -39,11 +42,22 @@ class GeneratedLocaleAssetsTest(unittest.TestCase):
|
|
|
39
42
|
"kingscript-gate-v1", "mcp-server-v1", "metalog-v1",
|
|
40
43
|
"operation-contract-harvest-v1", "operation-write-v1",
|
|
41
44
|
"plugin-handoff-v1", "property-policy-contract-v1", "python-runtime-v1",
|
|
42
|
-
"report-tests-v1", "report-write-v1", "rule-write-v1", "execution-recovery-v1", "plugin-handoff-query-v1"
|
|
45
|
+
"report-tests-v1", "report-write-v1", "rule-write-v1", "execution-recovery-v1", "plugin-handoff-query-v1",
|
|
46
|
+
"list-execution-v1", "report-repair-v1"},
|
|
43
47
|
set(result["families"]),
|
|
44
48
|
)
|
|
45
49
|
self.assertEqual(len(result["files"]), result["counts"]["in_sync"])
|
|
46
50
|
|
|
51
|
+
def test_report_repair_sync_preserves_localized_runtime_diagnostics(self) -> None:
|
|
52
|
+
module = _load_sync_module()
|
|
53
|
+
generated = {item for group in module.GENERATED_FAMILIES.values() for item in group}
|
|
54
|
+
for name in ("cmd_apps.py", "validators.py"):
|
|
55
|
+
self.assertNotIn(f"skills/cosmic-meta-api/scripts/_core/{name}", generated)
|
|
56
|
+
self.assertIn("skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py",
|
|
57
|
+
module.GENERATED_FAMILIES["report-repair-v1"])
|
|
58
|
+
result = module.inspect(["report-repair-v1"])
|
|
59
|
+
self.assertTrue(result["ok"], result)
|
|
60
|
+
|
|
47
61
|
def test_execution_recovery_mirrors_are_machine_synced(self) -> None:
|
|
48
62
|
module = _load_sync_module()
|
|
49
63
|
result = module.inspect(["execution-recovery-v1"])
|
|
@@ -60,6 +74,11 @@ class GeneratedLocaleAssetsTest(unittest.TestCase):
|
|
|
60
74
|
module = _load_sync_module()
|
|
61
75
|
family = module.GENERATED_FAMILIES["report-tests-v1"]
|
|
62
76
|
self.assertIn("scripts/test/test_plugin_handoff.py", family)
|
|
77
|
+
self.assertIn("scripts/test/test_dcs_code_gate.py", family)
|
|
78
|
+
self.assertIn("scripts/dcs_code_gate.py", module.GENERATED_FAMILIES["plugin-handoff-v1"])
|
|
79
|
+
self.assertIn("scripts/dcs-rule-catalog.json", module.GENERATED_FAMILIES["plugin-handoff-v1"])
|
|
80
|
+
result = module.inspect(["plugin-handoff-v1"])
|
|
81
|
+
self.assertTrue(result["ok"], result)
|
|
63
82
|
for _family, source, target in module.iter_assets(["report-tests-v1"]):
|
|
64
83
|
with self.subTest(asset=source.name):
|
|
65
84
|
self.assertTrue(target.is_file(), f"{source.name} has no zh mirror")
|
|
@@ -114,6 +133,8 @@ class ReportSingleSourceTest(unittest.TestCase):
|
|
|
114
133
|
expected = {
|
|
115
134
|
"setReportColumns": "columns",
|
|
116
135
|
"addReportColumns": "columns",
|
|
136
|
+
# Single-leaf deletion has its own minimal payload: one exact fieldKey, never a column set.
|
|
137
|
+
"removeReportColumn": "columnRemoval",
|
|
117
138
|
"setReportFilters": "filters",
|
|
118
139
|
"setReportSorts": "sorts",
|
|
119
140
|
"registerReportPlugin": "plugin",
|
|
@@ -69,6 +69,7 @@ def test_timeprop_hints_survive_single_and_batch_handoff_without_false_blocking(
|
|
|
69
69
|
source = project_root / "src" / "main" / "java" / "demo" / "Booking.java"
|
|
70
70
|
source.parent.mkdir(parents=True)
|
|
71
71
|
source.write_text(
|
|
72
|
+
'package demo;\n'
|
|
72
73
|
'class Booking {\n'
|
|
73
74
|
' private static final String START = "start_time";\n'
|
|
74
75
|
' void check(DynamicObject row) { row.getDate(START); }\n'
|
|
@@ -196,10 +197,10 @@ BUILD FAILED
|
|
|
196
197
|
|
|
197
198
|
assert errors == [
|
|
198
199
|
{"file": "/tmp/DailyTask.java", "line": 7,
|
|
199
|
-
"message": "程序包kd.bos.datahelper不存在", "detail": []},
|
|
200
|
+
"message": "程序包kd.bos.datahelper不存在", "detail": ["import kd.bos.datahelper.BusinessDataServiceHelper;", "^"]},
|
|
200
201
|
{"file": "/tmp/DailyTask.java", "line": 14,
|
|
201
202
|
"message": "cannot find symbol",
|
|
202
|
-
"detail": ["symbol: variable SaveServiceHelper", "location: class DailyTask"]},
|
|
203
|
+
"detail": ["SaveServiceHelper.save(values);", "^", "symbol: variable SaveServiceHelper", "location: class DailyTask"]},
|
|
203
204
|
]
|
|
204
205
|
|
|
205
206
|
|
|
@@ -399,6 +400,87 @@ def test_compile_refuses_a_qfilter_operator_written_as_a_string_literal(tmp_path
|
|
|
399
400
|
assert not (project_root / ".temp").exists()
|
|
400
401
|
|
|
401
402
|
|
|
403
|
+
def test_compile_refuses_swapped_operation_arguments_even_without_javac(tmp_path, monkeypatch):
|
|
404
|
+
project = tmp_path / "module"
|
|
405
|
+
source = project / "src" / "main" / "java" / "demo" / "DeleteFlock.java"
|
|
406
|
+
source.parent.mkdir(parents=True)
|
|
407
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda write_derived: {
|
|
408
|
+
"status": "disabled", "compile": False,
|
|
409
|
+
})
|
|
410
|
+
for option in ("", ", OperateOption.create()"):
|
|
411
|
+
source.write_text('''package demo;
|
|
412
|
+
import kd.bos.servicehelper.BusinessDataServiceHelper;
|
|
413
|
+
import kd.bos.servicehelper.operation.OperationServiceHelper;
|
|
414
|
+
import kd.bos.dataentity.entity.DynamicObject;
|
|
415
|
+
''' + ('import kd.bos.dataentity.OperateOption;\n' if option else '') + '''class DeleteFlock {
|
|
416
|
+
private static final String FLOCK_ENTITY = "demo_flock";
|
|
417
|
+
void run(Object id) {
|
|
418
|
+
DynamicObject flk = BusinessDataServiceHelper.loadSingle(id, FLOCK_ENTITY);
|
|
419
|
+
OperationServiceHelper.executeOperate(FLOCK_ENTITY, "delete", new DynamicObject[]{flk}''' + option + ''');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
''', encoding="utf-8")
|
|
423
|
+
result = compile_plugin_source.compile_source(
|
|
424
|
+
project, source, "DeleteFlock", 30, app_root=tmp_path / "app",
|
|
425
|
+
plugin_type="operation", form_number="demo_receipt", operation_keys=["delete"],
|
|
426
|
+
description="删除关联记录",
|
|
427
|
+
)
|
|
428
|
+
assert result["status"] is False, result
|
|
429
|
+
assert "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER" in result["errorCodes"]
|
|
430
|
+
assert not (tmp_path / "app" / "archive.json").exists()
|
|
431
|
+
assert not (project / ".temp").exists()
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def test_unverified_operation_overload_cannot_be_archived_when_compilation_is_disabled(tmp_path, monkeypatch):
|
|
435
|
+
project = tmp_path / "module"
|
|
436
|
+
source = project / "src" / "main" / "java" / "demo" / "OperationCall.java"
|
|
437
|
+
source.parent.mkdir(parents=True)
|
|
438
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda write_derived: {
|
|
439
|
+
"status": "disabled", "compile": False,
|
|
440
|
+
})
|
|
441
|
+
for option, expected in (("", False), (", option", True)):
|
|
442
|
+
source.write_text('''package demo;
|
|
443
|
+
import kd.bos.servicehelper.operation.OperationServiceHelper;
|
|
444
|
+
import kd.bos.dataentity.OperateOption;
|
|
445
|
+
class OperationCall {
|
|
446
|
+
Object run(Object id, OperateOption option) {
|
|
447
|
+
return OperationServiceHelper.executeOperate("delete", "demo_flock", new Object[]{id}''' + option + ''');
|
|
448
|
+
}
|
|
449
|
+
}''', encoding="utf-8")
|
|
450
|
+
result = compile_plugin_source.compile_source(
|
|
451
|
+
project, source, "OperationCall", 30, app_root=tmp_path / "app",
|
|
452
|
+
plugin_type="operation", form_number="demo_receipt", operation_keys=["delete"],
|
|
453
|
+
description="关联记录删除",
|
|
454
|
+
)
|
|
455
|
+
assert result["status"] is expected, result
|
|
456
|
+
if not expected:
|
|
457
|
+
assert result["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"
|
|
458
|
+
assert not (tmp_path / "app" / "archive.json").exists()
|
|
459
|
+
assert not (project / ".temp").exists()
|
|
460
|
+
continue
|
|
461
|
+
assert result["compileSkipped"] is True
|
|
462
|
+
assert result["loadingVerification"]["runtimeVerified"] is False
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def test_operation_contract_cli_blocks_proven_swap_and_reports_unknown_overload(tmp_path):
|
|
466
|
+
source = tmp_path / "OperationCall.java"
|
|
467
|
+
source.write_text('''import kd.bos.servicehelper.BusinessDataServiceHelper;
|
|
468
|
+
import kd.bos.servicehelper.operation.OperationServiceHelper;
|
|
469
|
+
class OperationCall {
|
|
470
|
+
void run(Object id) {
|
|
471
|
+
Object flk = BusinessDataServiceHelper.loadSingle(id, "demo_flock");
|
|
472
|
+
OperationServiceHelper.executeOperate("demo_flock", "delete", new Object[]{id});
|
|
473
|
+
}
|
|
474
|
+
}''', encoding="utf-8")
|
|
475
|
+
run = subprocess.run([sys.executable, str(SCRIPTS / "platform_api_contract.py"),
|
|
476
|
+
"--source-path", str(source)], capture_output=True, text=True)
|
|
477
|
+
result = json.loads(run.stdout)
|
|
478
|
+
assert run.returncode == 2
|
|
479
|
+
assert result["errorCode"] == "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER"
|
|
480
|
+
assert result["issues"][0]["line"] == 6
|
|
481
|
+
assert result["sourceWarnings"][0]["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"
|
|
482
|
+
|
|
483
|
+
|
|
402
484
|
def test_compile_refuses_a_member_name_that_does_not_exist(tmp_path, monkeypatch):
|
|
403
485
|
project_root = tmp_path / "module"
|
|
404
486
|
source = project_root / "src" / "main" / "java" / "demo" / "EntryCountFormPlugin.java"
|
|
@@ -574,10 +656,11 @@ def test_compile_accepts_the_registered_return_type(tmp_path, monkeypatch):
|
|
|
574
656
|
"import kd.bos.dataentity.metadata.dynamicobject.DynamicObjectType;\n"
|
|
575
657
|
"import kd.bos.servicehelper.BusinessDataServiceHelper;\n"
|
|
576
658
|
"public class Task {\n"
|
|
577
|
-
" public
|
|
659
|
+
" public Object[] run(Object[] pks, DynamicObjectType type) {\n"
|
|
578
660
|
" DynamicObject[] rows = BusinessDataServiceHelper.load(pks, type);\n"
|
|
579
661
|
" Map<Object, DynamicObject> cached = BusinessDataServiceHelper.loadFromCache(pks, type);\n"
|
|
580
662
|
" DynamicObject one = BusinessDataServiceHelper.loadSingle(pks[0], type);\n"
|
|
663
|
+
" return new Object[] {rows, cached, one};\n"
|
|
581
664
|
" }\n"
|
|
582
665
|
"}\n",
|
|
583
666
|
encoding="utf-8",
|
|
@@ -596,6 +679,78 @@ def test_compile_accepts_the_registered_return_type(tmp_path, monkeypatch):
|
|
|
596
679
|
assert result["compileSkipped"] is True
|
|
597
680
|
|
|
598
681
|
|
|
682
|
+
def test_compile_refuses_what_the_dcs_deployment_gate_would_reject(tmp_path, monkeypatch):
|
|
683
|
+
"""DCS rejects the packaged module after delivery; the same rules stop it here, before archive."""
|
|
684
|
+
project_root = tmp_path / "module"
|
|
685
|
+
source = project_root / "src" / "main" / "java" / "demo" / "Sync.java"
|
|
686
|
+
app_root = tmp_path / "app" / "uhyl_demo"
|
|
687
|
+
source.parent.mkdir(parents=True)
|
|
688
|
+
app_root.mkdir(parents=True)
|
|
689
|
+
body = (
|
|
690
|
+
"package demo;\n"
|
|
691
|
+
"import java.util.HashMap;\n"
|
|
692
|
+
"import java.util.Map;\n"
|
|
693
|
+
"public class Sync {\n"
|
|
694
|
+
" private static final Map<String, Object> CACHE = new HashMap<>();\n"
|
|
695
|
+
" public Object read(String key) {\n"
|
|
696
|
+
" %s\n"
|
|
697
|
+
" return CACHE.get(key);\n"
|
|
698
|
+
" }\n"
|
|
699
|
+
"}\n"
|
|
700
|
+
)
|
|
701
|
+
source.write_text(body % "System.out.println(key);", encoding="utf-8")
|
|
702
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda write_derived: {
|
|
703
|
+
"status": "disabled", "compile": False,
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
def compile_it():
|
|
707
|
+
return compile_plugin_source.compile_source(
|
|
708
|
+
project_root, source, "Sync", 30,
|
|
709
|
+
app_root=app_root, plugin_type="task", form_number="uhyl_demo",
|
|
710
|
+
operation_keys=[], description="sync cache",
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
result = compile_it()
|
|
714
|
+
|
|
715
|
+
assert result["status"] is False
|
|
716
|
+
assert result["errorCode"] == "PLUGIN_SOURCE_DCS_RULE_VIOLATION"
|
|
717
|
+
codes = {code for issue in result["issues"] for code in issue["ruleCodes"]}
|
|
718
|
+
assert {"CVE-KD-0056-02", "java:S106"} <= codes
|
|
719
|
+
assert all(issue["fix"] and issue["dcsRisk"] for issue in result["issues"])
|
|
720
|
+
assert not (app_root / "archive.json").exists()
|
|
721
|
+
# Commented-out code is advisory: it is reported but does not stop delivery.
|
|
722
|
+
source.write_text((body % "// return CACHE.get(key.trim());").replace(
|
|
723
|
+
" private static final Map<String, Object> CACHE = new HashMap<>();\n", "").replace(
|
|
724
|
+
"import java.util.HashMap;\nimport java.util.Map;\n", "").replace(
|
|
725
|
+
"return CACHE.get(key);", "return key;"), encoding="utf-8")
|
|
726
|
+
result = compile_it()
|
|
727
|
+
assert result["status"] is True
|
|
728
|
+
assert [item["errorCode"] for item in result["sourceWarnings"]] == ["PLUGIN_SOURCE_DCS_RULE_ADVISORY"]
|
|
729
|
+
assert result["sourceWarnings"][0]["ruleCodes"] == ["java:S125"]
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def test_new_spreadsheet_rules_stop_delivery_before_archive_with_compile_disabled(tmp_path, monkeypatch):
|
|
733
|
+
project = tmp_path / "module"
|
|
734
|
+
source = project / "src" / "main" / "java" / "demo" / "Task.java"
|
|
735
|
+
source.parent.mkdir(parents=True)
|
|
736
|
+
source.write_text(
|
|
737
|
+
'package demo;\nimport kd.bos.servicehelper.BusinessDataServiceHelper;\n'
|
|
738
|
+
'public class Task {\n'
|
|
739
|
+
' public void run(Object[] ids) {\n'
|
|
740
|
+
' for (Object id : ids) { BusinessDataServiceHelper.loadSingle(id); }\n'
|
|
741
|
+
' }\n}\n', encoding="utf-8")
|
|
742
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: {"status": "disabled", "compile": False})
|
|
743
|
+
result = compile_plugin_source.compile_source(
|
|
744
|
+
project, source, "Task", 30, app_root=tmp_path / "app", plugin_type="task",
|
|
745
|
+
form_number="demo", operation_keys=[], description="check new DCS rules")
|
|
746
|
+
assert result["status"] is False
|
|
747
|
+
assert "PLUGIN_SOURCE_DCS_RULE_VIOLATION" in result["errorCodes"]
|
|
748
|
+
codes = {code for issue in result["issues"] for code in issue.get("ruleCodes", [])}
|
|
749
|
+
assert "CVE-KD-0051-47" in codes
|
|
750
|
+
assert not (tmp_path / "app" / "archive.json").exists()
|
|
751
|
+
assert not (project / ".temp").exists()
|
|
752
|
+
|
|
753
|
+
|
|
599
754
|
def test_the_compile_skipped_result_names_what_the_source_gates_do_not_cover(tmp_path, monkeypatch):
|
|
600
755
|
"""`compileSkipped=true` archives and registers with nothing having compiled the source, so the
|
|
601
756
|
result has to say which classes of error the four gates cannot reach."""
|
|
@@ -795,7 +950,9 @@ def test_batch_builds_one_module_once_and_keeps_full_diagnostics_in_one_log(tmp_
|
|
|
795
950
|
assert "--rerun-tasks" not in calls[0]
|
|
796
951
|
assert len(result["results"]) == 6
|
|
797
952
|
assert all(item["status"] is False for item in result["results"])
|
|
798
|
-
assert
|
|
953
|
+
assert all(any(error["file"].endswith(item["className"] + ".java") for error in item["diagnostics"]) for item in result["results"])
|
|
954
|
+
assert all(item["diagnosticCount"] == 60 for item in result["results"])
|
|
955
|
+
assert all(len(item["diagnostics"]) == 5 for item in result["results"])
|
|
799
956
|
paths = {item["logPath"] for item in result["results"]}
|
|
800
957
|
assert len(paths) == 1
|
|
801
958
|
assert Path(next(iter(paths))).read_text() == diagnostics
|
|
@@ -870,3 +1027,129 @@ def test_compile_consumes_handoff_without_retyped_identity(tmp_path, monkeypatch
|
|
|
870
1027
|
archive = json.loads((tmp_path / "app" / "archive.json").read_text())
|
|
871
1028
|
assert archive["javas"][0]["className"] == "Example"
|
|
872
1029
|
assert archive["javas"][0]["filePath"] == str(source.resolve())
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
_REPORT_FORM_SOURCE = '''package sample;
|
|
1033
|
+
import java.util.List;
|
|
1034
|
+
import kd.bos.form.events.HyperLinkClickListener;
|
|
1035
|
+
import kd.bos.report.events.SortAndFilterEvent;
|
|
1036
|
+
import kd.bos.report.plugin.AbstractReportFormPlugin;
|
|
1037
|
+
public class KpiFormPlugin extends AbstractReportFormPlugin implements HyperLinkClickListener {
|
|
1038
|
+
@Override
|
|
1039
|
+
public void setSortAndFilter(List<SortAndFilterEvent> allColumns) {
|
|
1040
|
+
super.setSortAndFilter(allColumns);
|
|
1041
|
+
for (SortAndFilterEvent column : allColumns) {
|
|
1042
|
+
column.setFilter(true);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
%s}
|
|
1046
|
+
'''
|
|
1047
|
+
_REPORT_DATA_SOURCE = '''package sample;
|
|
1048
|
+
import java.util.ArrayList;
|
|
1049
|
+
import java.util.List;
|
|
1050
|
+
import kd.bos.algo.DataSet;
|
|
1051
|
+
import kd.bos.entity.report.AbstractReportListDataPlugin;
|
|
1052
|
+
import kd.bos.entity.report.ReportQueryParam;
|
|
1053
|
+
import kd.bos.orm.query.QFilter;
|
|
1054
|
+
import kd.bos.servicehelper.QueryServiceHelper;
|
|
1055
|
+
public class KpiDataPlugin extends AbstractReportListDataPlugin {
|
|
1056
|
+
@Override
|
|
1057
|
+
public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {
|
|
1058
|
+
List<QFilter> filters = new ArrayList<>(queryParam.getFilter().getQFilters());
|
|
1059
|
+
%s return QueryServiceHelper.queryDataSet("kpi", "uhyl_kpi", "id", filters.toArray(new QFilter[filters.size()]), null);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
'''
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def _report_source(tmp_path, name, text):
|
|
1066
|
+
source = tmp_path / "src" / "main" / "java" / "sample" / f"{name}.java"
|
|
1067
|
+
source.parent.mkdir(parents=True, exist_ok=True)
|
|
1068
|
+
source.write_text(text, encoding="utf-8")
|
|
1069
|
+
return source
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def _compile_report(tmp_path, source, name, plugin_type):
|
|
1073
|
+
return compile_plugin_source.compile_source(
|
|
1074
|
+
tmp_path, source, name, 10, app_root=tmp_path / "app", plugin_type=plugin_type,
|
|
1075
|
+
form_number="uhyl_kpi_rpt", operation_keys=[], description="report plugin")
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def test_compile_refuses_a_report_drill_down_that_never_registers_its_listener(tmp_path, monkeypatch):
|
|
1079
|
+
"""The defect compiles and registers; only the gate can say the link will never fire."""
|
|
1080
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: {"status": "disabled"})
|
|
1081
|
+
source = _report_source(tmp_path, "KpiFormPlugin", _REPORT_FORM_SOURCE % (
|
|
1082
|
+
" @Override\n"
|
|
1083
|
+
" public void hyperLinkClick(kd.bos.form.events.HyperLinkClickEvent evt) { }\n"))
|
|
1084
|
+
|
|
1085
|
+
result = _compile_report(tmp_path, source, "KpiFormPlugin", "reportform")
|
|
1086
|
+
|
|
1087
|
+
assert result["status"] is False
|
|
1088
|
+
assert result["errorCode"] == "PLUGIN_SOURCE_LISTENER_UNREGISTERED"
|
|
1089
|
+
assert result["issues"][0]["requiredCall"] == "addHyperClickListener"
|
|
1090
|
+
assert not (tmp_path / "app" / "archive.json").exists()
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def test_head_filtering_without_its_data_half_is_warned_by_whichever_plugin_compiles_second(
|
|
1094
|
+
tmp_path, monkeypatch):
|
|
1095
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: {"status": "disabled"})
|
|
1096
|
+
data = _report_source(tmp_path, "KpiDataPlugin", _REPORT_DATA_SOURCE % "")
|
|
1097
|
+
form = _report_source(tmp_path, "KpiFormPlugin", _REPORT_FORM_SOURCE % "")
|
|
1098
|
+
|
|
1099
|
+
first = _compile_report(tmp_path, data, "KpiDataPlugin", "reportlist")
|
|
1100
|
+
second = _compile_report(tmp_path, form, "KpiFormPlugin", "reportform")
|
|
1101
|
+
|
|
1102
|
+
assert first["status"] is True and first["sourceWarnings"] == []
|
|
1103
|
+
assert second["status"] is True
|
|
1104
|
+
assert [item["errorCode"] for item in second["sourceWarnings"]] == [
|
|
1105
|
+
"PLUGIN_REPORT_HEAD_FILTER_UNAPPLIED"]
|
|
1106
|
+
assert "KpiDataPlugin" in second["sourceWarnings"][0]["message"]
|
|
1107
|
+
# The data plugin compiled again after the form plugin sees the same gap from its side.
|
|
1108
|
+
again = _compile_report(tmp_path, data, "KpiDataPlugin", "reportlist")
|
|
1109
|
+
assert [item["errorCode"] for item in again["sourceWarnings"]] == [
|
|
1110
|
+
"PLUGIN_REPORT_HEAD_FILTER_UNAPPLIED"]
|
|
1111
|
+
assert "KpiFormPlugin" in again["sourceWarnings"][0]["message"]
|
|
1112
|
+
# Applying the header conditions closes it on both sides.
|
|
1113
|
+
data.write_text(_REPORT_DATA_SOURCE % (
|
|
1114
|
+
" filters.addAll(queryParam.getFilter().getHeadFilters());\n"), encoding="utf-8")
|
|
1115
|
+
assert _compile_report(tmp_path, data, "KpiDataPlugin", "reportlist")["sourceWarnings"] == []
|
|
1116
|
+
assert _compile_report(tmp_path, form, "KpiFormPlugin", "reportform")["sourceWarnings"] == []
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def test_stale_handoff_fails_before_archiving_or_advancing_source_gate(tmp_path, monkeypatch):
|
|
1120
|
+
import hashlib
|
|
1121
|
+
import pytest
|
|
1122
|
+
input_file = tmp_path / 'intent.json'
|
|
1123
|
+
input_file.write_text('{"coreLogic":["old"]}')
|
|
1124
|
+
handoff = tmp_path / 'handoff.json'
|
|
1125
|
+
handoff.write_text(json.dumps({'status': True, 'handoff': {}, 'inputFiles': [
|
|
1126
|
+
{'path': str(input_file), 'sha256': hashlib.sha256(input_file.read_bytes()).hexdigest()}]}))
|
|
1127
|
+
input_file.write_text('{"coreLogic":["new"]}')
|
|
1128
|
+
with pytest.raises(compile_plugin_source.CompileError) as error:
|
|
1129
|
+
compile_plugin_source.compile_handoff(str(handoff))
|
|
1130
|
+
assert error.value.code == 'PLUGIN_HANDOFF_STALE'
|
|
1131
|
+
assert not (tmp_path / 'archive.json').exists()
|
|
1132
|
+
assert not (tmp_path / 'output' / 'generated').exists()
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
def test_batch_checks_all_sources_before_any_build_or_archive(tmp_path, monkeypatch, capsys):
|
|
1136
|
+
project = tmp_path / "module"
|
|
1137
|
+
java = project / "src" / "main" / "java" / "demo"
|
|
1138
|
+
java.mkdir(parents=True)
|
|
1139
|
+
app = tmp_path / "app"
|
|
1140
|
+
good = java / "Good.java"
|
|
1141
|
+
bad = java / "Bad.java"
|
|
1142
|
+
good.write_text("package demo; public class Good {}\n")
|
|
1143
|
+
bad.write_text("package demo; public class Bad { // L3-BLOCK: unresolved requirement\n}\n")
|
|
1144
|
+
batch = tmp_path / "sources.json"
|
|
1145
|
+
batch.write_text(json.dumps([{"sourcePath": str(source), "className": source.stem, "type": "task",
|
|
1146
|
+
"formNumber": "example", "description": "scheduled task"}
|
|
1147
|
+
for source in (good, bad)]))
|
|
1148
|
+
monkeypatch.setattr(sys, "argv", ["compile_plugin_source.py", "--project-root", str(project),
|
|
1149
|
+
"--app-root", str(app), "--sources-file", str(batch)])
|
|
1150
|
+
monkeypatch.setattr(compile_plugin_source, "inspect_config", lambda **_: (_ for _ in ()).throw(AssertionError("build was started")))
|
|
1151
|
+
assert compile_plugin_source.main() == 2
|
|
1152
|
+
result = json.loads(capsys.readouterr().out)
|
|
1153
|
+
assert [item["errorCode"] for item in result["results"]] == ["PLUGIN_BATCH_PREFLIGHT_BLOCKED", "PLUGIN_SOURCE_PLACEHOLDER_BLOCKED"]
|
|
1154
|
+
assert not (app / "archive.json").exists()
|
|
1155
|
+
assert not (project / ".temp").exists()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
SCRIPT = Path(__file__).resolve().parents[1] / "configure_list.py"
|
|
12
|
+
spec = importlib.util.spec_from_file_location("configure_list_delivery", SCRIPT)
|
|
13
|
+
assert spec and spec.loader
|
|
14
|
+
DRIVER = importlib.util.module_from_spec(spec)
|
|
15
|
+
spec.loader.exec_module(DRIVER)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def children(tmp_path, monkeypatch):
|
|
20
|
+
root = tmp_path / "应用目录"
|
|
21
|
+
root.mkdir()
|
|
22
|
+
calls = tmp_path / "calls.jsonl"
|
|
23
|
+
api = tmp_path / "native_api.py"
|
|
24
|
+
payload = tmp_path / "payload.json"
|
|
25
|
+
rendered = tmp_path / "rendered.txt"
|
|
26
|
+
api.write_text("import sys,json\nfrom pathlib import Path\n"
|
|
27
|
+
f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(['configure',sys.argv[1:]])+'\\n')\n"
|
|
28
|
+
f"value=json.loads(Path({str(payload)!r}).read_text(encoding='utf-8'))\n"
|
|
29
|
+
"Path(sys.argv[sys.argv.index('--output')+1]).write_text(json.dumps(value,ensure_ascii=False),encoding='utf-8')\n"
|
|
30
|
+
"raise SystemExit(0 if value['status'] else 3)\n", encoding="utf-8")
|
|
31
|
+
renderer = tmp_path / "render_output_cards.py"
|
|
32
|
+
renderer.write_text("import sys,json\nfrom pathlib import Path\n"
|
|
33
|
+
f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(['render',sys.argv[1:]])+'\\n')\n"
|
|
34
|
+
f"print(Path({str(rendered)!r}).read_text(encoding='utf-8'))\n", encoding="utf-8")
|
|
35
|
+
monkeypatch.setattr(DRIVER, "API", api)
|
|
36
|
+
monkeypatch.setattr(DRIVER, "SCRIPTS", tmp_path)
|
|
37
|
+
return root, payload, rendered, calls
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def receipt(root, outcome):
|
|
41
|
+
return {"status": outcome in {"verified", "verified_noop"}, "errorCode": "0" if outcome in {"verified", "verified_noop"} else "TEST_REJECTION",
|
|
42
|
+
"writeAttempted": outcome == "verified", "mutation": {"outcome": outcome, "writeAttempted": outcome == "verified"},
|
|
43
|
+
"verification": {"verified": outcome in {"verified", "verified_noop"}, "columns": [{"fieldKey": "count", "properties": {
|
|
44
|
+
"Summary": {"requested": 0, "actual": 0, "matched": True},
|
|
45
|
+
"ZeroShow": {"requested": False, "actual": False, "matched": True}}}]},
|
|
46
|
+
"data": {"formNumber": "exact_form", "appRoot": str(root), "structuralOps": 0,
|
|
47
|
+
"propertyWrites": int(outcome == "verified"),
|
|
48
|
+
"completion": {"requiresArtifactFinalization": outcome == "verified"}}}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_verified_update_combines_native_delivery_preserves_assertions_and_tracked_sync(children):
|
|
52
|
+
root, payload, rendered, calls = children
|
|
53
|
+
expected = receipt(root, "verified")
|
|
54
|
+
payload.write_text(json.dumps(expected))
|
|
55
|
+
block = ':::render:kdform\n{"title":"测试表","url":"https://example.test"}\n:::'
|
|
56
|
+
sync = 'DYM_BACKGROUND_REQUEST={"command":["python3","worker.py"],"run_in_background":true,"preview":false}'
|
|
57
|
+
rendered.write_text(block + "\n" + sync, encoding="utf-8")
|
|
58
|
+
value = '{"columns":[{"fieldKey":"a\' $()","properties":{"ZeroShow":false}}]}'
|
|
59
|
+
code, output = DRIVER.execute(str(root), None, "用户显示名", value)
|
|
60
|
+
assert code == 0 and output.startswith(block + "\n") and sync in output
|
|
61
|
+
result = json.loads(output.split("RESULT_SUMMARY=", 1)[1])
|
|
62
|
+
assert result["verification"] == expected["verification"]
|
|
63
|
+
assert result["verifiedPropertyCount"] == 2
|
|
64
|
+
assert result["completion"]["requiresArtifactFinalization"] is False
|
|
65
|
+
assert result["data"]["completion"] == result["completion"]
|
|
66
|
+
assert result["completion"]["nextAction"] == "launch_returned_background_request_then_respond"
|
|
67
|
+
observed = [json.loads(line) for line in calls.read_text().splitlines()]
|
|
68
|
+
assert [row[0] for row in observed] == ["configure", "render"]
|
|
69
|
+
assert observed[0][1][observed[0][1].index("--input") + 1] == value
|
|
70
|
+
assert observed[1][1][observed[1][1].index("--form-number") + 1] == "exact_form"
|
|
71
|
+
assert observed[1][1][observed[1][1].index("--app-root") + 1] == str(root)
|
|
72
|
+
# Exercise the actual unchanged host auto-arm predicate, not a copied regex.
|
|
73
|
+
source = SCRIPT.parents[3] / "src" / "tool" / "bash.ts"
|
|
74
|
+
output_path = root / "driver-output.txt"
|
|
75
|
+
output_path.write_text(output, encoding="utf-8")
|
|
76
|
+
run = subprocess.run(["bun", "-e", "const {isSuccessfulRenderOutput}=await import(process.argv[1]); process.stdout.write(JSON.stringify(isSuccessfulRenderOutput(await Bun.file(process.argv[2]).text(),0)))", str(source), str(output_path)], capture_output=True, text=True, timeout=30)
|
|
77
|
+
assert run.returncode == 0, run.stderr
|
|
78
|
+
assert json.loads(run.stdout) is True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("outcome", ["verified_noop", "failed_preflight"])
|
|
82
|
+
def test_noop_and_refusal_end_without_old_cards_or_delivery(children, outcome):
|
|
83
|
+
root, payload, _rendered, calls = children
|
|
84
|
+
expected = receipt(root, outcome)
|
|
85
|
+
payload.write_text(json.dumps(expected))
|
|
86
|
+
(root / "archive.json").write_text(json.dumps({"artifacts": [{"title": "OLD_CARD"}]}))
|
|
87
|
+
code, output = DRIVER.execute(str(root), None, "display", '{}')
|
|
88
|
+
result = json.loads(output)
|
|
89
|
+
assert code == (0 if outcome == "verified_noop" else 3)
|
|
90
|
+
assert ":::render:" not in output and "OLD_CARD" not in output and "DYM_BACKGROUND_REQUEST" not in output
|
|
91
|
+
assert result["verification"] == expected["verification"]
|
|
92
|
+
assert result["completion"]["requiresArtifactFinalization"] is False
|
|
93
|
+
assert result["data"]["completion"] == result["completion"]
|
|
94
|
+
assert [json.loads(line)[0] for line in calls.read_text().splitlines()] == ["configure"]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_render_failure_exposes_delivery_only_retry_without_replaying_write(children):
|
|
98
|
+
root, payload, rendered, calls = children
|
|
99
|
+
payload.write_text(json.dumps(receipt(root, "verified")))
|
|
100
|
+
rendered.write_text("No card was produced")
|
|
101
|
+
code, output = DRIVER.execute(str(root), None, "display", '{}')
|
|
102
|
+
result = json.loads(output)
|
|
103
|
+
assert code == 3 and result["verification"]["verified"] is True
|
|
104
|
+
assert result["status"] is False and result["errorCode"] == "LIST_DELIVERY_PENDING"
|
|
105
|
+
assert result["configurationVerified"] is True
|
|
106
|
+
assert result["completion"]["nextAction"] == "retry_delivery_only"
|
|
107
|
+
assert "render_output_cards.py" in result["completion"]["command"][1]
|
|
108
|
+
assert [json.loads(line)[0] for line in calls.read_text().splitlines()].count("configure") == 1
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_unknown_write_state_never_renders_or_replays(children):
|
|
112
|
+
root, payload, _rendered, calls = children
|
|
113
|
+
payload.write_text(json.dumps(receipt(root, "unknown_after_write")))
|
|
114
|
+
code, output = DRIVER.execute(str(root), None, "display", '{}')
|
|
115
|
+
assert code == 3 and json.loads(output)["completion"]["nextAction"] == "reconcile_write_state"
|
|
116
|
+
assert [json.loads(line)[0] for line in calls.read_text().splitlines()] == ["configure"]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_native_preflight_receipt_without_duplicate_top_level_flag_still_reports_zero_write(children):
|
|
120
|
+
root, payload, _rendered, _calls = children
|
|
121
|
+
value = receipt(root, "failed_preflight")
|
|
122
|
+
value.pop("writeAttempted")
|
|
123
|
+
value["data"]["writeAttempted"] = False
|
|
124
|
+
payload.write_text(json.dumps(value))
|
|
125
|
+
code, output = DRIVER.execute(str(root), None, "display", '{}')
|
|
126
|
+
assert code == 3 and json.loads(output)["writeAttempted"] is False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_list_executables_remain_identical_in_localized_package():
|
|
130
|
+
root = SCRIPT.parents[1]
|
|
131
|
+
for relative in ["scripts/configure_list.py", "skills/cosmic-meta-api/scripts/_core/cmd_listui.py"]:
|
|
132
|
+
assert (root / relative).read_bytes() == (root / ".locales/zh" / relative).read_bytes()
|