From 87b9cba8f0298b34f2a759233802ed5e8a67fad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tine=20Stari=C4=8D?= <42935028+tinestaric@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:01:09 +0300 Subject: [PATCH] [Shopify] Add Metafields to Products and Variants (#26185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a PR without a dedicated issue as it's part of delivery for development agreed directly with @AndreiPanko Here's the overview of the changes: Obsolete fields: • Obsoleted Value Type as it‘s no longer used in Shopify API -> Replaced with Type field • Obsoleted Owner Resource field. I‘m not sure why this was a free text that was then always populated through a case statement. It‘s replaced with an Owner Type field that is an Enum. New enums and interfaces: • „Shpfy Metafield Type“ and „Shpfy IMetafield Type“ – Used for handling the metafield type. The interface defines if the type has a special AssistEdit, logic to execute the assist edit, and validations when free input is allowed for a value. I have a couple of questions here, see below. Rating, Rich Text, and List Types are not included, Rating and Rich Text options are added but commented out so it‘s clear that it was intentional. It also has String and Integer types which are there to support backward compatibility, if old values are still in Shopify. • „Shpfy Metafield Owner Type“ and „Shpfy IMetafield Owner Type“ – Used for building the full owner resource ID (gid://shopify//) and for retrieving metafield IDs of a single resource (used during export, to identify which metafields should be sent for update based on UpdatedAt) • Metafield Dimenion/Volume/Weight Type – three enums to support a selection of Units when assist editing these metafield types Pages: • „Shpfy Metafields“ – used for viewing and editing metafields for a resource. It‘s only editable if Shop is syncing Products to Shopify and Can update Shopify products. On Insert of a new record, it‘s immediately sent to Shopify and is only inserted in the DB when we get back the Shopify ID for the metafield. Types that have AssistEdit functionality defined cannot be edited directly on the line. OnValidate of Value a check is executed to ensure value is correct for a type. • „Shpfy Metafield Assist Edit“ – used for assist edit of complex metafield types. It has several groups defined, and only the appropriate one is displayed to the user. • Action for display metafields for a resource has been added to the Products and Variants pages. GraphQL query changes: • ProductById and VariantById have been modified so that they retrieve the first 50 metafields (previously the first 10). Previously there was a filter to only retrieve Metafields with the „Microsoft.Dynamics365.BusinessCentral“ namespace. I‘ve removed that to get all the metafields of a resource. • ProductMetafieldIds and VariantMetafieldIds were added. These are used during the export of metafields to determine which BC metafields should be sent for an update • MetafieldsSet – This is used to create/update metafields for a resource Changes to API codeunits: • Product API – on UpdateShopifyProductFields a metafields part of JSON is now passed to Metafields API where it creates or updates Metafields in BC based on information retrieved from Shopify • Variant API – on UpdateShopifyVariantFields the metafields part of the JSON is passed to Metafields API, same as above. • „Shpfy Product Export“ – on UpdateProductData execute Metafields API to update/create metafields for the product and all variants that belong to the product • „Metafield API“ – Implements logic to parse the „metafields“JSON that is retrieved with products/variants during import and creates/updates metafield records in BC. Adds logic to export metafields for a specific resource. Only sends an update for metafields that have a lower UpdatedAt timestamp than BC record‘s last updated at timestamp. It batches the Metafields to 25 at a time, as that‘s the max MetafieldsSet mutation can handle as per the documentation. Added logic to delete metafields on delete of a product/variant A couple of questions or concerns: • New metafields that get created in Shopify do not have a Metafield Definition attached to them. Should we care about this right now? If yes, should we also store metafield definitions in BC, or just create new definitions if any are missing in Shopify? • I‘ve obsoleted the two fields and one enum, should I be wrapping the obsoletions in some preprocessing symbols? Which one? • I‘m doing some regex validations when checking if the value of metafield is correct. I‘m not sure if the one for mixed reference and file reference is correct. I‘d appreciate it if someone could take a look at that. • If value validation fails, it throws a pretty „simple“ error message right now, should I make this more expressive? • Currently, the „update metafields“ has no error handling in place if the update query fails. Technically I‘m trying to catch everything on data input, but there can always be unexpected situations. How should I handle this? • The code for sending the metafield to Shopify immediately after it‘s inserted through UI is currently on the Page‘s OnInsertRecord trigger. I‘m not sure about it, should I move it to the table trigger? The hesitation is because there already is some logic on the table from the partial implementation of metafields on Customers. • I‘ve placed interface implementations in a separate folder under Codeunits folder, that‘s not the usual practice on MS apps, so I wanted to double-check it. • Money metafield type needs to have the same currency as the shop, which leads to this completely separate code path after validating the Value. I‘m not sure if there‘s a better approach to handle this. • I saw that most procedures in the app have documentation triggers that are not really beneficial. I‘ve only added them to the public or internal procedures. I‘ve also skipped on most object documentation triggers. Fixes #26819 Fixes [AB#443908](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/443908) --------- Co-authored-by: Tine Staric Co-authored-by: Jesper Schulz-Wedde Co-authored-by: Onat Buyukakkus <55088871+onbuyuka@users.noreply.github.com> --- Apps/W1/Shopify/app/app.json | 2 +- .../src/Base/Tables/ShpfyMetafield.Table.al | 103 ------- .../Codeunits/ShpfyCustomerExport.Codeunit.al | 2 +- .../Customers/Tables/ShpfyCustomer.Table.al | 1 + .../ShpfyGQLMetafieldsSet.Codeunit.al | 27 ++ .../Codeunits/ShpfyGQLProductById.Codeunit.al | 2 +- .../ShpfyGQLProductMetafieldIds.Codeunit.al | 28 ++ .../Codeunits/ShpfyGQLVariantById.Codeunit.al | 2 +- .../ShpfyGQLVariantMetafieldIds.Codeunit.al | 28 ++ .../GraphQL/Enums/ShpfyGraphQLType.Enum.al | 15 + .../ShpfyMtfldTypeBoolean.Codeunit.al | 27 ++ .../ShpfyMtfldTypeCollectRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeColor.Codeunit.al | 29 ++ .../ShpfyMtfldTypeDate.Codeunit.al | 27 ++ .../ShpfyMtfldTypeDateTime.Codeunit.al | 27 ++ .../ShpfyMtfldTypeDimension.Codeunit.al | 70 +++++ .../ShpfyMtfldTypeFileRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeInteger.Codeunit.al | 37 +++ .../ShpfyMtfldTypeJson.Codeunit.al | 27 ++ .../ShpfyMtfldTypeMetaobjRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeMixedRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeMoney.Codeunit.al | 74 +++++ .../ShpfyMtfldTypeMultiText.Codeunit.al | 30 ++ .../ShpfyMtfldTypeNumDecimal.Codeunit.al | 30 ++ .../ShpfyMtfldTypeNumInteger.Codeunit.al | 37 +++ .../ShpfyMtfldTypePageRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeProductRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeSingleText.Codeunit.al | 25 ++ .../ShpfyMtfldTypeString.Codeunit.al | 25 ++ .../ShpfyMtfldTypeUrl.Codeunit.al | 29 ++ .../ShpfyMtfldTypeVariantRef.Codeunit.al | 29 ++ .../ShpfyMtfldTypeVolume.Codeunit.al | 72 +++++ .../ShpfyMtfldTypeWeight.Codeunit.al | 72 +++++ .../ShpfyMetafieldOwnerCustomer.Codeunit.al | 19 ++ .../ShpfyMetafieldOwnerProduct.Codeunit.al | 42 +++ .../ShpfyMetafieldOwnerVariant.Codeunit.al | 42 +++ .../Codeunits/ShpfyMetafieldAPI.Codeunit.al | 237 ++++++++++++++++ .../Enums/ShpfyMetafieldDimensionType.Enum.al | 35 +++ .../Enums/ShpfyMetafieldOwnerType.Enum.al | 24 ++ .../Enums/ShpfyMetafieldType.Enum.al | 161 +++++++++++ .../Enums/ShpfyMetafieldValueType.Enum.al | 3 + .../Enums/ShpfyMetafieldVolumeType.Enum.al | 59 ++++ .../Enums/ShpfyMetafieldWeightType.Enum.al | 26 ++ .../ShpfyIMetafieldOwnerType.Interface.al | 29 ++ .../ShpfyIMetafieldType.Interface.al | 35 +++ .../Pages/ShpfyMetafieldAssistEdit.Page.al | 264 ++++++++++++++++++ .../Metafields/Pages/ShpfyMetafields.Page.al | 130 +++++++++ .../Metafields/Tables/ShpfyMetafield.Table.al | 219 +++++++++++++++ .../Codeunits/ShpfyProductAPI.Codeunit.al | 8 +- .../Codeunits/ShpfyProductExport.Codeunit.al | 19 +- .../Codeunits/ShpfyVariantAPI.Codeunit.al | 3 +- .../src/Products/Pages/ShpfyProducts.Page.al | 18 ++ .../src/Products/Pages/ShpfyVariants.Page.al | 14 + .../src/Products/Tables/ShpfyProduct.Table.al | 6 + .../src/Products/Tables/ShpfyVariant.Table.al | 6 + 55 files changed, 2339 insertions(+), 111 deletions(-) delete mode 100644 Apps/W1/Shopify/app/src/Base/Tables/ShpfyMetafield.Table.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLMetafieldsSet.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductMetafieldIds.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantMetafieldIds.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeBoolean.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeCollectRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeColor.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDate.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDateTime.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDimension.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeFileRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeInteger.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeJson.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMetaobjRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMixedRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMoney.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMultiText.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumDecimal.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumInteger.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypePageRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeProductRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeSingleText.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeString.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeUrl.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVariantRef.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVolume.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeWeight.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerCustomer.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerProduct.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerVariant.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldDimensionType.Enum.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldOwnerType.Enum.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldType.Enum.al rename Apps/W1/Shopify/app/src/{Base => Metafields}/Enums/ShpfyMetafieldValueType.Enum.al (75%) create mode 100644 Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldVolumeType.Enum.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldWeightType.Enum.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldOwnerType.Interface.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldType.Interface.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafieldAssistEdit.Page.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafields.Page.al create mode 100644 Apps/W1/Shopify/app/src/Metafields/Tables/ShpfyMetafield.Table.al diff --git a/Apps/W1/Shopify/app/app.json b/Apps/W1/Shopify/app/app.json index 0be3e1f856..3ddb14510c 100644 --- a/Apps/W1/Shopify/app/app.json +++ b/Apps/W1/Shopify/app/app.json @@ -21,7 +21,7 @@ "idRanges": [ { "from": 30100, - "to": 30350 + "to": 30360 } ], "internalsVisibleTo": [ diff --git a/Apps/W1/Shopify/app/src/Base/Tables/ShpfyMetafield.Table.al b/Apps/W1/Shopify/app/src/Base/Tables/ShpfyMetafield.Table.al deleted file mode 100644 index f368c2ab82..0000000000 --- a/Apps/W1/Shopify/app/src/Base/Tables/ShpfyMetafield.Table.al +++ /dev/null @@ -1,103 +0,0 @@ -namespace Microsoft.Integration.Shopify; - -/// -/// Table Shpfy Metafield (ID 30101). -/// -table 30101 "Shpfy Metafield" -{ - Access = Internal; - Caption = 'Shopify Metafield'; - DataClassification = CustomerContent; - - fields - { - field(1; Id; BigInteger) - { - Caption = 'Id'; - DataClassification = SystemMetadata; - Editable = false; - } - - field(2; Namespace; Text[100]) - { - Caption = 'Namespace'; - DataClassification = SystemMetadata; - } - - field(3; "Owner Resource"; Text[50]) - { - Caption = 'Owner Resource'; - DataClassification = SystemMetadata; - - trigger OnValidate() - begin - case "Owner Resource" of - 'customer': - "Parent Table No." := Database::"Shpfy Customer"; - end; - end; - } - - field(4; "Owner Id"; BigInteger) - { - Caption = 'Owner Id'; - DataClassification = SystemMetadata; - } - - - field(5; Name; Text[30]) - { - Caption = 'Key'; - DataClassification = CustomerContent; - } - - field(6; "Value Type"; enum "Shpfy Metafield Value Type") - { - Caption = 'Value Type'; - DataClassification = CustomerContent; - } - - field(7; Value; Text[250]) - { - Caption = 'Value'; - DataClassification = CustomerContent; - } - - field(101; "Parent Table No."; Integer) - { - Caption = 'Parent Table No.'; - DataClassification = SystemMetadata; - Editable = false; - - trigger OnValidate() - begin - case "Parent Table No." of - Database::"Shpfy Customer": - "Owner Resource" := 'customer'; - end; - end; - } - } - - keys - { - key(PK; Id) - { - Clustered = true; - } - } - - - trigger OnInsert() - var - Metafield: Record "Shpfy Metafield"; - begin - if Namespace = '' then - Namespace := 'Microsoft.Dynamics365.BusinessCentral'; - if Id = 0 then - if Metafield.FindFirst() and (Metafield.Id < 0) then - Id := Metafield.Id - 1 - else - Id := -1; - end; -} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al b/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al index ec62eec90b..363fe23448 100644 --- a/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al @@ -64,7 +64,7 @@ codeunit 30116 "Shpfy Customer Export" Metafield.Namespace := 'Microsoft.Dynamics365.BusinessCentral'; Metafield.Validate("Parent Table No.", Database::"Shpfy Customer"); Metafield."Owner Id" := ShopifyCustomer.Id; - Metafield."Value Type" := Metafield."Value Type"::String; + Metafield.Type := Metafield.Type::string; Metafield.Value := Format(MetadataFieldRef.Value); end; end; diff --git a/Apps/W1/Shopify/app/src/Customers/Tables/ShpfyCustomer.Table.al b/Apps/W1/Shopify/app/src/Customers/Tables/ShpfyCustomer.Table.al index b8ad162bec..c194c2e1a0 100644 --- a/Apps/W1/Shopify/app/src/Customers/Tables/ShpfyCustomer.Table.al +++ b/Apps/W1/Shopify/app/src/Customers/Tables/ShpfyCustomer.Table.al @@ -148,6 +148,7 @@ table 30105 "Shpfy Customer" if not Tag.IsEmpty() then Tag.DeleteAll(); + Metafield.SetRange("Parent Table No.", Database::"Shpfy Customer"); Metafield.SetRange("Owner Id", Id); if not Metafield.IsEmpty then Metafield.DeleteAll(); diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLMetafieldsSet.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLMetafieldsSet.Codeunit.al new file mode 100644 index 0000000000..f04cfc1cab --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLMetafieldsSet.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL MetafieldSet (ID 30168) implements Interface Shpfy IGraphQL. +/// +codeunit 30350 "Shpfy GQL MetafieldsSet" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query": "mutation { metafieldsSet(metafields: [{{Metafields}}]) { metafields {legacyResourceId namespace key} userErrors {field, message}}}"}'); + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(10); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductById.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductById.Codeunit.al index 183fcc8147..a8a559b5c2 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductById.Codeunit.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductById.Codeunit.al @@ -13,7 +13,7 @@ codeunit 30146 "Shpfy GQL ProductById" implements "Shpfy IGraphQL" /// Return value of type Text. internal procedure GetGraphQL(): Text begin - exit('{"query":"{product(id: \"gid://shopify/Product/{{ProductId}}\") {createdAt updatedAt hasOnlyDefaultVariant description(truncateAt: {{MaxLengthDescription}}) descriptionHtml onlineStorePreviewUrl onlineStoreUrl productType status tags title vendor seo{description, title} images(first: 1) {edges{node{id}}} metafields(namespace: \"Microsoft.Dynamics365.BusinessCentral\", first: 10) {edges {node {id namespace type legacyResourceId key value}}}}}"}'); + exit('{"query":"{product(id: \"gid://shopify/Product/{{ProductId}}\") {createdAt updatedAt hasOnlyDefaultVariant description(truncateAt: {{MaxLengthDescription}}) descriptionHtml onlineStorePreviewUrl onlineStoreUrl productType status tags title vendor seo{description, title} images(first: 1) {edges{node{id}}} metafields(first: 50) {edges {node {id namespace type legacyResourceId key value}}}}}"}'); end; /// diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductMetafieldIds.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductMetafieldIds.Codeunit.al new file mode 100644 index 0000000000..f70ba9b255 --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLProductMetafieldIds.Codeunit.al @@ -0,0 +1,28 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL ProductMetafieldIds (ID 30332) implements Interface Shpfy IGraphQL. +/// +codeunit 30332 "Shpfy GQL ProductMetafieldIds" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query":"{product(id: \"gid://shopify/Product/{{ProductId}}\") { metafields(first: 50) {edges{node{legacyResourceId updatedAt}}}}}"}'); + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(50); + end; + +} diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al index 3973390897..8bb1d2c9c4 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al @@ -13,7 +13,7 @@ codeunit 30150 "Shpfy GQL VariantById" implements "Shpfy IGraphQL" /// Return value of type Text. internal procedure GetGraphQL(): Text begin - exit('{"query":"{productVariant(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {createdAt updatedAt availableForSale barcode compareAtPrice displayName inventoryPolicy position price sku taxCode taxable title weight product{id}selectedOptions{name value} inventoryItem{countryCodeOfOrigin createdAt id inventoryHistoryUrl legacyResourceId provinceCodeOfOrigin requiresShipping sku tracked updatedAt unitCost { amount currencyCode }} metafields(namespace: \"Microsoft.Dynamics365.BusinessCentral\", first: 10) {edges {node {id namespace ownerType legacyResourceId key value}}}}}"}'); + exit('{"query":"{productVariant(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {createdAt updatedAt availableForSale barcode compareAtPrice displayName inventoryPolicy position price sku taxCode taxable title weight product{id}selectedOptions{name value} inventoryItem{countryCodeOfOrigin createdAt id inventoryHistoryUrl legacyResourceId provinceCodeOfOrigin requiresShipping sku tracked updatedAt unitCost { amount currencyCode }} metafields(first: 50) {edges {node {id namespace ownerType legacyResourceId key value}}}}}"}'); end; /// diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantMetafieldIds.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantMetafieldIds.Codeunit.al new file mode 100644 index 0000000000..4bf3e281b7 --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLVariantMetafieldIds.Codeunit.al @@ -0,0 +1,28 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL VariantMetafieldIds (ID 30336) implements Interface Shpfy IGraphQL. +/// +codeunit 30336 "Shpfy GQL VariantMetafieldIds" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query":"{productVariant(id: \"gid://shopify/ProductVariant/{{VariantId}}\") { metafields(first: 50) {edges{ node{legacyResourceId updatedAt}}}}}"}'); + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(50); + end; + +} diff --git a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al index 3157f92a53..8c9626b419 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al @@ -405,6 +405,21 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL" Caption = 'Get Order Transactions'; Implementation = "Shpfy IGraphQL" = "Shpfy GQL OrderTransactions"; } + value(79; MetafieldSet) + { + Caption = 'MetfieldSet'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL MetafieldsSet"; + } + value(80; ProductMetafieldIds) + { + Caption = 'Product Metafield Ids'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL ProductMetafieldIds"; + } + value(81; VariantMetafieldIds) + { + Caption = 'Variant Metafield Ids'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL VariantMetafieldIds"; + } value(85; ProductVariantDelete) { Caption = 'Product Variant Delete'; diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeBoolean.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeBoolean.Codeunit.al new file mode 100644 index 0000000000..9b1b65d3a7 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeBoolean.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30338 "Shpfy Mtfld Type Boolean" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + DummyBoolean: Boolean; + begin + exit(Evaluate(DummyBoolean, Value, 9)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('true'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeCollectRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeCollectRef.Codeunit.al new file mode 100644 index 0000000000..25e78a3bdd --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeCollectRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30321 "Shpfy Mtfld Type Collect. Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/Collection\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/Collection/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeColor.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeColor.Codeunit.al new file mode 100644 index 0000000000..55759bda36 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeColor.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30319 "Shpfy Mtfld Type Color" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^#[0-9A-Fa-f]{6}$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('#fff123'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDate.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDate.Codeunit.al new file mode 100644 index 0000000000..96b530e90c --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDate.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30318 "Shpfy Mtfld Type Date" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + DummyDate: Date; + begin + exit(Evaluate(DummyDate, Value, 9)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('2022-02-02'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDateTime.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDateTime.Codeunit.al new file mode 100644 index 0000000000..e83ed841ed --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDateTime.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30315 "Shpfy Mtfld Type DateTime" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + DummyDateTime: DateTime; + begin + exit(Evaluate(DummyDateTime, Value, 9)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('2022-01-01T12:30:00'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDimension.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDimension.Codeunit.al new file mode 100644 index 0000000000..107cac0063 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeDimension.Codeunit.al @@ -0,0 +1,70 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30351 "Shpfy Mtfld Type Dimension" implements "Shpfy IMetafield Type" +{ + var + DimensionJsonTemplateTxt: Label '{"value": %1, "unit": "%2"}', Locked = true; + + procedure HasAssistEdit(): Boolean + begin + exit(true); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Dimension: Decimal; + Unit: Enum "Shpfy Metafield Dimension Type"; + begin + exit(TryExtractValues(Value, Dimension, Unit)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + var + MetafieldAssistEdit: Page "Shpfy Metafield Assist Edit"; + Dimension: Decimal; + Unit: Enum "Shpfy Metafield Dimension Type"; + begin + if Value <> '' then + if not TryExtractValues(Value, Dimension, Unit) then begin + Clear(Dimension); + Clear(Unit); + end; + + if MetafieldAssistEdit.OpenForDimension(Dimension, Unit) then begin + MetafieldAssistEdit.GetDimensionValue(Dimension, Unit); + Value := StrSubstNo(DimensionJsonTemplateTxt, Format(Dimension, 0, 9), GetDimensionTypeName(Unit)); + end else + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit(StrSubstNo(DimensionJsonTemplateTxt, '1.5', 'cm')); + end; + + [TryFunction] + local procedure TryExtractValues(Value: Text; var Dimension: Decimal; var Unit: Enum "Shpfy Metafield Dimension Type") + var + JToken: JsonToken; + JObject: JsonObject; + begin + JObject.ReadFrom(Value); + JObject.SelectToken('value', JToken); + Dimension := JToken.AsValue().AsDecimal(); + JObject.SelectToken('unit', JToken); + Unit := ConvertToDimensionType(JToken.AsValue().AsText()); + + if JObject.Keys.Count() <> 2 then + Error(''); + end; + + local procedure GetDimensionTypeName(DimensionType: Enum "Shpfy Metafield Dimension Type"): Text + begin + exit(DimensionType.Names().Get(DimensionType.Ordinals().IndexOf(DimensionType.AsInteger()))); + end; + + local procedure ConvertToDimensionType(Value: Text) Type: Enum "Shpfy Metafield Dimension Type" + begin + exit(Enum::"Shpfy Metafield Dimension Type".FromInteger(Type.Ordinals().Get(Type.Names().IndexOf(Value)))); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeFileRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeFileRef.Codeunit.al new file mode 100644 index 0000000000..9e5c357782 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeFileRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30322 "Shpfy Mtfld Type File Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/(GenericFile|MediaImage|Video)\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/MediaImage/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeInteger.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeInteger.Codeunit.al new file mode 100644 index 0000000000..28a7406b37 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeInteger.Codeunit.al @@ -0,0 +1,37 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30339 "Shpfy Mtfld Type Integer" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + DummyInteger: BigInteger; + MinInt: BigInteger; + MaxInt: BigInteger; + begin + if not Evaluate(DummyInteger, Value, 9) then + exit(false); + + Evaluate(MinInt, '-9007199254740991', 9); + Evaluate(MaxInt, '9007199254740991', 9); + if (DummyInteger < MinInt) or (DummyInteger > MaxInt) then + exit(false); + + exit(true); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('123'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeJson.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeJson.Codeunit.al new file mode 100644 index 0000000000..95d5732b99 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeJson.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30353 "Shpfy Mtfld Type Json" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + JsonObject: JsonObject; + begin + exit(JsonObject.ReadFrom(Value)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('{"ingredient": "flour", "amount": 0.3}'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMetaobjRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMetaobjRef.Codeunit.al new file mode 100644 index 0000000000..9053ac668d --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMetaobjRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30327 "Shpfy Mtfld Type Metaobj. Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/Metaobject\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/Metaobject/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMixedRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMixedRef.Codeunit.al new file mode 100644 index 0000000000..ca7dca2ed4 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMixedRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30331 "Shpfy Mtfld Type Mixed Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/[A-Za-z]+\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/Product/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMoney.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMoney.Codeunit.al new file mode 100644 index 0000000000..2acb6640d6 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMoney.Codeunit.al @@ -0,0 +1,74 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Finance.Currency; + +codeunit 30317 "Shpfy Mtfld Type Money" implements "Shpfy IMetafield Type" +{ + var + MoneyJsonTemplateTxt: Label '{"amount": "%1", "currency_code": "%2"}', Locked = true; + + procedure HasAssistEdit(): Boolean + begin + exit(true); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Amount: Decimal; + CurrencyCode: Code[10]; + begin + exit(TryExtractValues(Value, Amount, CurrencyCode)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + var + MetafieldAssistEdit: Page "Shpfy Metafield Assist Edit"; + Amount: Decimal; + CurrencyCode: Code[10]; + begin + if Value <> '' then + if not TryExtractValues(Value, Amount, CurrencyCode) then begin + Clear(Amount); + Clear(CurrencyCode); + end; + + if MetafieldAssistEdit.OpenForMoney(Amount, CurrencyCode) then begin + MetafieldAssistEdit.GetMoneyValue(Amount, CurrencyCode); + Value := StrSubstNo(MoneyJsonTemplateTxt, Format(Amount, 0, 9), CurrencyCode); + exit(true); + end else + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit(StrSubstNo(MoneyJsonTemplateTxt, '5.99', 'CAD')); + end; + + /// + /// Tried to extract the amount and currency code from the JSON string. + /// + /// JSON string with the following format: {"amount": "5.99", "currency_code": "CAD"} + /// Return value: the amount extracted from the JSON string. + /// Return value: the currency code extracted from the JSON string. + /// True if no errors occurred during the extraction. + [TryFunction] + internal procedure TryExtractValues(Value: Text; var Amount: Decimal; var CurrencyCode: Code[10]) + var + Currency: Record Currency; + JToken: JsonToken; + JObject: JsonObject; + begin + JObject.ReadFrom(Value); + JObject.SelectToken('amount', JToken); + Amount := JToken.AsValue().AsDecimal(); + JObject.SelectToken('currency_code', JToken); +#pragma warning disable AA0139 + CurrencyCode := JToken.AsValue().AsText(); +#pragma warning restore AA0139 + Currency.Get(CurrencyCode); + + if JObject.Keys.Count() <> 2 then + Error(''); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMultiText.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMultiText.Codeunit.al new file mode 100644 index 0000000000..5ddb65e64f --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeMultiText.Codeunit.al @@ -0,0 +1,30 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30352 "Shpfy Mtfld Type Multi Text" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(true); + end; + + procedure IsValidValue(Value: Text): Boolean + begin + exit(true); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + var + MetafieldAssistEdit: Page "Shpfy Metafield Assist Edit"; + begin + if MetafieldAssistEdit.OpenForMultiLineText(Value) then begin + MetafieldAssistEdit.GetMultiLineText(Value); + exit(true); + end else + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('Ingredients\Flour\Water\Milk\Eggs'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumDecimal.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumDecimal.Codeunit.al new file mode 100644 index 0000000000..7d6b11c8d6 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumDecimal.Codeunit.al @@ -0,0 +1,30 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30354 "Shpfy Mtfld Type Num Decimal" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + // +/-9999999999999.999999999 + exit(Regex.IsMatch(Value, '^[-+]?\d{1,13}(?:\.\d{1,9})?$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('123.45'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumInteger.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumInteger.Codeunit.al new file mode 100644 index 0000000000..dbce1c839b --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeNumInteger.Codeunit.al @@ -0,0 +1,37 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30320 "Shpfy Mtfld Type Num Integer" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + DummyInteger: BigInteger; + MinInt: BigInteger; + MaxInt: BigInteger; + begin + if not Evaluate(DummyInteger, Value, 9) then + exit(false); + + Evaluate(MinInt, '-9007199254740991', 9); + Evaluate(MaxInt, '9007199254740991', 9); + if (DummyInteger < MinInt) or (DummyInteger > MaxInt) then + exit(false); + + exit(true); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('123'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypePageRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypePageRef.Codeunit.al new file mode 100644 index 0000000000..81401c2ee1 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypePageRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30328 "Shpfy Mtfld Type Page Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/OnlinePage\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/OnlinePage/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeProductRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeProductRef.Codeunit.al new file mode 100644 index 0000000000..e9c765c5d6 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeProductRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30329 "Shpfy Mtfld Type Product Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/Product\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/Product/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeSingleText.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeSingleText.Codeunit.al new file mode 100644 index 0000000000..5a207003f6 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeSingleText.Codeunit.al @@ -0,0 +1,25 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30323 "Shpfy Mtfld Type Single Text" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + begin + exit(true); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('VIP shipping method'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeString.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeString.Codeunit.al new file mode 100644 index 0000000000..ddb25456f8 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeString.Codeunit.al @@ -0,0 +1,25 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30337 "Shpfy Mtfld Type String" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + begin + exit(true); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('Example'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeUrl.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeUrl.Codeunit.al new file mode 100644 index 0000000000..bbb87ff1fc --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeUrl.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30324 "Shpfy Mtfld Type Url" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^(http|https|mailto|sms|tel)://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('https://www.shopify.com'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVariantRef.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVariantRef.Codeunit.al new file mode 100644 index 0000000000..e3fe97876d --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVariantRef.Codeunit.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +using System.Utilities; + +codeunit 30330 "Shpfy Mtfld Type Variant Ref" implements "Shpfy IMetafield Type" +{ + procedure HasAssistEdit(): Boolean + begin + exit(false); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Regex: Codeunit Regex; + begin + exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/Variant\/\d+$')); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + begin + Value := Value; + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit('gid://shopify/Variant/1234567890'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVolume.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVolume.Codeunit.al new file mode 100644 index 0000000000..18faa414ef --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeVolume.Codeunit.al @@ -0,0 +1,72 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30325 "Shpfy Mtfld Type Volume" implements "Shpfy IMetafield Type" +{ + var + VolumeJsonTemplateTxt: Label '{"value": %1,"unit":"%2"}', Locked = true; + + procedure HasAssistEdit(): Boolean + begin + exit(true); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Volume: Decimal; + Unit: Enum "Shpfy Metafield Volume Type"; + begin + exit(TryExtractValues(Value, Volume, Unit)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + var + MetafieldAssistEdit: Page "Shpfy Metafield Assist Edit"; + Volume: Decimal; + Unit: Enum "Shpfy Metafield Volume Type"; + begin + if Value <> '' then + if not TryExtractValues(Value, Volume, Unit) then begin + Clear(Volume); + Clear(Unit); + end; + + if MetafieldAssistEdit.OpenForVolume(Volume, Unit) then begin + MetafieldAssistEdit.GetVolumeValue(Volume, Unit); + Value := StrSubstNo(VolumeJsonTemplateTxt, Format(Volume, 0, 9), GetVolumeTypeName(Unit)); + exit(true); + end else + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit(StrSubstNo(VolumeJsonTemplateTxt, '20.0', 'ml')); + end; + + + [TryFunction] + local procedure TryExtractValues(Value: Text; var Volume: Decimal; var Unit: Enum "Shpfy Metafield Volume Type") + var + JToken: JsonToken; + JObject: JsonObject; + begin + JObject.ReadFrom(Value); + JObject.SelectToken('value', JToken); + Volume := JToken.AsValue().AsDecimal(); + JObject.SelectToken('unit', JToken); + Unit := ConvertToVolumeType(JToken.AsValue().AsText()); + + if JObject.Keys.Count() <> 2 then + Error(''); + end; + + local procedure GetVolumeTypeName(VolumeType: Enum "Shpfy Metafield Volume Type"): Text + begin + exit(VolumeType.Names().Get(VolumeType.Ordinals().IndexOf(VolumeType.AsInteger()))); + end; + + local procedure ConvertToVolumeType(Value: Text) Type: Enum "Shpfy Metafield Volume Type" + begin + exit(Enum::"Shpfy Metafield Volume Type".FromInteger(Type.Ordinals().Get(Type.Names().IndexOf(Value)))); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeWeight.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeWeight.Codeunit.al new file mode 100644 index 0000000000..5bf2777206 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeWeight.Codeunit.al @@ -0,0 +1,72 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30326 "Shpfy Mtfld Type Weight" implements "Shpfy IMetafield Type" +{ + var + WeightJsonTemplateTxt: Label '{"value": %1,"unit":"%2"}', Locked = true; + + procedure HasAssistEdit(): Boolean + begin + exit(true); + end; + + procedure IsValidValue(Value: Text): Boolean + var + Weight: Decimal; + Unit: Enum "Shpfy Metafield Weight Type"; + begin + exit(TryExtractValues(Value, Weight, Unit)); + end; + + procedure AssistEdit(var Value: Text[2048]): Boolean + var + MetafieldAssistEdit: Page "Shpfy Metafield Assist Edit"; + Weight: Decimal; + Unit: Enum "Shpfy Metafield Weight Type"; + begin + if Value <> '' then + if not TryExtractValues(Value, Weight, Unit) then begin + Clear(Weight); + Clear(Unit); + end; + + if MetafieldAssistEdit.OpenForWeight(Weight, Unit) then begin + MetafieldAssistEdit.GetWeightValue(Weight, Unit); + Value := StrSubstNo(WeightJsonTemplateTxt, Format(Weight, 0, 9), GetWeightTypeName(Unit)); + exit(true); + end else + exit(false); + end; + + procedure GetExampleValue(): Text + begin + exit(StrSubstNo(WeightJsonTemplateTxt, '2.5', 'kg')); + end; + + + [TryFunction] + local procedure TryExtractValues(Value: Text; var Weight: Decimal; var Unit: Enum "Shpfy Metafield Weight Type") + var + JToken: JsonToken; + JObject: JsonObject; + begin + JObject.ReadFrom(Value); + JObject.SelectToken('value', JToken); + Weight := JToken.AsValue().AsDecimal(); + JObject.SelectToken('unit', JToken); + Unit := ConvertToWeightType(JToken.AsValue().AsText()); + + if JObject.Keys.Count() <> 2 then + Error(''); + end; + + local procedure GetWeightTypeName(WeightType: Enum "Shpfy Metafield Weight Type"): Text + begin + exit(WeightType.Names().Get(WeightType.Ordinals().IndexOf(WeightType.AsInteger()))); + end; + + local procedure ConvertToWeightType(Value: Text) Type: Enum "Shpfy Metafield Weight Type" + begin + exit(Enum::"Shpfy Metafield Weight Type".FromInteger(Type.Ordinals().Get(Type.Names().IndexOf(Value)))); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerCustomer.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerCustomer.Codeunit.al new file mode 100644 index 0000000000..98646f1731 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerCustomer.Codeunit.al @@ -0,0 +1,19 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30333 "Shpfy Metafield Owner Customer" implements "Shpfy IMetafield Owner Type" +{ + procedure GetTableId(): Integer + begin + exit(Database::"Shpfy Customer"); + end; + + procedure RetrieveMetafieldIdsFromShopify(OwnerId: BigInteger): Dictionary of [BigInteger, DateTime] + begin + Error('Not implemented'); + end; + + procedure GetShopCode(OwnerId: BigInteger): Code[20] + begin + exit('Not implemented'); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerProduct.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerProduct.Codeunit.al new file mode 100644 index 0000000000..3998b83f29 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerProduct.Codeunit.al @@ -0,0 +1,42 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30334 "Shpfy Metafield Owner Product" implements "Shpfy IMetafield Owner Type" +{ + procedure GetTableId(): Integer + begin + exit(Database::"Shpfy Product"); + end; + + procedure RetrieveMetafieldIdsFromShopify(OwnerId: BigInteger) MetafieldIds: Dictionary of [BigInteger, DateTime] + var + CommunicationMgt: Codeunit "Shpfy Communication Mgt."; + JsonHelper: Codeunit "Shpfy Json Helper"; + Parameters: Dictionary of [Text, Text]; + GraphQLType: Enum "Shpfy GraphQL Type"; + JResponse: JsonToken; + JMetafields: JsonArray; + JNode: JsonObject; + JItem: JsonToken; + Id: BigInteger; + UpdatedAt: DateTime; + begin + Parameters.Add('ProductId', Format(OwnerId)); + GraphQLType := GraphQLType::ProductMetafieldIds; + JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); + if JsonHelper.GetJsonArray(JResponse, JMetafields, 'data.product.metafields.edges') then + foreach JItem in JMetafields do + if JsonHelper.GetJsonObject(JItem.AsObject(), JNode, 'node') then begin + Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JNode, 'legacyResourceId')); + UpdatedAt := JsonHelper.GetValueAsDateTime(JNode, 'updatedAt'); + MetafieldIds.Add(Id, UpdatedAt); + end; + end; + + procedure GetShopCode(OwnerId: BigInteger): Code[20] + var + Product: Record "Shpfy Product"; + begin + Product.Get(OwnerId); + exit(Product."Shop Code"); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerVariant.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerVariant.Codeunit.al new file mode 100644 index 0000000000..3a9ff1e4b0 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/IOwnerType/ShpfyMetafieldOwnerVariant.Codeunit.al @@ -0,0 +1,42 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30335 "Shpfy Metafield Owner Variant" implements "Shpfy IMetafield Owner Type" +{ + procedure GetTableId(): Integer + begin + exit(Database::"Shpfy Variant"); + end; + + procedure RetrieveMetafieldIdsFromShopify(OwnerId: BigInteger) MetafieldIds: Dictionary of [BigInteger, DateTime] + var + CommunicationMgt: Codeunit "Shpfy Communication Mgt."; + JsonHelper: Codeunit "Shpfy Json Helper"; + Parameters: Dictionary of [Text, Text]; + GraphQLType: Enum "Shpfy GraphQL Type"; + JResponse: JsonToken; + JMetafields: JsonArray; + JNode: JsonObject; + JItem: JsonToken; + Id: BigInteger; + UpdatedAt: DateTime; + begin + Parameters.Add('VariantId', Format(OwnerId)); + GraphQLType := GraphQLType::VariantMetafieldIds; + JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); + if JsonHelper.GetJsonArray(JResponse, JMetafields, 'data.product.metafields.edges') then + foreach JItem in JMetafields do + if JsonHelper.GetJsonObject(JItem.AsObject(), JNode, 'node') then begin + Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JNode, 'legacyResourceId')); + UpdatedAt := JsonHelper.GetValueAsDateTime(JNode, 'updatedAt'); + MetafieldIds.Add(Id, UpdatedAt); + end; + end; + + procedure GetShopCode(OwnerId: BigInteger): Code[20] + var + Variant: Record "Shpfy Variant"; + begin + Variant.Get(OwnerId); + exit(Variant."Shop Code"); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al new file mode 100644 index 0000000000..bd3ba3ad8d --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al @@ -0,0 +1,237 @@ +namespace Microsoft.Integration.Shopify; + +codeunit 30316 "Shpfy Metafield API" +{ + Access = Internal; + + var + JsonHelper: Codeunit "Shpfy Json Helper"; + + #region To Shopify + /// + /// Creates or updates the metafields in Shopify. + /// + /// + /// Only metafields that have been updated in BC since last update in Shopify will be updated. + /// MetafieldSet mutation only accepts 25 metafields at a time, so the function will create multiple queries if needed. + /// + /// + /// + internal procedure CreateOrUpdateMetafieldsInShopify(ParentTableId: Integer; OwnerId: BigInteger) + var + TempMetafieldSet: Record "Shpfy Metafield" temporary; + MetafieldIds: Dictionary of [BigInteger, DateTime]; + Continue: Boolean; + Count: Integer; + GraphQuery: TextBuilder; + begin + MetafieldIds := RetrieveMetafieldsFromShopify(ParentTableId, OwnerId); + CollectMetafieldsInBC(ParentTableId, OwnerId, TempMetafieldSet, MetafieldIds); + + // MetafieldsSet mutation only accepts 25 metafields at a time + Continue := true; + if TempMetafieldSet.FindSet() then + while Continue do begin + Count := 0; + Continue := false; + GraphQuery.Clear(); + + repeat + if Count = GetMaxMetafieldsToUpdate() then begin + Continue := true; + Clear(Count); + break; + end; + + CreateMetafieldQuery(TempMetafieldSet, GraphQuery); + Count += 1; + until TempMetafieldSet.Next() = 0; + + UpdateMetafields(GraphQuery.ToText()); + end; + end; + + local procedure GetMaxMetafieldsToUpdate(): Integer + begin + exit(25); + end; + + local procedure RetrieveMetafieldsFromShopify(ParentTableId: Integer; OwnerId: BigInteger): Dictionary of [BigInteger, DateTime] + var + Metafield: Record "Shpfy Metafield"; + IMetafieldOwnerType: Interface "Shpfy IMetafield Owner Type"; + begin + IMetafieldOwnerType := Metafield.GetOwnerType(ParentTableId); + exit(IMetafieldOwnerType.RetrieveMetafieldIdsFromShopify(OwnerId)); + end; + + local procedure CollectMetafieldsInBC(ParentTableId: Integer; OwnerId: BigInteger; var TempMetafieldSet: Record "Shpfy Metafield" temporary; MetafieldIds: Dictionary of [BigInteger, DateTime]) + var + Metafield: Record "Shpfy Metafield"; + UpdatedAt: DateTime; + begin + Metafield.SetRange("Parent Table No.", ParentTableId); + Metafield.SetRange("Owner Id", OwnerId); + if Metafield.FindSet() then + repeat + if MetafieldIds.Get(Metafield.Id, UpdatedAt) then begin + if Metafield."Last Updated by BC" > UpdatedAt then begin + TempMetafieldSet := Metafield; + TempMetafieldSet.Insert(false); + end; + end else begin + TempMetafieldSet := Metafield; + TempMetafieldSet.Insert(false); + end; + until Metafield.Next() = 0; + end; + + /// + /// Updates the metafields in Shopify. + /// + /// GraphQL query for the metafields. + internal procedure UpdateMetafields(MetafieldsQuery: Text) JResponse: JsonToken + var + CommunicationMgt: Codeunit "Shpfy Communication Mgt."; + Parameters: Dictionary of [Text, Text]; + begin + Parameters.Add('Metafields', MetafieldsQuery); + JResponse := CommunicationMgt.ExecuteGraphQL(Enum::"Shpfy GraphQL Type"::MetafieldSet, Parameters); + end; + + /// + /// Creates a GraphQL query for a metafield. + /// + /// Metafield record to create the query for. + /// Return value: TextBuilder to append the query to. + internal procedure CreateMetafieldQuery(MetafieldSet: Record "Shpfy Metafield"; GraphQuery: TextBuilder) + begin + GraphQuery.Append('{'); + GraphQuery.Append('key: \"'); + GraphQuery.Append(MetafieldSet.Name); + GraphQuery.Append('\",'); + GraphQuery.Append('namespace: \"'); + GraphQuery.Append(MetafieldSet."Namespace"); + GraphQuery.Append('\",'); + GraphQuery.Append('ownerId: \"gid://shopify/'); + GraphQuery.Append(MetafieldSet.GetOwnerTypeName()); + GraphQuery.Append('/'); + GraphQuery.Append(Format(MetafieldSet."Owner Id")); + GraphQuery.Append('\",'); + GraphQuery.Append('value: \"'); + GraphQuery.Append(EscapeGrapQLData(MetafieldSet.Value)); + GraphQuery.Append('\",'); + GraphQuery.Append('type: \"'); + GraphQuery.Append(GetTypeName(MetafieldSet.Type)); + GraphQuery.Append('\"'); + GraphQuery.Append('},'); + end; + + local procedure EscapeGrapQLData(Data: Text): Text + begin + exit(Data.Replace('\', '\\\\').Replace('"', '\\\"')); + end; + + local procedure GetTypeName(Type: Enum "Shpfy Metafield Type"): Text + begin + exit(Enum::"Shpfy Metafield Type".Names().Get(Enum::"Shpfy Metafield Type".Ordinals().IndexOf(Type.AsInteger()))); + end; + #endregion + + #region From Shopify + /// + /// Updates the metafields in Business Central from Shopify. + /// + /// + /// Metafields with a value longer than 2048 characters will not be imported. + /// Some metafield types are unsupported in Business Central (i.e. Rating). + /// + /// JSON array of metafields from Shopify. + /// Table id of the parent resource. + /// Id of the parent resource. + internal procedure UpdateMetafieldsFromShopify(JMetafields: JsonArray; ParentTableNo: Integer; OwnerId: BigInteger) + var + JNode: JsonObject; + JItem: JsonToken; + MetafieldIds: List of [BigInteger]; + MetafieldId: BigInteger; + begin + CollectMetafieldIds(OwnerId, MetafieldIds); + + foreach JItem in JMetafields do begin + JsonHelper.GetJsonObject(JItem.AsObject(), JNode, 'node'); + MetafieldId := UpdateMetadataField(ParentTableNo, OwnerId, JNode); + MetafieldIds.Remove(MetafieldId); + end; + + DeleteUnusedMetafields(MetafieldIds); + end; + + local procedure UpdateMetadataField(ParentTableNo: Integer; OwnerId: BigInteger; JNode: JsonObject): BigInteger + var + Metafield: Record "Shpfy Metafield"; + ValueText: Text; + Type: Enum "Shpfy Metafield Type"; + begin + // Shopify has no limit on the length of the value, but Business Central has a limit of 2048 characters. + // If the value is longer than 2048 characters, Metafield is not imported. + ValueText := JsonHelper.GetValueAsText(JNode, 'value'); + if StrLen(ValueText) > MaxStrLen(Metafield.Value) then + exit(0); + + // Some metafield types are unsupported in Business Central (i.e. Rating) + if not ConvertToMetafieldType(JsonHelper.GetValueAsText(JNode, 'type'), Type) then + exit(0); + + + Metafield.Validate("Parent Table No.", ParentTableNo); + Metafield."Owner Id" := OwnerId; + Metafield.Id := JsonHelper.GetValueAsBigInteger(JNode, 'legacyResourceId'); + Metafield.Type := Type; +#pragma warning disable AA0139 + Metafield."Namespace" := JsonHelper.GetValueAsText(JNode, 'namespace'); + Metafield.Name := JsonHelper.GetValueAsText(JNode, 'key'); + Metafield.Value := ValueText; +#pragma warning restore AA0139 + if not Metafield.Modify(false) then + Metafield.Insert(false); + + exit(Metafield.Id); + end; + + local procedure ConvertToMetafieldType(Value: Text; var Type: Enum "Shpfy Metafield Type"): Boolean + var + EnumOrdinal: Integer; + begin + // Some metafield types are unsupported in Business Central (i.e. Rating) + if not Enum::"Shpfy Metafield Type".Ordinals().Get(Enum::"Shpfy Metafield Type".Names().IndexOf(Value), EnumOrdinal) then + exit(false); + + Type := Enum::"Shpfy Metafield Type".FromInteger(EnumOrdinal); + exit(true); + end; + + local procedure CollectMetafieldIds(ProductId: BigInteger; MetafieldIds: List of [BigInteger]) + var + Metafield: Record "Shpfy Metafield"; + begin + MetaField.SetRange("Parent Table No.", Database::"Shpfy Product"); + Metafield.SetRange("Owner Id", ProductId); + if Metafield.FindSet() then + repeat + MetafieldIds.Add(Metafield.Id); + until Metafield.Next() = 0; + end; + + local procedure DeleteUnusedMetafields(MetafieldIds: List of [BigInteger]) + var + Metafield: Record "Shpfy Metafield"; + MetafieldId: BigInteger; + begin + foreach MetafieldId in MetafieldIds do begin + Metafield.Get(MetafieldId); + Metafield.Delete(false); + end; + end; + #endregion +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldDimensionType.Enum.al b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldDimensionType.Enum.al new file mode 100644 index 0000000000..1c3c72cb46 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldDimensionType.Enum.al @@ -0,0 +1,35 @@ +namespace Microsoft.Integration.Shopify; + +enum 30160 "Shpfy Metafield Dimension Type" +{ + Access = Internal; + + value(0; in) + { + Caption = 'in'; + } + + value(1; ft) + { + Caption = 'ft'; + } + + value(2; yd) + { + Caption = 'yd'; + } + value(3; mm) + { + Caption = 'mm'; + } + + value(4; cm) + { + Caption = 'cm'; + } + + value(5; m) + { + Caption = 'm'; + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldOwnerType.Enum.al b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldOwnerType.Enum.al new file mode 100644 index 0000000000..71d42d21c1 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldOwnerType.Enum.al @@ -0,0 +1,24 @@ +namespace Microsoft.Integration.Shopify; + +enum 30156 "Shpfy Metafield Owner Type" implements "Shpfy IMetafield Owner Type" +{ + Access = Internal; + + value(0; Customer) + { + Caption = 'Customer'; + Implementation = "Shpfy IMetafield Owner Type" = "Shpfy Metafield Owner Customer"; + } + + value(1; Product) + { + Caption = 'Product'; + Implementation = "Shpfy IMetafield Owner Type" = "Shpfy Metafield Owner Product"; + } + + value(2; ProductVariant) + { + Caption = 'Variant'; + Implementation = "Shpfy IMetafield Owner Type" = "Shpfy Metafield Owner Variant"; + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldType.Enum.al b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldType.Enum.al new file mode 100644 index 0000000000..b4c21fb5b4 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldType.Enum.al @@ -0,0 +1,161 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Enum Shpfy Metafield Type (ID 30159). +/// +enum 30159 "Shpfy Metafield Type" implements "Shpfy IMetafield Type" +{ + Access = Internal; + Caption = 'Shopify Metafield Type'; + + Extensible = false; + + value(0; string) + { + Caption = 'String'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type String"; + } + + value(1; integer) + { + Caption = 'Integer'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Integer"; + } + + value(2; json) + { + Caption = 'JSON'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type JSON"; + } + + value(3; boolean) + { + Caption = 'True or false'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Boolean"; + } + + value(4; color) + { + Caption = 'Color'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Color"; + } + + value(5; date) + { + Caption = 'Date'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Date"; + } + + value(6; date_time) + { + Caption = 'Date and time'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type DateTime"; + } + + value(7; dimension) + { + Caption = 'Dimension'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Dimension"; + } + + value(8; money) + { + Caption = 'Money'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Money"; + } + + value(9; multi_line_text_field) + { + Caption = 'Multi-line text'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Multi Text"; + } + + value(10; number_decimal) + { + Caption = 'Decimal'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Num Decimal"; + } + + value(11; number_integer) + { + Caption = 'Integer'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Num Integer"; + } + + // Intentionally commented out as we are not supporting this type at the moment + // value(12; rating) + // { + // Caption = 'Rating'; + // } + + // Intentionally commented out as we are not supporting this type at the moment + // value(13; rich_text_field) + // { + // Caption = 'Rich text'; + // } + + value(14; single_line_text_field) + { + Caption = 'Single line text'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Single Text"; + } + + value(15; url) + { + Caption = 'URL'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type URL"; + } + + value(16; volume) + { + Caption = 'Volume'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Volume"; + } + + value(17; weight) + { + Caption = 'Weight'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Weight"; + } + value(18; collection_reference) + { + Caption = 'Collection reference'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Collect. Ref"; + } + + value(19; file_reference) + { + Caption = 'File'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type File Ref"; + } + + value(20; metaobject_reference) + { + Caption = 'Metaobject'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Metaobj. Ref"; + } + + value(21; mixed_reference) + { + Caption = 'Mixed reference'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Mixed Ref"; + } + + value(22; page_reference) + { + Caption = 'Page'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Page Ref"; + } + + value(23; product_reference) + { + Caption = 'Product'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Product Ref"; + } + + value(24; variant_reference) + { + Caption = 'Variant'; + Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Variant Ref"; + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Base/Enums/ShpfyMetafieldValueType.Enum.al b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldValueType.Enum.al similarity index 75% rename from Apps/W1/Shopify/app/src/Base/Enums/ShpfyMetafieldValueType.Enum.al rename to Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldValueType.Enum.al index dc3bb8acd4..fcf76e44aa 100644 --- a/Apps/W1/Shopify/app/src/Base/Enums/ShpfyMetafieldValueType.Enum.al +++ b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldValueType.Enum.al @@ -8,6 +8,9 @@ enum 30102 "Shpfy Metafield Value Type" Access = Internal; Caption = 'Shopify Metafield Value Type'; Extensible = false; + ObsoleteState = Pending; + ObsoleteReason = 'Value Type is obsolete in Shopify API. Use Metafield Type instead.'; + ObsoleteTag = '25.0'; value(0; String) { diff --git a/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldVolumeType.Enum.al b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldVolumeType.Enum.al new file mode 100644 index 0000000000..c1b87eb4ea --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldVolumeType.Enum.al @@ -0,0 +1,59 @@ +namespace Microsoft.Integration.Shopify; + +enum 30158 "Shpfy Metafield Volume Type" +{ + Access = Internal; + + value(0; ml) + { + Caption = 'ml'; + } + + value(1; cl) + { + Caption = 'cl'; + } + + value(2; l) + { + Caption = 'L'; + } + value(3; m3) + { + Caption = 'm3'; + } + + value(4; us_fl_oz) + { + Caption = 'fl oz'; + } + + value(5; us_pt) + { + Caption = 'pt'; + } + value(6; us_qt) + { + Caption = 'qt'; + } + value(7; us_gal) + { + Caption = 'gal'; + } + value(8; imp_fl_oz) + { + Caption = 'imp fl oz'; + } + value(9; imp_pt) + { + Caption = 'imp pt'; + } + value(10; imp_qt) + { + Caption = 'imp qt'; + } + value(11; imp_gal) + { + Caption = 'imp gal'; + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldWeightType.Enum.al b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldWeightType.Enum.al new file mode 100644 index 0000000000..2581e24b3a --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Enums/ShpfyMetafieldWeightType.Enum.al @@ -0,0 +1,26 @@ +namespace Microsoft.Integration.Shopify; + +enum 30157 "Shpfy Metafield Weight Type" +{ + Access = Internal; + + value(0; kg) + { + Caption = 'kg'; + } + + value(1; g) + { + Caption = 'g'; + } + + value(2; lb) + { + Caption = 'lb'; + } + + value(3; oz) + { + Caption = 'oz'; + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldOwnerType.Interface.al b/Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldOwnerType.Interface.al new file mode 100644 index 0000000000..f11f3540f7 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldOwnerType.Interface.al @@ -0,0 +1,29 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Interface used to for metafield operations related to metafield owner resource. +/// +interface "Shpfy IMetafield Owner Type" +{ + Access = Internal; + + /// + /// Returns the table id where the owner record is stored in BC. + /// + /// Table id. + procedure GetTableId(): Integer + + /// + /// Retrieves metafields belonging to the owner resource in a dictionary with the last updated at timestamp. + /// + /// Id of the owner resource. + /// Dictionary of metafield ids and last updated at timestamp. + procedure RetrieveMetafieldIdsFromShopify(OwnerId: BigInteger): Dictionary of [BigInteger, DateTime] + + /// + /// Retrieves the shop code from the owner resource. + /// + /// Id of the owner resource. + /// Shop code. + procedure GetShopCode(OwnerId: BigInteger): Code[20] +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldType.Interface.al b/Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldType.Interface.al new file mode 100644 index 0000000000..47dc65200d --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Interfaces/ShpfyIMetafieldType.Interface.al @@ -0,0 +1,35 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Interface used for validating and editing values of a Shopify Metafield. +/// +interface "Shpfy IMetafield Type" +{ + Access = Internal; + + /// + /// Determines if Type defines an Assist Edit dialog. + /// + /// True if Type defines an Assist Edit dialog, otherwise false. + procedure HasAssistEdit(): Boolean + + /// + /// Determines if provided value is valid for Type. + /// + /// Value to validate. + /// True if value is valid, otherwise False. + procedure IsValidValue(Value: Text): Boolean + + /// + /// Opens a dialog to assist in editing the value. + /// + /// Value to edit. Value may be modified. + /// True if value was edited, otherwise False. + procedure AssistEdit(var Value: Text[2048]): Boolean + + /// + /// Returns an example value for the Type. + /// + /// Example value. + procedure GetExampleValue(): Text +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafieldAssistEdit.Page.al b/Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafieldAssistEdit.Page.al new file mode 100644 index 0000000000..623fcd5ffc --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafieldAssistEdit.Page.al @@ -0,0 +1,264 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Finance.Currency; + +page 30164 "Shpfy Metafield Assist Edit" +{ + Caption = 'Metafield Assist Edit'; + PageType = StandardDialog; + ApplicationArea = All; + UsageCategory = Administration; + + layout + { + area(Content) + { + group(MoneyGroup) + { + Visible = IsMoneyVisible; + ShowCaption = false; + + field(MoneyValue; MoneyValue) + { + Caption = 'Value'; + ToolTip = 'Enter the amount.'; + } + field(MoneyCurrency; MoneyCurrency) + { + Caption = 'Currency'; + ToolTip = 'Enter the currency code.'; + TableRelation = Currency; + } + } + group(DimensionGroup) + { + Visible = IsDimensionVisible; + ShowCaption = false; + + field(DimensionValue; DimensionValue) + { + Caption = 'Value'; + ToolTip = 'Enter the value.'; + } + field(DimensionUnit; DimensionUnit) + { + Caption = 'Unit'; + ToolTip = 'Enter the unit of measure.'; + } + } + group(VolumeGroup) + { + Visible = IsVolumeVisible; + ShowCaption = false; + + field(VolumeValue; VolumeValue) + { + Caption = 'Value'; + ToolTip = 'Enter the value.'; + } + field(VolumeUnit; VolumeUnit) + { + Caption = 'Unit'; + ToolTip = 'Enter the unit of measure.'; + } + } + group(WeightGroup) + { + Visible = IsWeightVisible; + ShowCaption = false; + + field(WeightValue; WeightValue) + { + Caption = 'Value'; + ToolTip = 'Enter the value.'; + } + field(WeightUnit; WeightUnit) + { + Caption = 'Unit'; + ToolTip = 'Enter the unit of measure.'; + } + } + group(MultiLineTextGroup) + { + Visible = IsMultiLineTextVisible; + ShowCaption = false; + + field(MultiLineText; MultiLineText) + { + Caption = 'Text'; + ToolTip = 'Enter the text.'; + MultiLine = true; + ExtendedDatatype = RichContent; + + trigger OnValidate() + var + TextTooLongErr: Label 'The text is too long. The maximum length is 2048 characters.'; + begin + if StrLen(MultiLineText) > 2048 then + Error(ErrorInfo.Create(TextTooLongErr)); + end; + } + } + } + } + + #region Money + var + IsMoneyVisible: Boolean; + MoneyValue: Decimal; + MoneyCurrency: Code[10]; + + /// + /// Opens the page for assisting with input of money values. + /// + /// The amount to preset on the page. + /// The currency code to preset on the page. + /// True if the user clicks OK; otherwise, false. + internal procedure OpenForMoney(Amount: Decimal; CurrencyCode: Code[10]): Boolean + begin + IsMoneyVisible := true; + MoneyValue := Amount; + MoneyCurrency := CurrencyCode; + + exit(CurrPage.RunModal() = Action::OK); + end; + + /// + /// Gets the money value and currency code. + /// + /// Return value: The money value. + /// Return value: The currency code. + internal procedure GetMoneyValue(var Amount: Decimal; var Currency: Code[10]) + begin + Amount := MoneyValue; + Currency := MoneyCurrency; + end; + #endregion + + #region Dimension + var + IsDimensionVisible: Boolean; + DimensionValue: Decimal; + DimensionUnit: Enum "Shpfy Metafield Dimension Type"; + + /// + /// Opens the page for assisting with input of dimension values. + /// + /// The dimension to preset on the page. + /// The unit of measure to preset on the page. + /// True if the user clicks OK; otherwise, false. + internal procedure OpenForDimension(Dimension: Decimal; Unit: Enum "Shpfy Metafield Dimension Type"): Boolean + begin + IsDimensionVisible := true; + DimensionValue := Dimension; + DimensionUnit := Unit; + + exit(CurrPage.RunModal() = Action::OK); + end; + + /// + /// Gets the dimension value and unit of measure. + /// + /// Return value: The dimension value. + /// Return value: The unit of measure. + internal procedure GetDimensionValue(var Value: Decimal; var Unit: Enum "Shpfy Metafield Dimension Type") + begin + Value := DimensionValue; + Unit := DimensionUnit; + end; + #endregion + + #region Volume + var + IsVolumeVisible: Boolean; + VolumeValue: Decimal; + VolumeUnit: Enum "Shpfy Metafield Volume Type"; + + /// + /// Opens the page for assisting with input of volume values. + /// + /// The volume to preset on the page. + /// The unit of measure to preset on the page. + /// True if the user clicks OK; otherwise, false. + internal procedure OpenForVolume(Volume: Decimal; Unit: Enum "Shpfy Metafield Volume Type"): Boolean + begin + IsVolumeVisible := true; + VolumeValue := Volume; + VolumeUnit := Unit; + + exit(CurrPage.RunModal() = Action::OK); + end; + + /// + /// Gets the volume value and unit of measure. + /// + /// Return value: The volume value. + /// Return value: The unit of measure. + internal procedure GetVolumeValue(var Volume: Decimal; var Unit: Enum "Shpfy Metafield Volume Type") + begin + Volume := VolumeValue; + Unit := VolumeUnit; + end; + #endregion + + #region Weight + var + IsWeightVisible: Boolean; + WeightValue: Decimal; + WeightUnit: Enum "Shpfy Metafield Weight Type"; + + /// + /// Opens the page for assisting with input of weight values. + /// + /// The weight to preset on the page. + /// The unit of measure to preset on the page. + /// True if the user clicks OK; otherwise, false. + internal procedure OpenForWeight(Weight: Decimal; Unit: Enum "Shpfy Metafield Weight Type"): Boolean + begin + IsWeightVisible := true; + WeightValue := Weight; + WeightUnit := Unit; + + exit(CurrPage.RunModal() = Action::OK); + end; + + /// + /// Gets the weight value and unit of measure. + /// + /// Return value: The weight value. + /// Return value: The unit of measure. + internal procedure GetWeightValue(var Value: Decimal; var Unit: Enum "Shpfy Metafield Weight Type") + begin + Value := WeightValue; + Unit := WeightUnit; + end; + #endregion + + #region MultiLineText + var + IsMultiLineTextVisible: Boolean; + MultiLineText: Text; + + /// + /// Opens the page for assisting with input of multi-line text. + /// + /// The text to preset on the page. + /// True if the user clicks OK; otherwise, false. + internal procedure OpenForMultiLineText(Text: Text[2048]): Boolean + begin + IsMultiLineTextVisible := true; + MultiLineText := Text; + + exit(CurrPage.RunModal() = Action::OK); + end; + + /// + /// Gets the multi-line text. + /// + /// Return value: The multi-line text. + internal procedure GetMultiLineText(var Text: Text[2048]) + begin + Text := CopyStr(MultiLineText, 1, MaxStrLen(Text)); + end; + #endregion +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafields.Page.al b/Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafields.Page.al new file mode 100644 index 0000000000..2029380a48 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Pages/ShpfyMetafields.Page.al @@ -0,0 +1,130 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Page Shpfy Metafields (ID 30163). +/// +page 30163 "Shpfy Metafields" +{ + Caption = 'Shopify Metafields'; + Extensible = false; + PageType = List; + SourceTable = "Shpfy Metafield"; + UsageCategory = None; + ApplicationArea = All; + DelayedInsert = true; + + layout + { + area(Content) + { + repeater(Metafields) + { + Editable = IsPageEditable; + + field(Namespace; Rec.Namespace) + { + ToolTip = 'Specifies the namespace of the metafield.'; + } + field(Type; Rec.Type) + { + ToolTip = 'Specifies the type of value for the metafield.'; + } + field(Name; Rec.Name) + { + ToolTip = 'Specifies the key of the metafield.'; + } + field(Value; Rec.Value) + { + ToolTip = 'Specifies the value of the metafield.'; + Editable = IsValueEditable; + + trigger OnAssistEdit() + var + IMetafieldType: Interface "Shpfy IMetafield Type"; + begin + IMetafieldType := Rec.Type; + + if IMetafieldType.HasAssistEdit() then + if IMetafieldType.AssistEdit(Rec.Value) then + Rec.Validate(Value); + end; + } + } + } + } + + trigger OnNewRecord(BelowxRec: Boolean) + begin + Evaluate(Rec."Parent Table No.", Rec.GetFilter("Parent Table No.")); + Rec.Validate("Parent Table No."); + Evaluate(Rec."Owner Id", Rec.GetFilter("Owner Id")); + end; + + trigger OnAfterGetCurrRecord() + var + IMetafieldType: Interface "Shpfy IMetafield Type"; + begin + IMetafieldType := Rec.Type; + IsValueEditable := not IMetafieldType.HasAssistEdit(); + end; + + trigger OnInsertRecord(BelowxRec: Boolean): Boolean + begin + Rec.TestField(Namespace); + Rec.TestField(Name); + Rec.Validate(Value); + + Rec.Id := SendMetafieldToShopify(); + end; + + var + Shop: Record "Shpfy Shop"; + IsPageEditable: Boolean; + IsValueEditable: Boolean; + + /// + /// Opens the page displaying metafields for the specified resource. + /// + /// Table id of the resource. + /// System Id of the resource. + internal procedure RunForResource(ParentTableId: Integer; OwnerId: BigInteger; ShopCode: Code[20]) + var + Metafield: Record "Shpfy Metafield"; + begin + Shop.Get(ShopCode); + IsPageEditable := (Shop."Sync Item" = Shop."Sync Item"::"To Shopify") and (Shop."Can Update Shopify Products"); + + Metafield.SetRange("Parent Table No.", ParentTableId); + Metafield.SetRange("Owner Id", OwnerId); + + CurrPage.SetTableView(Metafield); + CurrPage.RunModal(); + end; + + local procedure SendMetafieldToShopify(): BigInteger + var + JsonHelper: Codeunit "Shpfy Json Helper"; + MetafieldAPI: Codeunit "Shpfy Metafield API"; + ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; + UserErrorOnShopifyErr: Label 'Something went wrong while sending the metafield to Shopify. Check Shopify Log Entries for more details.'; + GraphQuery: TextBuilder; + JResponse: JsonToken; + JMetafields: JsonArray; + JUserErrors: JsonArray; + JItem: JsonToken; + begin + ShpfyCommunicationMgt.SetShop(Shop); + + MetafieldAPI.CreateMetafieldQuery(Rec, GraphQuery); + JResponse := MetafieldAPI.UpdateMetafields(GraphQuery.ToText()); + + JsonHelper.GetJsonArray(JResponse, JUserErrors, 'data.metafieldsSet.userErrors'); + + if JUserErrors.Count() = 0 then begin + JsonHelper.GetJsonArray(JResponse, JMetafields, 'data.metafieldsSet.metafields'); + JMetafields.Get(0, JItem); + exit(JsonHelper.GetValueAsBigInteger(JItem, 'legacyResourceId')); + end else + Error(UserErrorOnShopifyErr); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Metafields/Tables/ShpfyMetafield.Table.al b/Apps/W1/Shopify/app/src/Metafields/Tables/ShpfyMetafield.Table.al new file mode 100644 index 0000000000..6e7955daa8 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Metafields/Tables/ShpfyMetafield.Table.al @@ -0,0 +1,219 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Finance.GeneralLedger.Setup; + +/// +/// Table Shpfy Metafield (ID 30101). +/// +table 30101 "Shpfy Metafield" +{ + Access = Internal; + Caption = 'Shopify Metafield'; + DataClassification = CustomerContent; + DrillDownPageId = "Shpfy Metafields"; + LookupPageId = "Shpfy Metafields"; + + fields + { + field(1; Id; BigInteger) + { + Caption = 'Id'; + DataClassification = SystemMetadata; + Editable = false; + } + +#pragma warning disable AS0086 // false positive on extending the field length on internal table + field(2; Namespace; Text[255]) + { + Caption = 'Namespace'; + DataClassification = SystemMetadata; + } +#pragma warning restore AS0086 + + field(3; "Owner Resource"; Text[50]) + { + Caption = 'Owner Resource'; + DataClassification = SystemMetadata; + ObsoleteState = Pending; + ObsoleteReason = 'Owner Resource is obsolete. Use Owner Type instead.'; + ObsoleteTag = '25.0'; + + trigger OnValidate() + begin + case "Owner Resource" of + 'Customer': + Validate("Owner Type", "Owner Type"::Customer); + 'Product': + Validate("Owner Type", "Owner Type"::Product); + 'Variant': + Validate("Owner Type", "Owner Type"::ProductVariant); + end; + end; + } + + field(4; "Owner Id"; BigInteger) + { + Caption = 'Owner Id'; + DataClassification = SystemMetadata; + } + +#pragma warning disable AS0086 // false positive on extending the field length on internal table + field(5; Name; Text[64]) + { + Caption = 'Key'; + DataClassification = CustomerContent; + } +#pragma warning restore AS0086 + + field(6; "Value Type"; Enum "Shpfy Metafield Value Type") + { + Caption = 'Value Type'; + DataClassification = CustomerContent; + ObsoleteState = Pending; + ObsoleteReason = 'Value Type is obsolete in Shopify API. Use Type instead.'; + ObsoleteTag = '25.0'; + } + +#pragma warning disable AS0086 // false positive on extending the field length on internal table + field(7; Value; Text[2048]) + { + Caption = 'Value'; + DataClassification = CustomerContent; + + trigger OnValidate() + var + ValueNotValidErr: Label 'The value is not valid for the type. Example value: '; + IMetafieldType: Interface "Shpfy IMetafield Type"; + begin + IMetafieldType := Rec.Type; + if not IMetafieldType.IsValidValue(Value) then + Error(ErrorInfo.Create(ValueNotValidErr + IMetafieldType.GetExampleValue())); + + if Rec.Type = Rec.Type::money then + CheckShopCurrency(Value); + end; + } +#pragma warning restore AS0086 + field(8; Type; Enum "Shpfy Metafield Type") + { + Caption = 'Type'; + DataClassification = CustomerContent; + } + field(9; "Last Updated by BC"; DateTime) + { + Caption = 'Last Updated by BC'; + DataClassification = SystemMetadata; + } + field(10; "Owner Type"; Enum "Shpfy Metafield Owner Type") + { + Caption = 'Owner Type'; + DataClassification = SystemMetadata; + + trigger OnValidate() + var + IMetafieldOwnerType: Interface "Shpfy IMetafield Owner Type"; + begin + IMetafieldOwnerType := Rec."Owner Type"; + "Parent Table No." := IMetafieldOwnerType.GetTableId(); + end; + } + + field(101; "Parent Table No."; Integer) + { + Caption = 'Parent Table No.'; + DataClassification = SystemMetadata; + Editable = false; + + trigger OnValidate() + begin + "Owner Type" := GetOwnerType("Parent Table No."); + end; + } + } + + keys + { + key(PK; Id) + { + Clustered = true; + } + key(Idx1; "Parent Table No.", "Owner Id") + { + } + } + + trigger OnInsert() + var + Metafield: Record "Shpfy Metafield"; + begin + if Namespace = '' then + Namespace := 'Microsoft.Dynamics365.BusinessCentral'; + if Id = 0 then + if Metafield.FindFirst() and (Metafield.Id < 0) then + Id := Metafield.Id - 1 + else + Id := -1; + end; + + trigger OnModify() + begin + "Last Updated by BC" := CurrentDateTime; + end; + + /// + /// Get the owner type based on the resources's owner table number. + /// + /// The owning resource table number. + internal procedure GetOwnerType(ParentTableNo: Integer): Enum "Shpfy Metafield Owner Type" + begin + case ParentTableNo of + Database::"Shpfy Customer": + exit("Owner Type"::Customer); + Database::"Shpfy Product": + exit("Owner Type"::Product); + Database::"Shpfy Variant": + exit("Owner Type"::ProductVariant); + end; + end; + + /// + /// Returns the name of the enum value for the owner type. Used when the full owner resource id needs to be built. + /// + /// The name of the owner type. + internal procedure GetOwnerTypeName(): Text + begin + exit("Owner Type".Names().Get("Owner Type".Ordinals().IndexOf("Owner Type".AsInteger()))); + end; + + local procedure CheckShopCurrency(MetafieldValue: Text[2048]) + var + ShpfyMtfldTypeMoney: Codeunit "Shpfy Mtfld Type Money"; + CurrencyCode: Code[10]; + ShopCurrencyCode: Code[10]; + Amount: Decimal; + CurrencyCodeMismatchErr: Label 'The currency code must match the shop currency code. Shop currency code: %1', Comment = '%1 - Shop currency code'; + begin + ShopCurrencyCode := GetShopCurrencyCode(); + + ShpfyMtfldTypeMoney.TryExtractValues(MetafieldValue, Amount, CurrencyCode); + if CurrencyCode <> ShopCurrencyCode then + Error(ErrorInfo.Create(StrSubstNo(CurrencyCodeMismatchErr, ShopCurrencyCode))); + end; + + local procedure GetShopCurrencyCode(): Code[10] + var + GeneralLedgerSetup: Record "General Ledger Setup"; + Shop: Record "Shpfy Shop"; + IMetafieldOwnerType: Interface "Shpfy IMetafield Owner Type"; + begin + IMetafieldOwnerType := Rec."Owner Type"; + Shop.Get(IMetafieldOwnerType.GetShopCode(Rec."Owner Id")); + + if Shop."Currency Code" <> '' then + exit(Shop."Currency Code") + else begin + GeneralLedgerSetup.Get(); + exit(GeneralLedgerSetup."LCY Code"); + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductAPI.Codeunit.al index 3878464a4b..2ae6043bf5 100644 --- a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductAPI.Codeunit.al @@ -564,7 +564,9 @@ codeunit 30176 "Shpfy Product API" /// Return variable "Result" of type Boolean. internal procedure UpdateShopifyProductFields(var ShopifyProduct: record "Shpfy Product"; JProduct: JsonObject) Result: Boolean var + MetafieldAPI: Codeunit "Shpfy Metafield API"; UpdatedAt: DateTime; + JMetafields: JsonArray; begin UpdatedAt := JsonHelper.GetValueAsDateTime(JProduct, 'updatedAt'); if UpdatedAt < ShopifyProduct."Updated At" then @@ -584,7 +586,7 @@ codeunit 30176 "Shpfy Product API" ShopifyProduct."Product Type" := JsonHelper.GetValueAsText(JProduct, 'productType', MaxStrLen(ShopifyProduct."Product Type")); #pragma warning restore AA0139 ShopifyProduct.UpdateTags(JsonHelper.GetArrayAsText(JProduct, 'tags')); -#pragma warning disable AA0139 +#pragma warning disable AA0139 ShopifyProduct.Title := JsonHelper.GetValueAsText(JProduct, 'title', MaxStrLen(ShopifyProduct.Title)); ShopifyProduct.Vendor := JsonHelper.GetValueAsText(JProduct, 'vendor', MaxStrLen(ShopifyProduct.Vendor)); ShopifyProduct."SEO Description" := JsonHelper.GetValueAsText(JProduct, 'seo.description', MaxStrLen(ShopifyProduct."SEO Description")); @@ -592,8 +594,10 @@ codeunit 30176 "Shpfy Product API" #pragma warning restore AA0139 ShopifyProduct.Status := ConvertToProductStatus(JsonHelper.GetValueAsText(JProduct, 'status')); ShopifyProduct.Modify(false); - end; + if JsonHelper.GetJsonArray(JProduct, JMetafields, 'metafields.edges') then + MetafieldAPI.UpdateMetafieldsFromShopify(JMetafields, Database::"Shpfy Product", ShopifyProduct.Id); + end; local procedure ConvertToProductStatus(Value: Text): Enum "Shpfy Product Status" begin diff --git a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al index 911006d55b..2ac5c9fb87 100644 --- a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al @@ -693,10 +693,27 @@ codeunit 30178 "Shpfy Product Export" end; until ItemUnitofMeasure.Next() = 0; end; - UpdateProductTranslations(ShopifyProduct.Id, Item) + + UpdateMetafields(ShopifyProduct.Id); + UpdateProductTranslations(ShopifyProduct.Id, Item); end; end; + local procedure UpdateMetafields(ProductId: BigInteger) + var + ShpfyVariant: Record "Shpfy Variant"; + MetafieldAPI: Codeunit "Shpfy Metafield API"; + begin + MetafieldAPI.CreateOrUpdateMetafieldsInShopify(Database::"Shpfy Product", ProductId); + + ShpfyVariant.SetRange("Product Id", ProductId); + ShpfyVariant.ReadIsolation := IsolationLevel::ReadCommitted; + if ShpfyVariant.FindSet() then + repeat + MetafieldAPI.CreateOrUpdateMetafieldsInShopify(Database::"Shpfy Variant", ShpfyVariant.Id); + until ShpfyVariant.Next() = 0; + end; + /// /// Updates a product variant in Shopify. Used when item variant does not exist in BC, but variants per UoM are maintained in Shopify. /// diff --git a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al index 340ed848a6..edc6d2ff57 100644 --- a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al @@ -500,6 +500,7 @@ codeunit 30189 "Shpfy Variant API" /// Return variable "Result" of type Boolean. internal procedure UpdateShopifyVariantFields(ShopifyProduct: Record "Shpfy Product"; var ShopifyVariant: Record "Shpfy Variant"; var ShopifyInventoryItem: Record "Shpfy Inventory Item"; JVariant: JsonObject) Result: Boolean var + MetafieldAPI: Codeunit "Shpfy Metafield API"; RecordRef: RecordRef; UpdatedAt: DateTime; JMetafields: JsonArray; @@ -595,7 +596,7 @@ codeunit 30189 "Shpfy Variant API" end; if JsonHelper.GetJsonObject(JVariant, JNode, 'metafields') then if JsonHelper.GetJsonArray(JNode, JMetafields, 'edges') then - foreach JItem in JMetafields do; + MetafieldAPI.UpdateMetafieldsFromShopify(JMetafields, Database::"Shpfy Variant", ShopifyVariant.Id); end; /// diff --git a/Apps/W1/Shopify/app/src/Products/Pages/ShpfyProducts.Page.al b/Apps/W1/Shopify/app/src/Products/Pages/ShpfyProducts.Page.al index 29ce2f2628..3635ac301a 100644 --- a/Apps/W1/Shopify/app/src/Products/Pages/ShpfyProducts.Page.al +++ b/Apps/W1/Shopify/app/src/Products/Pages/ShpfyProducts.Page.al @@ -315,6 +315,24 @@ page 30126 "Shpfy Products" Tags.RunModal(); end; } + action(Metafields) + { + ApplicationArea = All; + Caption = 'Metafields'; + Image = PriceAdjustment; + Promoted = true; + PromotedCategory = Process; + PromotedIsBig = true; + PromotedOnly = true; + ToolTip = 'Add metafields to a product. This can be used for adding custom data fields to products in Shopify.'; + + trigger OnAction() + var + Metafields: Page "Shpfy Metafields"; + begin + Metafields.RunForResource(Database::"Shpfy Product", Rec.Id, Rec."Shop Code"); + end; + } group(Sync) { action(SyncProducts) diff --git a/Apps/W1/Shopify/app/src/Products/Pages/ShpfyVariants.Page.al b/Apps/W1/Shopify/app/src/Products/Pages/ShpfyVariants.Page.al index 3291b4e345..39b3107e07 100644 --- a/Apps/W1/Shopify/app/src/Products/Pages/ShpfyVariants.Page.al +++ b/Apps/W1/Shopify/app/src/Products/Pages/ShpfyVariants.Page.al @@ -213,6 +213,20 @@ page 30127 "Shpfy Variants" end; end; } + action(Metafields) + { + ApplicationArea = All; + Caption = 'Metafields'; + Image = PriceAdjustment; + ToolTip = 'Add metafields to a variant. This can be used for adding custom data fields to variants in Shopify.'; + + trigger OnAction() + var + Metafields: Page "Shpfy Metafields"; + begin + Metafields.RunForResource(Database::"Shpfy Variant", Rec.Id, Rec."Shop Code"); + end; + } action(AddItemsAsVariants) { ApplicationArea = All; diff --git a/Apps/W1/Shopify/app/src/Products/Tables/ShpfyProduct.Table.al b/Apps/W1/Shopify/app/src/Products/Tables/ShpfyProduct.Table.al index 751923005f..aa699d3747 100644 --- a/Apps/W1/Shopify/app/src/Products/Tables/ShpfyProduct.Table.al +++ b/Apps/W1/Shopify/app/src/Products/Tables/ShpfyProduct.Table.al @@ -152,6 +152,7 @@ table 30127 "Shpfy Product" var Shop: Record "Shpfy Shop"; ShopifyVariant: Record "Shpfy Variant"; + Metafield: Record "Shpfy Metafield"; IRemoveProduct: Interface "Shpfy IRemoveProductAction"; begin if Shop.Get(Rec."Shop Code") then begin @@ -161,6 +162,11 @@ table 30127 "Shpfy Product" ShopifyVariant.SetRange("Product Id", Id); if not ShopifyVariant.IsEmpty then ShopifyVariant.DeleteAll(true); + + Metafield.SetRange("Parent Table No.", Database::"Shpfy Product"); + Metafield.SetRange("Owner Id", Id); + if not Metafield.IsEmpty then + Metafield.DeleteAll(); end; /// diff --git a/Apps/W1/Shopify/app/src/Products/Tables/ShpfyVariant.Table.al b/Apps/W1/Shopify/app/src/Products/Tables/ShpfyVariant.Table.al index 85032e64c5..500f760cbd 100644 --- a/Apps/W1/Shopify/app/src/Products/Tables/ShpfyVariant.Table.al +++ b/Apps/W1/Shopify/app/src/Products/Tables/ShpfyVariant.Table.al @@ -192,9 +192,15 @@ table 30129 "Shpfy Variant" trigger OnDelete() var InventoryItem: Record "Shpfy Inventory Item"; + Metafield: Record "Shpfy Metafield"; begin InventoryItem.SetRange("Variant Id", Id); if not InventoryItem.IsEmpty then InventoryItem.DeleteAll(); + + Metafield.SetRange("Parent Table No.", Database::"Shpfy Variant"); + Metafield.SetRange("Owner Id", Id); + if not Metafield.IsEmpty then + Metafield.DeleteAll(); end; }