@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21
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/cordis.patch.yml +5 -5
- package/package.json +1 -1
- package/skills/cosmic-build/CHANGELOG.md +4 -0
- package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
- package/skills/cosmic-build/SKILL.md +46 -13
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
- package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
- package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
- package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
- package/skills/cosmic-build/references/application-context.md +35 -1
- package/skills/cosmic-build/references/common-reference.md +1 -1
- package/skills/cosmic-build/references/compile-guide.md +55 -68
- package/skills/cosmic-build/references/delivery.md +59 -7
- package/skills/cosmic-build/references/execution-contract.md +38 -0
- package/skills/cosmic-build/references/form-discovery.md +7 -1
- package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
- package/skills/cosmic-build/references/operation-dispatch.md +17 -0
- package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
- package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
- package/skills/cosmic-build/references/report-filter-contract.md +78 -0
- package/skills/cosmic-build/references/sdk-inspection.md +23 -0
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
- package/skills/cosmic-build/scripts/_http_client.py +55 -55
- package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
- package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
- package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
- package/skills/cosmic-build/scripts/execution_binding.py +40 -0
- package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
- package/skills/cosmic-build/scripts/kd_api.py +23 -22
- package/skills/cosmic-build/scripts/kd_auth.py +1 -1
- package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
- package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
- package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
- package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
- package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
- package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
- package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
- package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
- package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
- package/skills/cosmic-build/scripts/preview_url.py +35 -6
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
- package/skills/cosmic-build/scripts/register_plugins.py +28 -2
- package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
- package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
- package/skills/cosmic-build/scripts/run.py +2 -2
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
- package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
- package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
- package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
- package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
- package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
- package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
- package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
- package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
- package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
- package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
- package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
- package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
- package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
- package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
- package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
- package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
- package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
- package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
- package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
- package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
- package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
- package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
- package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
- package/skills/cosmic-build/scripts/test_environment.py +49 -0
- package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
- package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
- package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
- package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.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 +20 -5
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
- package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
- package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
- package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
- package/skills/erp-analysis/references/delivery-contract.md +4 -2
- package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
|
@@ -18,13 +18,10 @@ from .config import _api_call
|
|
|
18
18
|
from .formatters import _extract_error_message, _is_api_success, _has_api_failure
|
|
19
19
|
from .mutation_contract import mutation_contract, classify_mutation_result, combine_mutation_evidence
|
|
20
20
|
from .skill_assets import asset_candidates
|
|
21
|
+
from .property_policy_contract import PUBLISHED_SIMPLE_PROPERTIES as _PUBLISHED_SIMPLE_PROPERTIES
|
|
21
22
|
|
|
22
23
|
# ─── 属性设置 API ──────────────────────────────────────────────────────────────
|
|
23
24
|
|
|
24
|
-
_PUBLISHED_SIMPLE_PROPERTIES = frozenset({
|
|
25
|
-
"mustinput", "defvalue", "maxlength", "precision", "scale", "invisible",
|
|
26
|
-
"width", "height",
|
|
27
|
-
})
|
|
28
25
|
|
|
29
26
|
# Some field families render a scalar property through a dialog editor. The
|
|
30
27
|
# domain model therefore labels the designer surface "complex", but the field
|
|
@@ -571,6 +568,16 @@ def _describe_one_property(declaration, indexed, verified, element_type=None):
|
|
|
571
568
|
out["guidance"] = "the platform declares this property not editable; do not attempt a write"
|
|
572
569
|
return out
|
|
573
570
|
|
|
571
|
+
if declaration.get("propertyName") == "DropdownItems":
|
|
572
|
+
out["inputFormat"] = "menuEditIntent"
|
|
573
|
+
out["guidance"] = (
|
|
574
|
+
"Use configureDropdownMenu with append/update/remove/order/moves intent. "
|
|
575
|
+
"It resolves the live menu and preserves existing identities; do not submit "
|
|
576
|
+
"the read projection as a replacement or clear/rebuild after failure. "
|
|
577
|
+
"Its command schema defines the input."
|
|
578
|
+
)
|
|
579
|
+
return out
|
|
580
|
+
|
|
574
581
|
if declaration.get("propertyName") in _SCALAR_PROPERTIES_WITH_COMPLEX_EDITOR:
|
|
575
582
|
out.pop("complexType", None)
|
|
576
583
|
out["inputFormat"] = "scalarViaSetProperties"
|
|
@@ -1035,6 +1042,12 @@ def _normalize_recorded_complex_properties(properties):
|
|
|
1035
1042
|
normalized = {}
|
|
1036
1043
|
for selector, value in properties.items():
|
|
1037
1044
|
wanted = str(selector or "").strip().casefold()
|
|
1045
|
+
if wanted == "lock":
|
|
1046
|
+
raise ValueError(
|
|
1047
|
+
"PROPERTY_POLICY_LOCK_SHAPE_INVALID: Lock belongs in policy.set, "
|
|
1048
|
+
"for example {\"set\":{\"Lock\":\"new,edit,submit,audit\"}}; "
|
|
1049
|
+
"do not use policy.complex or infer support from the read-only designer schema"
|
|
1050
|
+
)
|
|
1038
1051
|
matches = [
|
|
1039
1052
|
name for name in _PROPERTY_POLICY_COMPLEX_TYPES
|
|
1040
1053
|
if wanted in {
|
|
@@ -5145,6 +5158,12 @@ def do_set_complex_property(cfg, token, form_number, complex_type, data, element
|
|
|
5145
5158
|
rejection = _assert_known_complex_type(complex_type)
|
|
5146
5159
|
if rejection is not None:
|
|
5147
5160
|
return rejection
|
|
5161
|
+
if complex_type == "dropdownItems":
|
|
5162
|
+
return {
|
|
5163
|
+
"status": False, "errorCode": "DROPDOWN_MENU_EDIT_REQUIRED",
|
|
5164
|
+
"message": "Use configureDropdownMenu with targeted edits so existing IDs and unmentioned items are retained.",
|
|
5165
|
+
"writeAttempted": False,
|
|
5166
|
+
}
|
|
5148
5167
|
data = _normalize_complex_payload(complex_type, data)
|
|
5149
5168
|
rejection = _assert_complex_payload_shape(complex_type, data)
|
|
5150
5169
|
if rejection is not None:
|
|
@@ -5592,6 +5611,649 @@ def _set_candidate_backed_property(
|
|
|
5592
5611
|
return result if verified else _mark_unknown_after_write(result)
|
|
5593
5612
|
|
|
5594
5613
|
|
|
5614
|
+
_DROPDOWN_ITEM_FIELDS = {
|
|
5615
|
+
"Key", "Title", "SubTitle", "OperationKey", "Background", "ImageKey",
|
|
5616
|
+
"IsSwitch", "IsTheme", "IsMerge", "Lock", "Visible", "Uploadable",
|
|
5617
|
+
"UploadConfig", "Id", "Index",
|
|
5618
|
+
}
|
|
5619
|
+
_DROPDOWN_ALIASES = {
|
|
5620
|
+
"key": "Key", "title": "Title", "subtitle": "SubTitle",
|
|
5621
|
+
"operationkey": "OperationKey", "operation": "OperationKey",
|
|
5622
|
+
"background": "Background", "imagekey": "ImageKey", "icon": "ImageKey",
|
|
5623
|
+
"isswitch": "IsSwitch", "switch": "IsSwitch", "istheme": "IsTheme",
|
|
5624
|
+
"theme": "IsTheme", "ismerge": "IsMerge", "merge": "IsMerge",
|
|
5625
|
+
"lock": "Lock", "visible": "Visible", "uploadable": "Uploadable",
|
|
5626
|
+
"uploadconfig": "UploadConfig", "id": "Id", "index": "Index",
|
|
5627
|
+
"isv": "Isv", "isvflag": "Isv",
|
|
5628
|
+
}
|
|
5629
|
+
_DROPDOWN_STATES = {
|
|
5630
|
+
"初始": "init", "新增": "new", "修改": "edit", "查看": "view",
|
|
5631
|
+
"提交": "submit", "审核": "audit",
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5634
|
+
|
|
5635
|
+
def _dropdown_items_value(cfg, token, form_number, element_key):
|
|
5636
|
+
read = do_get_properties(cfg, token, form_number, element_key, ["DropdownItems"])
|
|
5637
|
+
if not _is_api_success(read):
|
|
5638
|
+
return None, read
|
|
5639
|
+
values = read.get("data") or {}
|
|
5640
|
+
actual = next((value for key, value in values.items()
|
|
5641
|
+
if str(key).casefold() == "dropdownitems"), None)
|
|
5642
|
+
if actual is None:
|
|
5643
|
+
return None, {
|
|
5644
|
+
"status": False, "errorCode": "DROPDOWN_MENU_READ_UNAVAILABLE",
|
|
5645
|
+
"message": "DropdownItems was not returned; missing data is not an empty menu",
|
|
5646
|
+
"writeAttempted": False,
|
|
5647
|
+
}
|
|
5648
|
+
actual = _decode_jsonish(actual)
|
|
5649
|
+
if actual in (None, ""):
|
|
5650
|
+
actual = []
|
|
5651
|
+
if not isinstance(actual, list) or not all(isinstance(item, dict) for item in actual):
|
|
5652
|
+
return None, {
|
|
5653
|
+
"status": False, "errorCode": "DROPDOWN_MENU_READ_INVALID",
|
|
5654
|
+
"message": "DropdownItems read-back is not an item array",
|
|
5655
|
+
"writeAttempted": False, "data": {"actual": actual},
|
|
5656
|
+
}
|
|
5657
|
+
try:
|
|
5658
|
+
items = [_dropdown_authored_item(item) for item in actual]
|
|
5659
|
+
keys = [item.get("Key") for item in items]
|
|
5660
|
+
if any(not key for key in keys) or len(keys) != len(set(keys)):
|
|
5661
|
+
raise ValueError("current menu identities are missing or duplicated")
|
|
5662
|
+
return items, None
|
|
5663
|
+
except ValueError as exc:
|
|
5664
|
+
return None, {
|
|
5665
|
+
"status": False, "errorCode": "DROPDOWN_MENU_PRESERVATION_UNAVAILABLE",
|
|
5666
|
+
"message": str(exc), "writeAttempted": False,
|
|
5667
|
+
}
|
|
5668
|
+
|
|
5669
|
+
|
|
5670
|
+
def _dropdown_authored_item(item):
|
|
5671
|
+
"""Adapt MetaPropertyService JavaBeans to DropdownMenuEditPlugin's authored fields."""
|
|
5672
|
+
result = {}
|
|
5673
|
+
for raw_key, value in item.items():
|
|
5674
|
+
if raw_key == "_Type_" and value == "DropdownItem":
|
|
5675
|
+
continue
|
|
5676
|
+
if str(raw_key).casefold() == "visiblevalue":
|
|
5677
|
+
# Derived bitmask from getVisibleValue(), not an authored property.
|
|
5678
|
+
continue
|
|
5679
|
+
key = _DROPDOWN_ALIASES.get(str(raw_key).replace("_", "").casefold())
|
|
5680
|
+
# Read-back is a preservation baseline, not a write payload. Snapshot
|
|
5681
|
+
# editing retains the native objects by ID and patches only selected values.
|
|
5682
|
+
key = key or raw_key
|
|
5683
|
+
if value is not None:
|
|
5684
|
+
result[key] = value
|
|
5685
|
+
for key in ("Visible", "Lock"):
|
|
5686
|
+
if key in result:
|
|
5687
|
+
result[key] = ",".join(sorted(set(
|
|
5688
|
+
part.strip() for part in result[key].split(",") if part.strip())))
|
|
5689
|
+
if "UploadConfig" in result:
|
|
5690
|
+
result["UploadConfig"] = _dropdown_upload_config(result["UploadConfig"], readback=True)
|
|
5691
|
+
return result
|
|
5692
|
+
|
|
5693
|
+
|
|
5694
|
+
def _dropdown_mismatches(expected, actual, path="DropdownItems"):
|
|
5695
|
+
"""Identity and authored text comparisons must remain case-sensitive."""
|
|
5696
|
+
if isinstance(expected, str) and isinstance(actual, dict) and path.rsplit(".", 1)[-1] in ("Title", "SubTitle"):
|
|
5697
|
+
if expected in [value for key, value in actual.items() if key != "resId"]:
|
|
5698
|
+
return []
|
|
5699
|
+
if isinstance(expected, dict) and isinstance(actual, dict):
|
|
5700
|
+
return [mismatch for key, value in expected.items()
|
|
5701
|
+
for mismatch in _dropdown_mismatches(value, actual.get(key), f"{path}.{key}")]
|
|
5702
|
+
if isinstance(expected, list) and isinstance(actual, list) and len(expected) == len(actual):
|
|
5703
|
+
return [mismatch for index, value in enumerate(expected)
|
|
5704
|
+
for mismatch in _dropdown_mismatches(value, actual[index], f"{path}[{index}]")]
|
|
5705
|
+
if path.endswith((".Visible", ".Lock")) and isinstance(expected, str) and isinstance(actual, str):
|
|
5706
|
+
expected = set(filter(None, expected.split(",")))
|
|
5707
|
+
actual = set(filter(None, actual.split(",")))
|
|
5708
|
+
if expected == actual:
|
|
5709
|
+
return []
|
|
5710
|
+
return [{"property": path, "expected": sorted(expected) if isinstance(expected, set) else expected,
|
|
5711
|
+
"actual": sorted(actual) if isinstance(actual, set) else actual}]
|
|
5712
|
+
|
|
5713
|
+
|
|
5714
|
+
def _dropdown_upload_config(value, *, readback=False):
|
|
5715
|
+
value = _decode_jsonish(value)
|
|
5716
|
+
if not isinstance(value, dict):
|
|
5717
|
+
raise ValueError("DROPDOWN_MENU_UPLOAD_CONFIG_INVALID: object required")
|
|
5718
|
+
aliases = {
|
|
5719
|
+
"multiple": "Multiple", "suffix": "Suffix", "accept": "Suffix",
|
|
5720
|
+
"limitcount": "LimitCount", "maxfiles": "LimitCount",
|
|
5721
|
+
"limitsize": "LimitSize", "maxfilesize": "LimitSize", "url": "Url",
|
|
5722
|
+
}
|
|
5723
|
+
result = {}
|
|
5724
|
+
for raw_key, field in value.items():
|
|
5725
|
+
if raw_key == "_Type_" and value[raw_key] == "UploadOption":
|
|
5726
|
+
continue
|
|
5727
|
+
key = aliases.get(str(raw_key).replace("_", "").casefold())
|
|
5728
|
+
if readback:
|
|
5729
|
+
# Native UploadOption has more fields than the published authoring
|
|
5730
|
+
# input. Keep that state, including unknown/default/null values.
|
|
5731
|
+
key = key or str(raw_key).casefold()
|
|
5732
|
+
if key in result and result[key] != field:
|
|
5733
|
+
raise ValueError(f"DROPDOWN_MENU_UPLOAD_CONFIG_AMBIGUOUS: {raw_key}")
|
|
5734
|
+
result[key] = copy.deepcopy(field)
|
|
5735
|
+
continue
|
|
5736
|
+
if key in result and result[key] == field:
|
|
5737
|
+
continue
|
|
5738
|
+
if key is None or key in result:
|
|
5739
|
+
raise ValueError(f"DROPDOWN_MENU_UPLOAD_CONFIG_UNKNOWN_OR_DUPLICATE: {raw_key}")
|
|
5740
|
+
if key == "Multiple" and not isinstance(field, bool):
|
|
5741
|
+
raise ValueError("DROPDOWN_MENU_UPLOAD_CONFIG_INVALID: Multiple requires boolean")
|
|
5742
|
+
if key in ("LimitCount", "LimitSize") and (
|
|
5743
|
+
isinstance(field, bool) or not isinstance(field, int) or field < 0):
|
|
5744
|
+
raise ValueError(f"DROPDOWN_MENU_UPLOAD_CONFIG_INVALID: {key} requires nonnegative integer")
|
|
5745
|
+
if key in ("Suffix", "Url") and not isinstance(field, str):
|
|
5746
|
+
raise ValueError(f"DROPDOWN_MENU_UPLOAD_CONFIG_INVALID: {key} requires string")
|
|
5747
|
+
result[key] = field
|
|
5748
|
+
return result
|
|
5749
|
+
|
|
5750
|
+
|
|
5751
|
+
def _normalize_dropdown_item(item, *, partial=False):
|
|
5752
|
+
if not isinstance(item, dict):
|
|
5753
|
+
raise ValueError("DROPDOWN_MENU_ITEM_INVALID: every item must be an object")
|
|
5754
|
+
normalized = {}
|
|
5755
|
+
for raw_key, value in item.items():
|
|
5756
|
+
key = _DROPDOWN_ALIASES.get(str(raw_key).replace("_", "").casefold())
|
|
5757
|
+
if key is None or key not in _DROPDOWN_ITEM_FIELDS:
|
|
5758
|
+
raise ValueError(f"DROPDOWN_MENU_ITEM_UNKNOWN: {raw_key}")
|
|
5759
|
+
if key in normalized:
|
|
5760
|
+
raise ValueError(f"DROPDOWN_MENU_ITEM_DUPLICATE: {raw_key}")
|
|
5761
|
+
if key in ("Id", "Index"):
|
|
5762
|
+
raise ValueError(f"DROPDOWN_MENU_ITEM_IDENTITY_OWNED: {raw_key}")
|
|
5763
|
+
normalized[key] = value
|
|
5764
|
+
if not partial and not str(normalized.get("Key") or "").strip():
|
|
5765
|
+
raise ValueError("DROPDOWN_MENU_ITEM_KEY_REQUIRED")
|
|
5766
|
+
for name in ("Key", "Title", "SubTitle", "OperationKey", "Background", "ImageKey"):
|
|
5767
|
+
if name in normalized and not isinstance(normalized[name], str):
|
|
5768
|
+
raise ValueError(f"DROPDOWN_MENU_ITEM_STRING_REQUIRED: {name}")
|
|
5769
|
+
for name in ("Visible", "Lock"):
|
|
5770
|
+
value = normalized.get(name)
|
|
5771
|
+
if isinstance(value, (list, str)):
|
|
5772
|
+
parts = value if isinstance(value, list) else value.split(",")
|
|
5773
|
+
normalized[name] = ",".join(sorted({
|
|
5774
|
+
_DROPDOWN_STATES.get(str(part).strip(), str(part).strip())
|
|
5775
|
+
for part in parts if str(part).strip()
|
|
5776
|
+
}))
|
|
5777
|
+
allowed = {"init", "new", "edit", "view", "submit", "audit"}
|
|
5778
|
+
if name == "Lock":
|
|
5779
|
+
allowed.discard("init")
|
|
5780
|
+
if set(normalized[name].split(",")) - allowed - {""}:
|
|
5781
|
+
raise ValueError(f"DROPDOWN_MENU_STATE_INVALID: {name}")
|
|
5782
|
+
for name in ("IsSwitch", "IsTheme", "IsMerge", "Uploadable"):
|
|
5783
|
+
if name in normalized and not isinstance(normalized[name], bool):
|
|
5784
|
+
raise ValueError(f"DROPDOWN_MENU_ITEM_BOOLEAN_REQUIRED: {name}")
|
|
5785
|
+
if "UploadConfig" in normalized:
|
|
5786
|
+
normalized["UploadConfig"] = _dropdown_upload_config(normalized["UploadConfig"])
|
|
5787
|
+
if normalized.get("Uploadable") is True and str(normalized.get("OperationKey") or ""):
|
|
5788
|
+
raise ValueError("DROPDOWN_MENU_UPLOAD_OPERATION_CONFLICT")
|
|
5789
|
+
return normalized
|
|
5790
|
+
|
|
5791
|
+
|
|
5792
|
+
def _dropdown_target(items, selector):
|
|
5793
|
+
"""Resolve an exact key first, then one unique ID, title or operation key."""
|
|
5794
|
+
value = str(selector or "").strip()
|
|
5795
|
+
exact = [item for item in items if item.get("Key") == value]
|
|
5796
|
+
matches = exact or [item for item in items if value and (
|
|
5797
|
+
value in (item.get("Id"), item.get("Title"), item.get("OperationKey"))
|
|
5798
|
+
or isinstance(item.get("Title"), dict) and value in item["Title"].values())]
|
|
5799
|
+
if len(matches) != 1:
|
|
5800
|
+
raise ValueError(
|
|
5801
|
+
f"DROPDOWN_MENU_TARGET_{'AMBIGUOUS' if matches else 'NOT_FOUND'}: {value}")
|
|
5802
|
+
return matches[0]
|
|
5803
|
+
|
|
5804
|
+
|
|
5805
|
+
|
|
5806
|
+
def _dropdown_full_document(result):
|
|
5807
|
+
if not _is_api_success(result) or not isinstance(result.get("data"), dict):
|
|
5808
|
+
return None
|
|
5809
|
+
document = copy.deepcopy(result["data"])
|
|
5810
|
+
for key in ("formmeta", "entitymeta"):
|
|
5811
|
+
document[key] = _decode_jsonish(document.get(key))
|
|
5812
|
+
if not isinstance(document[key], dict):
|
|
5813
|
+
return None
|
|
5814
|
+
return document
|
|
5815
|
+
|
|
5816
|
+
|
|
5817
|
+
def _dropdown_apply_locale_value(document, item_id, property_name, value, requested_locale,
|
|
5818
|
+
*, create_prefix=None):
|
|
5819
|
+
"""Update one existing locale resource and return its exact JSON pointer."""
|
|
5820
|
+
rows = ((document.get("localeItems") or {}).get("form")) or []
|
|
5821
|
+
suffix = f".DropdownItems.{item_id}.{property_name}"
|
|
5822
|
+
matches = []
|
|
5823
|
+
for index, row in enumerate(rows):
|
|
5824
|
+
if not isinstance(row, dict):
|
|
5825
|
+
continue
|
|
5826
|
+
for resource_key, resource in row.items():
|
|
5827
|
+
if not isinstance(resource, dict):
|
|
5828
|
+
continue
|
|
5829
|
+
identity = str(resource.get("resId") or resource_key)
|
|
5830
|
+
if identity.endswith(suffix):
|
|
5831
|
+
matches.append((index, resource_key, resource))
|
|
5832
|
+
if not matches and create_prefix:
|
|
5833
|
+
known = {name for row in rows if isinstance(row, dict)
|
|
5834
|
+
for resource in row.values() if isinstance(resource, dict)
|
|
5835
|
+
for name in resource if name != "resId"}
|
|
5836
|
+
selected = requested_locale or document["formmeta"].get("Language")
|
|
5837
|
+
if not selected and len(known) == 1:
|
|
5838
|
+
selected = next(iter(known))
|
|
5839
|
+
if not selected:
|
|
5840
|
+
return None, {
|
|
5841
|
+
"status": False, "errorCode": "DROPDOWN_MENU_LOCALE_REQUIRED",
|
|
5842
|
+
"message": "Choose a language key for the new menu text with --locale.",
|
|
5843
|
+
"writeAttempted": False, "retryable": False,
|
|
5844
|
+
"data": {"itemId": item_id, "property": property_name,
|
|
5845
|
+
"locales": sorted(known), "argument": "--locale"},
|
|
5846
|
+
}
|
|
5847
|
+
resource_key = f"{create_prefix}.DropdownItems.{item_id}.{property_name}"
|
|
5848
|
+
resource = {"resId": resource_key, selected: value}
|
|
5849
|
+
rows.append({resource_key: resource})
|
|
5850
|
+
matches = [(len(rows) - 1, resource_key, resource)]
|
|
5851
|
+
if len(matches) != 1:
|
|
5852
|
+
return None, {
|
|
5853
|
+
"status": False, "errorCode": "DROPDOWN_MENU_LOCALE_RESOURCE_UNAVAILABLE",
|
|
5854
|
+
"message": "The selected menu text has no unique locale resource; nothing was written.",
|
|
5855
|
+
"writeAttempted": False, "retryable": False,
|
|
5856
|
+
"data": {"itemId": item_id, "property": property_name,
|
|
5857
|
+
"resourceMatches": len(matches)},
|
|
5858
|
+
}
|
|
5859
|
+
index, resource_key, resource = matches[0]
|
|
5860
|
+
locales = [name for name in resource if name != "resId"]
|
|
5861
|
+
selected = requested_locale if requested_locale in locales else None
|
|
5862
|
+
if selected is None and len(locales) == 1:
|
|
5863
|
+
selected = locales[0]
|
|
5864
|
+
if selected is None:
|
|
5865
|
+
return None, {
|
|
5866
|
+
"status": False, "errorCode": "DROPDOWN_MENU_LOCALE_REQUIRED",
|
|
5867
|
+
"message": "Choose one returned language key and retry the same intent with --locale.",
|
|
5868
|
+
"writeAttempted": False, "retryable": False,
|
|
5869
|
+
"data": {"itemId": item_id, "property": property_name,
|
|
5870
|
+
"locales": locales, "argument": "--locale"},
|
|
5871
|
+
}
|
|
5872
|
+
resource[selected] = value
|
|
5873
|
+
escape = lambda token: str(token).replace("~", "~0").replace("/", "~1")
|
|
5874
|
+
return f"/localeItems/form/{index}/{escape(resource_key)}/{escape(selected)}", None
|
|
5875
|
+
|
|
5876
|
+
def _dropdown_snapshot_edit(cfg, token, form_number, element_key, current, desired, *, locale=None):
|
|
5877
|
+
"""Preserve native objects through the existing full-snapshot API, scoped to one menu."""
|
|
5878
|
+
import pathlib
|
|
5879
|
+
import tempfile
|
|
5880
|
+
import uuid
|
|
5881
|
+
from .cmd_listui import do_load_full_metadata, do_save_full_metadata
|
|
5882
|
+
|
|
5883
|
+
def fail(code, message, **details):
|
|
5884
|
+
return {"status": False, "errorCode": code, "message": message,
|
|
5885
|
+
"writeAttempted": False, "retryable": False, "data": details}
|
|
5886
|
+
|
|
5887
|
+
schema = do_get_property_schema(cfg, token, form_number, element_key)
|
|
5888
|
+
if not _is_api_success(schema):
|
|
5889
|
+
return fail("DROPDOWN_MENU_SCHEMA_UNAVAILABLE", "Cannot verify the menu's editable surface.", cause=schema)
|
|
5890
|
+
declarations = [row for row in schema.get("data") or []
|
|
5891
|
+
if str(row.get("propertyName", "")).casefold() == "dropdownitems"]
|
|
5892
|
+
if len(declarations) != 1 or declarations[0].get("editable") is not True:
|
|
5893
|
+
return fail("DROPDOWN_MENU_NOT_EDITABLE", "The live designer does not declare this menu editable.",
|
|
5894
|
+
declarations=declarations)
|
|
5895
|
+
|
|
5896
|
+
loaded = do_load_full_metadata(cfg, token, form_number)
|
|
5897
|
+
before = _dropdown_full_document(loaded)
|
|
5898
|
+
if before is None:
|
|
5899
|
+
return fail("DROPDOWN_MENU_SNAPSHOT_UNAVAILABLE",
|
|
5900
|
+
"A complete form+entity snapshot is required for lossless menu editing. Other metadata commands remain available.",
|
|
5901
|
+
cause=loaded)
|
|
5902
|
+
if str(before["formmeta"].get("Key") or "") != form_number:
|
|
5903
|
+
return fail("DROPDOWN_MENU_SNAPSHOT_TARGET_MISMATCH", "Snapshot does not match the bound form.")
|
|
5904
|
+
matches = [(index, item) for index, item in enumerate(before["formmeta"].get("Items") or [])
|
|
5905
|
+
if isinstance(item, dict) and item.get("Key") == element_key]
|
|
5906
|
+
if len(matches) != 1:
|
|
5907
|
+
return fail("DROPDOWN_MENU_TARGET_AMBIGUOUS", "No unique button exists in the primary form layout.")
|
|
5908
|
+
position, button = matches[0]
|
|
5909
|
+
raw_menu = _decode_jsonish(button.get("DropdownItems", []))
|
|
5910
|
+
if not isinstance(raw_menu, list) or not all(isinstance(row, dict) for row in raw_menu):
|
|
5911
|
+
return fail("DROPDOWN_MENU_SNAPSHOT_INVALID", "The authored menu is not an object array.")
|
|
5912
|
+
pairs = [(row.get("Id"), row.get("Key")) for row in raw_menu]
|
|
5913
|
+
if pairs != [(row.get("Id"), row.get("Key")) for row in current]:
|
|
5914
|
+
return fail("DROPDOWN_MENU_BASELINE_CHANGED", "Menu identities or order changed after resolution; refresh this menu.")
|
|
5915
|
+
# Compare shared authored fields as well; full metadata omits default-valued fields.
|
|
5916
|
+
for raw, baseline in zip(raw_menu, current):
|
|
5917
|
+
shared = {key: value for key, value in _dropdown_authored_item(raw).items()
|
|
5918
|
+
if key in baseline and key != "Index"}
|
|
5919
|
+
if _dropdown_mismatches(shared, baseline):
|
|
5920
|
+
return fail("DROPDOWN_MENU_BASELINE_CHANGED", "Menu properties changed after resolution; refresh this menu.")
|
|
5921
|
+
|
|
5922
|
+
source_by_key = {row["Key"]: row for row in raw_menu}
|
|
5923
|
+
current_by_key = {row["Key"]: row for row in current}
|
|
5924
|
+
owner = before["entitymeta"].get("IsvFlag") or before["formmeta"].get("IsvFlag") or before.get("isv")
|
|
5925
|
+
edited = []
|
|
5926
|
+
locale_updates = []
|
|
5927
|
+
for item in desired:
|
|
5928
|
+
source = source_by_key.get(item["Key"])
|
|
5929
|
+
if source is None:
|
|
5930
|
+
if not owner:
|
|
5931
|
+
return fail("DROPDOWN_MENU_OWNER_UNRESOLVED", "Snapshot does not identify the developer for new menu items.")
|
|
5932
|
+
native = {"_Type_": "DropdownItem", "Id": uuid.uuid4().hex,
|
|
5933
|
+
"IsvFlag": owner, "Key": item["Key"]}
|
|
5934
|
+
changes = item
|
|
5935
|
+
else:
|
|
5936
|
+
native = copy.deepcopy(source)
|
|
5937
|
+
changes = {key: value for key, value in item.items()
|
|
5938
|
+
if key not in ("Id", "Isv") and _dropdown_mismatches(
|
|
5939
|
+
value, current_by_key[item["Key"]].get(key), key)}
|
|
5940
|
+
for key, value in changes.items():
|
|
5941
|
+
if key in ("Id", "Isv"):
|
|
5942
|
+
continue
|
|
5943
|
+
if key in ("Title", "SubTitle") and isinstance(value, str):
|
|
5944
|
+
selected_locale = locale or cfg.get("locale")
|
|
5945
|
+
if isinstance(native.get(key), dict):
|
|
5946
|
+
locales = [name for name in native[key] if name != "resId"]
|
|
5947
|
+
if selected_locale not in locales:
|
|
5948
|
+
if selected_locale or len(locales) != 1:
|
|
5949
|
+
return fail("DROPDOWN_MENU_LOCALE_REQUIRED",
|
|
5950
|
+
"Choose one returned language key and retry the same intent with --locale.",
|
|
5951
|
+
locales=locales, itemKey=item["Key"], argument="--locale")
|
|
5952
|
+
selected_locale = locales[0]
|
|
5953
|
+
native[key][selected_locale] = value
|
|
5954
|
+
else:
|
|
5955
|
+
native[key] = value
|
|
5956
|
+
locale_updates.append((
|
|
5957
|
+
native.get("Id"), key, value, selected_locale,
|
|
5958
|
+
".".join(filter(None, (
|
|
5959
|
+
str(before.get("appNum") or ""), str(before["formmeta"].get("Id") or ""),
|
|
5960
|
+
str(button.get("Id") or "")))) or None,
|
|
5961
|
+
))
|
|
5962
|
+
elif key == "UploadConfig":
|
|
5963
|
+
raw_config = native.get(key)
|
|
5964
|
+
merged = copy.deepcopy(_decode_jsonish(raw_config)) if raw_config is not None else {}
|
|
5965
|
+
if not isinstance(merged, dict):
|
|
5966
|
+
return fail("DROPDOWN_MENU_UPLOAD_CONFIG_INVALID", "Authored upload configuration is not an object.")
|
|
5967
|
+
baseline = current_by_key.get(item["Key"], {}).get("UploadConfig", {})
|
|
5968
|
+
for field, setting in value.items():
|
|
5969
|
+
if field in baseline and baseline[field] == setting:
|
|
5970
|
+
continue
|
|
5971
|
+
actual_key = next((name for name in merged
|
|
5972
|
+
if field in _dropdown_upload_config({name: merged[name]}, readback=True)), field)
|
|
5973
|
+
merged[actual_key] = copy.deepcopy(setting)
|
|
5974
|
+
native[key] = json.dumps(merged, ensure_ascii=False) if isinstance(raw_config, str) else merged
|
|
5975
|
+
else:
|
|
5976
|
+
native[key] = copy.deepcopy(value)
|
|
5977
|
+
native["Index"] = item["Index"]
|
|
5978
|
+
edited.append(native)
|
|
5979
|
+
after = copy.deepcopy(before)
|
|
5980
|
+
after["formmeta"]["Items"][position]["DropdownItems"] = edited
|
|
5981
|
+
pointer = f"/formmeta/Items/{position}/DropdownItems"
|
|
5982
|
+
locale_paths = set()
|
|
5983
|
+
for item_id, property_name, value, selected_locale, create_prefix in locale_updates:
|
|
5984
|
+
locale_path, locale_error = _dropdown_apply_locale_value(
|
|
5985
|
+
after, item_id, property_name, value, selected_locale,
|
|
5986
|
+
create_prefix=create_prefix)
|
|
5987
|
+
if locale_error is not None:
|
|
5988
|
+
return locale_error
|
|
5989
|
+
locale_paths.add(locale_path)
|
|
5990
|
+
changed = _instant_sync_changed_pointers(before, after)
|
|
5991
|
+
locale_list_changed = len(((before.get("localeItems") or {}).get("form")) or []) != len(
|
|
5992
|
+
((after.get("localeItems") or {}).get("form")) or [])
|
|
5993
|
+
if any(path not in locale_paths
|
|
5994
|
+
and not (locale_list_changed and path == "/localeItems/form")
|
|
5995
|
+
and path != pointer and not path.startswith(pointer + "/") for path in changed):
|
|
5996
|
+
return fail("DROPDOWN_MENU_DIFF_SCOPE_MISMATCH", "Proposed changes exceed the selected menu.")
|
|
5997
|
+
|
|
5998
|
+
# Preserve source serialization for API versions returning JSON-encoded sections.
|
|
5999
|
+
payload = copy.deepcopy(loaded["data"])
|
|
6000
|
+
payload["formmeta"] = (json.dumps(after["formmeta"], ensure_ascii=False)
|
|
6001
|
+
if isinstance(payload["formmeta"], str) else after["formmeta"])
|
|
6002
|
+
if locale_paths:
|
|
6003
|
+
payload["localeItems"] = copy.deepcopy(after["localeItems"])
|
|
6004
|
+
directory = pathlib.Path(tempfile.mkdtemp(prefix="cosmic-dropdown-"))
|
|
6005
|
+
snapshot = directory / "before.json"
|
|
6006
|
+
snapshot.write_text(json.dumps(loaded["data"], ensure_ascii=False, indent=2), encoding="utf-8")
|
|
6007
|
+
fresh = do_load_full_metadata(cfg, token, form_number)
|
|
6008
|
+
if _dropdown_full_document(fresh) != before:
|
|
6009
|
+
return fail("DROPDOWN_MENU_BASELINE_CHANGED", "The form changed before saving; no write was sent.",
|
|
6010
|
+
beforeSnapshot=str(snapshot))
|
|
6011
|
+
result = do_save_full_metadata(cfg, token, form_number, payload, allow_full_replace=True)
|
|
6012
|
+
if not _is_api_success(result):
|
|
6013
|
+
result = dict(result)
|
|
6014
|
+
result.setdefault("data", {})
|
|
6015
|
+
if not isinstance(result["data"], dict):
|
|
6016
|
+
result["data"] = {}
|
|
6017
|
+
result["data"]["beforeSnapshot"] = str(snapshot)
|
|
6018
|
+
return result
|
|
6019
|
+
readback = do_load_full_metadata(cfg, token, form_number)
|
|
6020
|
+
actual = _dropdown_full_document(readback)
|
|
6021
|
+
differences = _instant_sync_changed_pointers(after, actual) if actual is not None else {"/"}
|
|
6022
|
+
# Only server-managed audit fields are exempt; layout, rules, operations and
|
|
6023
|
+
# other business metadata are all included in this independent comparison.
|
|
6024
|
+
differences -= {"/modifyDate", "/modifierId"}
|
|
6025
|
+
if actual is not None:
|
|
6026
|
+
(directory / "after.json").write_text(
|
|
6027
|
+
json.dumps(readback["data"], ensure_ascii=False, indent=2), encoding="utf-8")
|
|
6028
|
+
verified = not differences
|
|
6029
|
+
result.update(status=verified, errorCode="0" if verified else "DROPDOWN_MENU_SNAPSHOT_READBACK_MISMATCH",
|
|
6030
|
+
message="Menu edited and full snapshot verified." if verified
|
|
6031
|
+
else "The saved snapshot differs from the intended result; inspect before another write.",
|
|
6032
|
+
writeAttempted=True,
|
|
6033
|
+
verification={"verified": verified, "readPath": "loadFullMetadata",
|
|
6034
|
+
"changedMenuPath": pointer, "changedLocalePaths": sorted(locale_paths),
|
|
6035
|
+
"unexpectedPaths": sorted(differences)[:30]},
|
|
6036
|
+
data={"beforeSnapshot": str(snapshot), "changed": True if verified else None})
|
|
6037
|
+
return result if verified else _mark_unknown_after_write(result)
|
|
6038
|
+
|
|
6039
|
+
def _resolve_dropdown_parent(cfg, token, form_number, intent):
|
|
6040
|
+
from .cmd_listui import do_load_full_metadata
|
|
6041
|
+
|
|
6042
|
+
document = _dropdown_full_document(do_load_full_metadata(cfg, token, form_number))
|
|
6043
|
+
if document is None:
|
|
6044
|
+
return None, {"status": False, "errorCode": "DROPDOWN_MENU_SNAPSHOT_UNAVAILABLE",
|
|
6045
|
+
"message": "Cannot resolve a menu without a complete live form snapshot.",
|
|
6046
|
+
"writeAttempted": False}
|
|
6047
|
+
appended = {item.get("key") or item.get("Key") for item in intent.get("append", []) if isinstance(item, dict)}
|
|
6048
|
+
selectors = [item.get("key") or item.get("Key") for item in intent.get("update", []) if isinstance(item, dict)]
|
|
6049
|
+
selectors += [item.get("key") or item.get("Key") if isinstance(item, dict) else item
|
|
6050
|
+
for item in intent.get("remove", [])]
|
|
6051
|
+
selectors += list(intent.get("order", []))
|
|
6052
|
+
selectors += [item.get(key) for item in intent.get("moves", []) if isinstance(item, dict)
|
|
6053
|
+
for key in ("key", "before", "after") if item.get(key)]
|
|
6054
|
+
selectors = [selector for selector in selectors if selector and selector not in appended]
|
|
6055
|
+
candidates, matches = [], []
|
|
6056
|
+
for control in document["formmeta"].get("Items", []):
|
|
6057
|
+
if not isinstance(control, dict) or "DropdownItems" not in control:
|
|
6058
|
+
continue
|
|
6059
|
+
raw = _decode_jsonish(control["DropdownItems"])
|
|
6060
|
+
if not isinstance(raw, list):
|
|
6061
|
+
continue
|
|
6062
|
+
candidate = {"key": control.get("Key"), "name": control.get("Name")}
|
|
6063
|
+
candidates.append(candidate)
|
|
6064
|
+
if not selectors:
|
|
6065
|
+
continue
|
|
6066
|
+
try:
|
|
6067
|
+
items = [_dropdown_authored_item(item) for item in raw]
|
|
6068
|
+
for selector in selectors:
|
|
6069
|
+
_dropdown_target(items, selector)
|
|
6070
|
+
except ValueError:
|
|
6071
|
+
continue
|
|
6072
|
+
matches.append(candidate)
|
|
6073
|
+
if len(matches) == 1 and matches[0]["key"]:
|
|
6074
|
+
return matches[0]["key"], None
|
|
6075
|
+
return None, {
|
|
6076
|
+
"status": False, "errorCode": "DROPDOWN_MENU_PARENT_REQUIRED",
|
|
6077
|
+
"message": "Pass --element with the intended button; menu items do not identify one unique parent.",
|
|
6078
|
+
"writeAttempted": False, "data": {"candidates": matches or candidates, "argument": "--element"},
|
|
6079
|
+
}
|
|
6080
|
+
|
|
6081
|
+
def do_configure_dropdown_menu(cfg, token, form_number, element_key, intent, *, locale=None):
|
|
6082
|
+
"""Stage edits on the current menu and save once, as the designer dialog does."""
|
|
6083
|
+
if not isinstance(intent, dict) or not intent:
|
|
6084
|
+
raise ValueError("DROPDOWN_MENU_INPUT_INVALID")
|
|
6085
|
+
allowed = {"append", "update", "remove", "order", "moves"}
|
|
6086
|
+
if set(intent) - allowed:
|
|
6087
|
+
return {
|
|
6088
|
+
"status": False, "errorCode": "DROPDOWN_MENU_INTENT_UNSUPPORTED",
|
|
6089
|
+
"message": "Use targeted edits. Clearing or replacing the menu is not an edit recovery strategy.",
|
|
6090
|
+
"writeAttempted": False,
|
|
6091
|
+
}
|
|
6092
|
+
if any(not isinstance(value, list) for value in intent.values()):
|
|
6093
|
+
raise ValueError("DROPDOWN_MENU_INPUT_INVALID: edit actions require arrays")
|
|
6094
|
+
if not element_key:
|
|
6095
|
+
element_key, error = _resolve_dropdown_parent(cfg, token, form_number, intent)
|
|
6096
|
+
if error is not None:
|
|
6097
|
+
return error
|
|
6098
|
+
current, error = _dropdown_items_value(cfg, token, form_number, element_key)
|
|
6099
|
+
if error is not None:
|
|
6100
|
+
return error
|
|
6101
|
+
desired = copy.deepcopy(current)
|
|
6102
|
+
changed_operations = set()
|
|
6103
|
+
try:
|
|
6104
|
+
appended = set()
|
|
6105
|
+
for raw in intent.get("append", []):
|
|
6106
|
+
item = _normalize_dropdown_item(raw)
|
|
6107
|
+
key = item["Key"]
|
|
6108
|
+
if any(row.get("Key") == key for row in desired):
|
|
6109
|
+
raise ValueError(f"DROPDOWN_MENU_APPEND_KEY_EXISTS: {key}; use update to edit it")
|
|
6110
|
+
desired.append(item)
|
|
6111
|
+
appended.add(key)
|
|
6112
|
+
if item.get("OperationKey"):
|
|
6113
|
+
changed_operations.add(item["OperationKey"])
|
|
6114
|
+
updated = set()
|
|
6115
|
+
for raw in intent.get("update", []):
|
|
6116
|
+
patch = _normalize_dropdown_item(raw)
|
|
6117
|
+
target = _dropdown_target(desired, patch.pop("Key"))
|
|
6118
|
+
key = target["Key"]
|
|
6119
|
+
if key in updated:
|
|
6120
|
+
raise ValueError(f"DROPDOWN_MENU_DUPLICATE_UPDATE: {key}")
|
|
6121
|
+
updated.add(key)
|
|
6122
|
+
if "UploadConfig" in patch:
|
|
6123
|
+
patch["UploadConfig"] = {
|
|
6124
|
+
**target.get("UploadConfig", {}), **patch["UploadConfig"],
|
|
6125
|
+
}
|
|
6126
|
+
if patch.get("OperationKey") and patch["OperationKey"] != target.get("OperationKey"):
|
|
6127
|
+
changed_operations.add(patch["OperationKey"])
|
|
6128
|
+
for name, value in patch.items():
|
|
6129
|
+
previous = target.get(name)
|
|
6130
|
+
if name in ("Title", "SubTitle") and isinstance(previous, dict):
|
|
6131
|
+
language = locale or cfg.get("locale")
|
|
6132
|
+
texts = {key: text for key, text in previous.items() if key != "resId"}
|
|
6133
|
+
if language and texts.get(language) == value:
|
|
6134
|
+
continue
|
|
6135
|
+
if not language and len(texts) == 1 and value in texts.values():
|
|
6136
|
+
continue
|
|
6137
|
+
elif not _dropdown_mismatches(value, previous, name):
|
|
6138
|
+
continue
|
|
6139
|
+
target[name] = value
|
|
6140
|
+
removed = set()
|
|
6141
|
+
for selector in intent.get("remove", []):
|
|
6142
|
+
if isinstance(selector, dict):
|
|
6143
|
+
selector = selector.get("key") or selector.get("Key")
|
|
6144
|
+
key = _dropdown_target(desired, selector)["Key"]
|
|
6145
|
+
if key in appended or key in updated:
|
|
6146
|
+
raise ValueError(f"DROPDOWN_MENU_CONFLICTING_EDIT: {key}")
|
|
6147
|
+
removed.add(key)
|
|
6148
|
+
desired = [item for item in desired if item["Key"] not in removed]
|
|
6149
|
+
if "order" in intent and "moves" in intent:
|
|
6150
|
+
raise ValueError("DROPDOWN_MENU_ORDER_MODE_CONFLICT")
|
|
6151
|
+
if "order" in intent:
|
|
6152
|
+
ordered = [_dropdown_target(desired, selector) for selector in intent["order"]]
|
|
6153
|
+
keys = {item["Key"] for item in ordered}
|
|
6154
|
+
if len(keys) != len(ordered):
|
|
6155
|
+
raise ValueError("DROPDOWN_MENU_ORDER_MISMATCH: each selected item must appear once")
|
|
6156
|
+
desired = ordered + [item for item in desired if item["Key"] not in keys]
|
|
6157
|
+
for move in intent.get("moves", []):
|
|
6158
|
+
if not isinstance(move, dict) or set(move) - {"key", "before", "after"}:
|
|
6159
|
+
raise ValueError("DROPDOWN_MENU_MOVE_INVALID")
|
|
6160
|
+
anchors = set(move) & {"before", "after"}
|
|
6161
|
+
if len(anchors) != 1:
|
|
6162
|
+
raise ValueError("DROPDOWN_MENU_MOVE_INVALID: supply one before/after anchor")
|
|
6163
|
+
anchor_name = next(iter(anchors))
|
|
6164
|
+
item = _dropdown_target(desired, move.get("key"))
|
|
6165
|
+
anchor = _dropdown_target(desired, move[anchor_name])
|
|
6166
|
+
if item is anchor:
|
|
6167
|
+
raise ValueError("DROPDOWN_MENU_MOVE_INVALID: item is its own anchor")
|
|
6168
|
+
desired.remove(item)
|
|
6169
|
+
desired.insert(desired.index(anchor) + (anchor_name == "after"), item)
|
|
6170
|
+
for item in desired:
|
|
6171
|
+
if item.get("Uploadable") and item.get("OperationKey"):
|
|
6172
|
+
raise ValueError("DROPDOWN_MENU_UPLOAD_OPERATION_CONFLICT")
|
|
6173
|
+
if desired == current:
|
|
6174
|
+
return {
|
|
6175
|
+
"status": True, "errorCode": "0", "message": "Menu already matches",
|
|
6176
|
+
"data": {"changed": False, "formNumber": form_number, "elementKey": element_key},
|
|
6177
|
+
"verification": {"verified": True, "readPath": "getProperties.DropdownItems"},
|
|
6178
|
+
}
|
|
6179
|
+
if any(not item.get("Id") for item in current):
|
|
6180
|
+
raise ValueError("DROPDOWN_MENU_ID_MISSING: cannot preserve original item identity")
|
|
6181
|
+
if len({item["Id"] for item in current}) != len(current):
|
|
6182
|
+
raise ValueError("DROPDOWN_MENU_ID_DUPLICATE: cannot preserve original item identity")
|
|
6183
|
+
except ValueError as exc:
|
|
6184
|
+
return {
|
|
6185
|
+
"status": False, "errorCode": str(exc).split(":")[0],
|
|
6186
|
+
"message": str(exc), "writeAttempted": False,
|
|
6187
|
+
"data": {"candidates": [
|
|
6188
|
+
{key: item.get(key) for key in ("Key", "Title", "OperationKey")}
|
|
6189
|
+
for item in current
|
|
6190
|
+
]},
|
|
6191
|
+
}
|
|
6192
|
+
if changed_operations:
|
|
6193
|
+
from .cmd_operations import do_list_operations
|
|
6194
|
+
operation_result = do_list_operations(cfg, token, form_number)
|
|
6195
|
+
if not _is_api_success(operation_result):
|
|
6196
|
+
return {
|
|
6197
|
+
"status": False, "errorCode": "DROPDOWN_MENU_OPERATION_READ_FAILED",
|
|
6198
|
+
"message": "Cannot verify changed operation bindings; nothing was written",
|
|
6199
|
+
"writeAttempted": False, "data": {"cause": operation_result},
|
|
6200
|
+
}
|
|
6201
|
+
data = operation_result.get("data") or []
|
|
6202
|
+
rows = data.get("operations", []) if isinstance(data, dict) else data
|
|
6203
|
+
operations = {str(row.get("operationKey") or "") for row in rows if isinstance(row, dict)}
|
|
6204
|
+
missing = sorted(changed_operations - operations)
|
|
6205
|
+
if missing:
|
|
6206
|
+
return {
|
|
6207
|
+
"status": False, "errorCode": "DROPDOWN_MENU_OPERATION_NOT_FOUND",
|
|
6208
|
+
"message": "Bind an existing implemented operation; a placeholder does not implement menu behavior.",
|
|
6209
|
+
"writeAttempted": False, "data": {"missingOperationKeys": missing},
|
|
6210
|
+
}
|
|
6211
|
+
unresolved = [
|
|
6212
|
+
row for row in rows if row.get("operationKey") in changed_operations
|
|
6213
|
+
and (not row.get("operationType")
|
|
6214
|
+
or row.get("operationType") == "donothing" and not (
|
|
6215
|
+
row.get("pluginCount") or row.get("opBizRuleCount")))
|
|
6216
|
+
]
|
|
6217
|
+
if unresolved:
|
|
6218
|
+
return {
|
|
6219
|
+
"status": False, "errorCode": "DROPDOWN_MENU_OPERATION_IMPLEMENTATION_REQUIRED",
|
|
6220
|
+
"message": "Menu binding exists but has no demonstrated executable implementation.",
|
|
6221
|
+
"writeAttempted": False, "retryable": False,
|
|
6222
|
+
"data": {"operations": [{
|
|
6223
|
+
"key": row.get("operationKey"), "type": row.get("operationType"),
|
|
6224
|
+
"pluginCount": row.get("pluginCount", 0),
|
|
6225
|
+
"businessRuleCount": row.get("opBizRuleCount", 0),
|
|
6226
|
+
} for row in unresolved]},
|
|
6227
|
+
"recovery": {"action": "implement_or_select_operation",
|
|
6228
|
+
"message": "Implement the requested behavior or select an existing implemented operation, then retry the same menu intent."},
|
|
6229
|
+
}
|
|
6230
|
+
for index, item in enumerate(desired, 1):
|
|
6231
|
+
item["Index"] = index
|
|
6232
|
+
result = _dropdown_snapshot_edit(cfg, token, form_number, element_key, current, desired, locale=locale)
|
|
6233
|
+
if isinstance(result, dict):
|
|
6234
|
+
if not _is_api_success(result):
|
|
6235
|
+
action = {
|
|
6236
|
+
"DROPDOWN_MENU_BASELINE_CHANGED": "refresh_menu_and_reapply_intent",
|
|
6237
|
+
"DROPDOWN_MENU_SNAPSHOT_UNAVAILABLE": "check_full_metadata_api_availability",
|
|
6238
|
+
"DROPDOWN_MENU_NOT_EDITABLE": "check_designer_editability",
|
|
6239
|
+
"DROPDOWN_MENU_LOCALE_REQUIRED": "select_title_locale",
|
|
6240
|
+
"DROPDOWN_MENU_SNAPSHOT_READBACK_MISMATCH": "compare_recovery_snapshots",
|
|
6241
|
+
}.get(result.get("errorCode"), "inspect_menu_edit_failure")
|
|
6242
|
+
result["recovery"] = {
|
|
6243
|
+
"automaticRetry": False,
|
|
6244
|
+
"action": action,
|
|
6245
|
+
"message": result.get("message"),
|
|
6246
|
+
"scope": {"formNumber": form_number, "elementKey": element_key},
|
|
6247
|
+
}
|
|
6248
|
+
if not isinstance(result.get("data"), dict):
|
|
6249
|
+
result["data"] = {}
|
|
6250
|
+
result["data"].update({
|
|
6251
|
+
"formNumber": form_number, "elementKey": element_key,
|
|
6252
|
+
"changed": True if _is_api_success(result) else None,
|
|
6253
|
+
})
|
|
6254
|
+
return result
|
|
6255
|
+
|
|
6256
|
+
|
|
5595
6257
|
def do_set_org_funcs(cfg, token, form_number, element_key, data):
|
|
5596
6258
|
"""Resolve a live org-function name/code, write it, then independently read it back."""
|
|
5597
6259
|
resolved, error = _resolve_org_func(
|