Page 2516 AppSource Product Details, source in 26

Source242526272829metadata in 26

src/System Application/App/AppSource Gallery/src/AppSourceProductDetails.Page.al415 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license inFormation.
// ------------------------------------------------------------------------------------------------
namespace System.Apps.AppSource;

using System.Apps;

/// <summary>
/// Single AppSource Product Details Page
/// </summary>
page 2516 "AppSource Product Details"
{
    PageType = Card;
    ApplicationArea = All;
    Editable = false;
    Caption = 'App overview';
    DataCaptionExpression = AppSourceJsonUtilities.GetStringValue(ProductObject, 'displayName');

    InherentEntitlements = X;
    InherentPermissions = X;

    layout
    {
        area(Content)
        {
            group(OfferGroup)
            {
                Caption = 'Offer';

                field(Offer_UniqueID; UniqueProductID)
                {
                    Caption = 'Unique Product ID';
                    ToolTip = 'Specifies the unique product identifier.';
                    Visible = false;
                }
                field(Offer_ProductType; AppSourceJsonUtilities.GetStringValue(ProductObject, 'productType'))
                {
                    Caption = 'Product Type';
                    ToolTip = 'Specifies the delivery method or deployment mode of the offer.';
                    Visible = false;
                }
                field(Offer_DisplayName; AppSourceJsonUtilities.GetStringValue(ProductObject, 'displayName'))
                {
                    Caption = 'Display Name';
                    ToolTip = 'Specifies the display name of the offer.';
                }
                field(Offer_PublisherID; AppSourceJsonUtilities.GetStringValue(ProductObject, 'publisherId'))
                {
                    Caption = 'Publisher ID';
                    ToolTip = 'Specifies the ID of the publisher.';
                    Visible = false;
                }
                field(Offer_PublisherDisplayName; AppSourceJsonUtilities.GetStringValue(ProductObject, 'publisherDisplayName'))
                {
                    Caption = 'Publisher Display Name';
                    ToolTip = 'Specifies the display name of the publisher.';
                }
                field(Offer_PublisherType; AppSourceJsonUtilities.GetStringValue(ProductObject, 'publisherType'))
                {
                    Caption = 'Publisher Type';
                    ToolTip = 'Specifies whether the offer is a Microsoft or third party product.';
                }
                field(Offer_LastModifiedDateTime; AppSourceJsonUtilities.GetStringValue(ProductObject, 'lastModifiedDateTime'))
                {
                    Caption = 'Last Modified Date Time';
                    ToolTip = 'Specifies the date the offer was last updated.';
                    Visible = false;
                }
            }
            group(DescriptionGroup)
            {
                ShowCaption = false;

                field(Description_Description; AppSourceJsonUtilities.GetStringValue(ProductObject, 'description'))
                {
                    Caption = 'Description';
                    MultiLine = true;
                    ExtendedDatatype = RichContent;
                    ApplicationArea = All;
                    ToolTip = 'Specifies the description text of the offer.';
                }
            }
            group(PlansGroup)
            {
                Caption = 'Plans';
                Visible = PlansAreVisible;

                field("PlansOverview"; PlansOverview)
                {
                    ExtendedDatatype = RichContent;
                    MultiLine = true;
                    Caption = 'Plans Overview';
                    ToolTip = 'Specifies the overview of all the plans available for the offer.';
                }
            }

            group(RatingGroup)
            {
                Caption = 'Rating';

                field(Rating_Popularity; AppSourceJsonUtilities.GetStringValue(ProductObject, 'popularity'))
                {
                    Caption = 'Popularity';
                    ToolTip = 'Specifies a value from 0-10 indicating the popularity of the offer.';
                }
                field(Rating_RatingAverage; AppSourceJsonUtilities.GetStringValue(ProductObject, 'ratingAverage'))
                {
                    Caption = 'Rating Average';
                    ToolTip = 'Specifies a value from 0-5 indicating the average user rating.';
                }
                field(Rating_RatingCount; AppSourceJsonUtilities.GetStringValue(ProductObject, 'ratingCount'))
                {
                    Caption = 'Rating Count';
                    ToolTip = 'Specifies the number of users that have rated the offer.';
                }
            }

            group(LinksGroup)
            {
                Caption = 'Links';

                field(Links_LegalTermsUri; AppSourceJsonUtilities.GetStringValue(ProductObject, 'legalTermsUri'))
                {
                    Caption = 'Legal Terms Uri';
                    ToolTip = 'Specifies the legal terms of the offer.';
                    ExtendedDatatype = URL;
                }
                field(Links_PrivacyPolicyUri; AppSourceJsonUtilities.GetStringValue(ProductObject, 'privacyPolicyUri'))
                {
                    Caption = 'Privacy Policy Uri';
                    ToolTip = 'Specifies the privacy policy of the offer.';
                    ExtendedDatatype = URL;
                }
                field(Links_SupportUri; AppSourceJsonUtilities.GetStringValue(ProductObject, 'supportUri'))
                {
                    Caption = 'Support Uri';
                    ToolTip = 'Specifies the support Uri of the offer.';
                    ExtendedDatatype = URL;
                }
            }
        }
    }

    actions
    {
        area(Promoted)
        {
            actionref(Open_Promoted; OpenInAppSource) { }
            actionref(Install_Promoted; Install) { }
            actionref(InstallFromAppSource_Promoted; InstallFromAppSource) { }
            actionref(Uninstall_Promoted; Uninstall) { }
        }

        area(Processing)
        {
            action(OpenInAppSource)
            {
                Caption = 'View on AppSource';
                Scope = Page;
                Image = Info;
                ToolTip = 'Opens the app on AppSource.';

                trigger OnAction()
                begin
                    AppSourceProductManager.OpenAppInAppSource(UniqueProductID);
                end;
            }

            action(Install)
            {
                Caption = 'Install App';
                Scope = Page;
                Enabled = (not CurrentRecordCanBeUninstalled) and CurrentRecordCanBeInstalled;
                Visible = (not CurrentRecordCanBeUninstalled) and CurrentRecordCanBeInstalled;
                Image = Insert;
                ToolTip = 'Installs the app.';

                trigger OnAction()
                var
                    ExtensionManagement: Codeunit "Extension Management";
                begin
                    if PlansAreVisible then
                        if not Confirm(PurchaseLicensesElsewhereLbl) then
                            exit;

                    ExtensionManagement.InstallMarketplaceExtension(AppID);
                end;
            }

            action(InstallFromAppSource)
            {
                Caption = 'Install from AppSource';
                Scope = Page;
                Image = Download;
                ToolTip = 'Installs the app from Microsoft AppSource.';
                Enabled = (not CurrentRecordCanBeUninstalled) and (not CurrentRecordCanBeInstalled);
                Visible = (not CurrentRecordCanBeUninstalled) and (not CurrentRecordCanBeInstalled);

                trigger OnAction()
                begin
                    AppSourceProductManager.OpenAppInAppSource(UniqueProductID);
                end;
            }

            action(Uninstall)
            {
                Caption = 'Uninstall App';
                Scope = Page;
                Enabled = CurrentRecordCanBeUninstalled;
                Image = Delete;
                ToolTip = 'Uninstalls the app.';
                AccessByPermission = tabledata "Installed Application" = d;

                trigger OnAction()
                begin
                    ExtensionManagement.UninstallExtension(AppID, true);
                end;
            }
        }
    }

    var
        AppSourceJsonUtilities: Codeunit "AppSource Json Utilities";
        ExtensionManagement: Codeunit "Extension Management";
        AppSourceProductManager: Codeunit "AppSource Product Manager";
        ProductObject: JsonObject;
        UniqueProductID: Text;
        AppID: Text;
        CurrentRecordCanBeUninstalled: Boolean;
        CurrentRecordCanBeInstalled: Boolean;
        PlansOverview: Text;
        PlansAreVisible: Boolean;
        PurchaseLicensesElsewhereLbl: Label 'Installing this app might lead to undesired behavior if licenses are not purchased before use. You must purchase licenses through Microsoft AppSource.\Do you want to continue with the installation?';
        PlanLinePrUserPrMonthLbl: Label '%1 %2 user/month', Comment = 'Price added a plan line, %1 is the currency code, such as USD or IDR, %2 is the price';
        PlanLinePrUserPrYearLbl: Label '%1 %2 user/year', Comment = 'Price added a plan line, %1 is the currency code, such as USD or IDR, %2 is the price';
        PlanLineFirstMonthIsFreeLbl: Label 'First month free, then %1.', Comment = 'Added to the plan line when the first month is free, %1 is the plan after the trial period.';
        PlanLinePriceVariesLbl: Label 'varies', Comment = 'Added to the plan line when the price varies.';
        PlanLinesTemplateLbl: Label '<table width="100%" padding="2" style="border-collapse:collapse;text-align:left;vertical-align:top;"><tr style="border-bottom: 1pt solid black;"><td>%1</td><td>%2</td><td>%3</td><td>%4</td></tr>%5</table>', Comment = 'Template for the plans section, %1 is the plans column header, %2 is the description column header, %3 is the monthly price column header, %4 is the yearly column header, %5 is the plan rows', Locked = true;
        PlanLineItemTemplateLbl: Label '<tr style="text-align:left;vertical-align:top;"><td>%1</td><td>%2</td><td>%3</td><td>%4</td></tr>', Comment = 'Template for a plan line item, %1 is the plan name, %2 is the plan description, %3 is the monthly price, %4 is the annual price', Locked = true;
        PlanLinesColumnPlansLbl: Label 'Plans', Comment = 'Column header for the plans section';
        PlanLinesColumnDescriptionLbl: Label 'Description', Comment = 'Column header for the plans section';
        PlanLinesColumnMonthlyPriceLbl: Label 'Monthly Price', Comment = 'Column header for the plans section';
        PlanLinesColumnAnnualPriceLbl: Label 'Annual Price', Comment = 'Column header for the plans section';

    procedure SetProduct(var ToProductObject: JsonObject)
    var
        ProductPlansToken: JsonToken;
    begin
        ProductObject := ToProductObject;
        UniqueProductID := AppSourceJsonUtilities.GetStringValue(ProductObject, 'uniqueProductId');
        AppId := AppSourceProductManager.ExtractAppIDFromUniqueProductID(UniqueProductID);
        CurrentRecordCanBeUninstalled := false;
        CurrentRecordCanBeInstalled := false;
        if (not IsNullGuid(AppID)) then
            CurrentRecordCanBeUninstalled := ExtensionManagement.IsInstalledByAppID(AppID);

        if ProductObject.Get('plans', ProductPlansToken) then
            RenderPlans(ProductPlansToken);
    end;

    procedure RenderPlans(PlansObject: JsonToken)
    var
        AllPlans: JsonArray;
        PlanLinesBuilder: TextBuilder;
        PlanItem: JsonToken;
        PlanItemObject: JsonObject;
        PlanItemArray: JsonArray;
        MonthlyPriceText, YearlyPriceText : Text;
        i, AvailabilitiesAdded : Integer;
    begin
        AvailabilitiesAdded := 0;
        PlanLinesBuilder.Clear();

        AllPlans := PlansObject.AsArray();
        for i := 0 to AllPlans.Count() do
            if AllPlans.Get(i, PlanItem) then begin
                PlanItemObject := PlanItem.AsObject();
                if PlanItem.SelectToken('availabilities', PlanItem) then begin
                    PlanItemArray := PlanItem.AsArray();
                    if PlanItemArray.Count() > 0 then begin
                        if BuildPlanPriceText(PlanItemArray, MonthlyPriceText, YearlyPriceText) then
                            AvailabilitiesAdded += 1;
                        PlanLinesBuilder.Append(
                            StrSubstNo(
                                PlanLineItemTemplateLbl,
                                GetStringValue(PlanItemObject, 'displayName'),
                                GetStringValue(PlanItemObject, 'description'),
                                MonthlyPriceText,
                                YearlyPriceText));
                    end;
                end;
            end;

        if AvailabilitiesAdded > 0 then begin
            PlansAreVisible := true;
            PlansOverview := StrSubstNo(
                PlanLinesTemplateLbl,
                PlanLinesColumnPlansLbl,
                PlanLinesColumnDescriptionLbl,
                PlanLinesColumnMonthlyPriceLbl,
                PlanLinesColumnAnnualPriceLbl,
                PlanLinesBuilder.ToText());
        end else begin
            PlansAreVisible := false;
            PlansOverview := '';
        end;

        CurrentRecordCanBeInstalled := (AppID <> '') and (not CurrentRecordCanBeUninstalled) and AppSourceProductManager.CanInstallProductWithPlans(UniqueProductID);
    end;

    local procedure BuildPlanPriceText(Availabilities: JsonArray; var MonthlyPriceText: Text; var YearlyPriceText: Text): Boolean
    var
        Availability: JsonToken;
        AvailabilityObject: JsonObject;
        TermItem: JsonToken;
        ArrayItem: JsonArray;
        i: Integer;
        Currency: Text;
        Monthly, Yearly : Decimal;
        FreeTrial: Boolean;
        PriceText: Text;
    begin
        FreeTrial := false;
        for i := 0 to Availabilities.Count do
            if Availabilities.Get(i, Availability) then begin
                AvailabilityObject := Availability.AsObject();

                if (GetStringValue(AvailabilityObject, 'hasFreeTrials') = 'true') then
                    FreeTrial := true;

                if (AvailabilityObject.Get('terms', TermItem)) then
                    if TermItem.IsArray then begin
                        ArrayItem := TermItem.AsArray();
                        GetTerms(ArrayItem, Monthly, Yearly, Currency);
                    end;
            end;

        YearlyPriceText := '';
        MonthlyPriceText := '';
        if (Monthly = 0) or (Yearly = 0) or not FreeTrial then
            exit(false);

        if FreeTrial then begin
            if Monthly > 0 then begin
                PriceText := StrSubstNo(PlanLinePrUserPrMonthLbl, Currency, Format(Monthly, 12, 0));
                MonthlyPriceText := StrSubstNo(PlanLineFirstMonthIsFreeLbl, PriceText)
            end
            else
                MonthlyPriceText := StrSubstNo(PlanLineFirstMonthIsFreeLbl, PlanLinePriceVariesLbl);
            if Yearly > 0 then begin
                PriceText := StrSubstNo(PlanLinePrUserPrYearLbl, Currency, Format(Yearly, 12, 0));
                YearlyPriceText := StrSubstNo(PlanLineFirstMonthIsFreeLbl, PriceText)
            end
            else
                YearlyPriceText := StrSubstNo(PlanLineFirstMonthIsFreeLbl, PlanLinePriceVariesLbl);
        end
        else begin
            if Monthly > 0 then
                MonthlyPriceText := StrSubstNo(PlanLinePrUserPrMonthLbl, Currency, Format(Monthly, 12, 0));
            if Yearly > 0 then
                YearlyPriceText := StrSubstNo(PlanLinePrUserPrYearLbl, Currency, Format(Yearly, 12, 0));
        end;

        exit(true);
    end;

    local procedure GetTerms(Terms: JsonArray; var Monthly: Decimal; var Yearly: Decimal; var Currency: Text)
    var
        Item: JsonToken;
        PriceToken: JsonToken;
        Price: JsonObject;
        PriceValue: Decimal;
        i: Integer;
    begin
        for i := 0 to Terms.Count() do
            if (Terms.Get(i, Item)) then begin
                Item.SelectToken('price', PriceToken);
                Price := PriceToken.AsObject();
                Currency := GetStringValue(Price, 'currencyCode');
                if not Evaluate(PriceValue, GetStringValue(Price, 'listPrice'), 9) then
                    PriceValue := 0;

                case GetStringValue(Item.AsObject(), 'termUnit') of
                    'P1Y':
                        Yearly := PriceValue;
                    'P1M':
                        Monthly := PriceValue;
                end;
            end;
    end;

    local procedure GetStringValue(JsonObject: JsonObject; PropertyName: Text): Text
    var
        JsonValue: JsonValue;
    begin
        if GetJsonValue(JsonObject, PropertyName, JsonValue) then
            exit(JsonValue.AsText());
        exit('');
    end;

    procedure GetJsonValue(JsonObject: JsonObject; PropertyName: Text; var ReturnValue: JsonValue): Boolean
    var
        JsonToken: JsonToken;
    begin
        if JsonObject.Contains(PropertyName) then
            if JsonObject.Get(PropertyName, JsonToken) then
                if not JsonToken.AsValue().IsNull() then begin
                    ReturnValue := JsonToken.AsValue();
                    exit(true);
                end;
        exit(false);
    end;
}