From 8d148a89a5efe0164061227100d54a7ff4969d50 Mon Sep 17 00:00:00 2001 From: Stepan Houdek Date: Mon, 8 Jun 2026 09:55:13 +0200 Subject: [PATCH] Add: support for formula definition in attribute creation options and registry --- .../Dataverse/CreateAttributeOptions.cs | 5 +++++ .../Entity/AttributeTypeRegistry.cs | 2 +- .../Entity/EntityAttributeCreateCliCommand.cs | 11 ++++++++++- .../DataverseEntityMetadataService.cs | 19 +++++++++++++++---- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/TALXIS.CLI.Core/Contracts/Dataverse/CreateAttributeOptions.cs b/src/TALXIS.CLI.Core/Contracts/Dataverse/CreateAttributeOptions.cs index 6dfe2c02..ebbd1994 100644 --- a/src/TALXIS.CLI.Core/Contracts/Dataverse/CreateAttributeOptions.cs +++ b/src/TALXIS.CLI.Core/Contracts/Dataverse/CreateAttributeOptions.cs @@ -82,4 +82,9 @@ public sealed record CreateAttributeOptions public bool IsAuditable { get; init; } public bool IsSearchable { get; init; } = true; public bool IsSecured { get; init; } + + // === Formula === + + /// Power Fx formula expression. When set, SourceType is set to 3 (Formula). Supported base types: string, number, decimal, float, money, bool, datetime, choice, multichoice. + public string? FormulaDefinition { get; init; } } diff --git a/src/TALXIS.CLI.Features.Environment/Entity/AttributeTypeRegistry.cs b/src/TALXIS.CLI.Features.Environment/Entity/AttributeTypeRegistry.cs index d22fa5e6..a42500a7 100644 --- a/src/TALXIS.CLI.Features.Environment/Entity/AttributeTypeRegistry.cs +++ b/src/TALXIS.CLI.Features.Environment/Entity/AttributeTypeRegistry.cs @@ -22,7 +22,7 @@ public static class AttributeTypeRegistry public static IReadOnlyList SharedParameterNames { get; } = new[] { "entity", "name", "display-name", "description", "required", "solution", - "is-auditable", "is-searchable", "is-secured" + "is-auditable", "is-searchable", "is-secured", "formula-definition" }; private static ReadOnlyCollection BuildAllTypes() diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityAttributeCreateCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Entity/EntityAttributeCreateCliCommand.cs index f6fefc39..84f079bc 100644 --- a/src/TALXIS.CLI.Features.Environment/Entity/EntityAttributeCreateCliCommand.cs +++ b/src/TALXIS.CLI.Features.Environment/Entity/EntityAttributeCreateCliCommand.cs @@ -32,7 +32,7 @@ public class EntityAttributeCreateCliCommand : StagedCliCommand [CliOption(Name = "--name", Description = "Schema name for the new column.", Required = true)] public string Name { get; set; } = null!; - [CliOption(Name = "--type", Description = "Column type: string, memo, number, decimal, float, money, bool, datetime, choice, multichoice, lookup, polymorphic-lookup, customer, image, file, bigint.", Required = true)] + [CliOption(Name = "--type", Description = "Column type: string, memo, number, decimal, float, money, bool, datetime, choice, multichoice, lookup, polymorphic-lookup, customer, image, file, bigint. Add --formula-definition to make any supported type a formula column.", Required = true)] public AttributeTypeArg Type { get; set; } // === Optional for all types === @@ -134,6 +134,11 @@ public class EntityAttributeCreateCliCommand : StagedCliCommand [DefaultValue(true)] public bool CanStoreFullImage { get; set; } = true; + // === Formula === + + [CliOption(Name = "--formula-definition", Description = "Power Fx formula expression. When provided, creates the column as a formula field (SourceType=3). Supported types: string, number, decimal, float, money, bool, datetime, choice, multichoice.", Required = false)] + public string? FormulaDefinition { get; set; } + protected override async Task ExecuteAsync() { ValidateExecutionMode(); @@ -177,6 +182,7 @@ protected override async Task ExecuteAsync() ["cascadeDelete"] = options.CascadeDelete, ["maxSizeKb"] = options.MaxSizeKb, ["canStoreFullImage"] = options.CanStoreFullImage, + ["formulaDefinition"] = options.FormulaDefinition, ["isAuditable"] = options.IsAuditable, ["isSearchable"] = options.IsSearchable, ["isSecured"] = options.IsSecured @@ -273,6 +279,9 @@ private CreateAttributeOptions BuildCreateOptions() MaxSizeKb = MaxSizeKb, CanStoreFullImage = CanStoreFullImage, + // Formula + FormulaDefinition = FormulaDefinition, + // Shared metadata properties IsAuditable = IsAuditable, IsSearchable = IsSearchable, diff --git a/src/TALXIS.CLI.Platform.Dataverse.Application/Services/DataverseEntityMetadataService.cs b/src/TALXIS.CLI.Platform.Dataverse.Application/Services/DataverseEntityMetadataService.cs index 21f5f26b..0066d1a0 100644 --- a/src/TALXIS.CLI.Platform.Dataverse.Application/Services/DataverseEntityMetadataService.cs +++ b/src/TALXIS.CLI.Platform.Dataverse.Application/Services/DataverseEntityMetadataService.cs @@ -145,7 +145,7 @@ public async Task CreateAttributeAsync( switch (options.Type) { case "string": - await ExecuteCreateAttribute(conn, options, new StringAttributeMetadata + var strMeta = new StringAttributeMetadata { SchemaName = options.SchemaName, DisplayName = displayLabel, @@ -153,7 +153,9 @@ public async Task CreateAttributeAsync( RequiredLevel = requiredLevel, MaxLength = options.MaxLength ?? 200, FormatName = options.StringFormat is not null ? MapStringFormat(options.StringFormat) : StringFormatName.Text - }, ct).ConfigureAwait(false); + }; + if (options.FormulaDefinition is not null) { strMeta.SourceType = 3; strMeta.FormulaDefinition = options.FormulaDefinition; } + await ExecuteCreateAttribute(conn, options, strMeta, ct).ConfigureAwait(false); break; case "memo": @@ -178,6 +180,7 @@ public async Task CreateAttributeAsync( if (options.MinValue.HasValue) intMeta.MinValue = (int)options.MinValue.Value; if (options.MaxValue.HasValue) intMeta.MaxValue = (int)options.MaxValue.Value; if (options.NumberFormat.HasValue()) intMeta.Format = MapIntegerFormat(options.NumberFormat!); + if (options.FormulaDefinition is not null) { intMeta.SourceType = 3; intMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, intMeta, ct).ConfigureAwait(false); break; @@ -192,6 +195,7 @@ public async Task CreateAttributeAsync( if (options.MinValue.HasValue) decMeta.MinValue = (decimal)options.MinValue.Value; if (options.MaxValue.HasValue) decMeta.MaxValue = (decimal)options.MaxValue.Value; if (options.Precision.HasValue) decMeta.Precision = options.Precision.Value; + if (options.FormulaDefinition is not null) { decMeta.SourceType = 3; decMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, decMeta, ct).ConfigureAwait(false); break; @@ -206,6 +210,7 @@ public async Task CreateAttributeAsync( if (options.MinValue.HasValue) dblMeta.MinValue = options.MinValue.Value; if (options.MaxValue.HasValue) dblMeta.MaxValue = options.MaxValue.Value; if (options.Precision.HasValue) dblMeta.Precision = options.Precision.Value; + if (options.FormulaDefinition is not null) { dblMeta.SourceType = 3; dblMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, dblMeta, ct).ConfigureAwait(false); break; @@ -221,11 +226,12 @@ public async Task CreateAttributeAsync( if (options.MaxValue.HasValue) moneyMeta.MaxValue = options.MaxValue.Value; if (options.Precision.HasValue) moneyMeta.Precision = options.Precision.Value; if (options.PrecisionSource.HasValue) moneyMeta.PrecisionSource = options.PrecisionSource.Value; + if (options.FormulaDefinition is not null) { moneyMeta.SourceType = 3; moneyMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, moneyMeta, ct).ConfigureAwait(false); break; case "bool": - await ExecuteCreateAttribute(conn, options, new BooleanAttributeMetadata + var boolMeta = new BooleanAttributeMetadata { SchemaName = options.SchemaName, DisplayName = displayLabel, @@ -234,7 +240,9 @@ public async Task CreateAttributeAsync( OptionSet = new BooleanOptionSetMetadata( new OptionMetadata(new Label(options.TrueLabel, 1033), 1), new OptionMetadata(new Label(options.FalseLabel, 1033), 0)) - }, ct).ConfigureAwait(false); + }; + if (options.FormulaDefinition is not null) { boolMeta.SourceType = 3; boolMeta.FormulaDefinition = options.FormulaDefinition; } + await ExecuteCreateAttribute(conn, options, boolMeta, ct).ConfigureAwait(false); break; case "datetime": @@ -248,6 +256,7 @@ public async Task CreateAttributeAsync( }; if (options.DateTimeBehavior is not null) dtMeta.DateTimeBehavior = MapDateTimeBehavior(options.DateTimeBehavior); + if (options.FormulaDefinition is not null) { dtMeta.SourceType = 3; dtMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, dtMeta, ct).ConfigureAwait(false); break; @@ -919,6 +928,7 @@ private static async Task CreatePicklistAttribute( picklistMeta.OptionSet = BuildLocalOptionSet(options); } + if (options.FormulaDefinition is not null) { picklistMeta.SourceType = 3; picklistMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, picklistMeta, ct).ConfigureAwait(false); } @@ -944,6 +954,7 @@ private static async Task CreateMultiSelectPicklistAttribute( multiMeta.OptionSet = BuildLocalOptionSet(options); } + if (options.FormulaDefinition is not null) { multiMeta.SourceType = 3; multiMeta.FormulaDefinition = options.FormulaDefinition; } await ExecuteCreateAttribute(conn, options, multiMeta, ct).ConfigureAwait(false); }