Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Shopify] Sales Channels publishing for new products #27540

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Apps/W1/Shopify/app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"idRanges": [
{
"from": 30100,
"to": 30370
"to": 30380
}
],
"internalsVisibleTo": [
Expand Down
13 changes: 13 additions & 0 deletions Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,19 @@ page 30101 "Shpfy Shop Card"
RunPageLink = "Shop Code" = field(Code);
ToolTip = 'View a list of Shopify Languages for the shop.';
}
action(SalesChannels)
{
ApplicationArea = All;
Caption = 'Sales Channels';
Image = List;
Promoted = true;
PromotedCategory = Category4;
PromotedIsBig = true;
PromotedOnly = true;
RunObject = Page "Shpfy Sales Channels";
RunPageLink = "Shop Code" = field(Code);
ToolTip = 'View a list of Shopify Sales Channels for the shop and choose ones used for new product publishing.';
}
}
area(Processing)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Microsoft.Integration.Shopify;

/// <summary>
/// Codeunit Shpfy GQL SalesChannels (ID 30371) implements Interface Shpfy IGraphQL.
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
codeunit 30371 "Shpfy GQL SalesChannels" implements "Shpfy IGraphQL"
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
{
Access = Internal;

/// <summary>
/// GetGraphQL.
/// </summary>
/// <returns>Return value of type Text.</returns>
internal procedure GetGraphQL(): Text
begin
exit('{"query": "{publications(first: 10, catalogType: APP) { edges { node { id catalog { id ... on AppCatalog { apps(first: 1) { edges { node { id handle title } } } } } } } } }"}');
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
end;

/// <summary>
/// GetExpectedCost.
/// </summary>
/// <returns>Return value of type Integer.</returns>
internal procedure GetExpectedCost(): Integer
begin
exit(22);
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -490,4 +490,9 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL"
Caption = 'Get Product Image';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL GetProductImage";
}
value(102; GetSalesChannels)
{
Caption = 'Get Sales Channels';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL SalesChannels";
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ permissionset 30102 "Shpfy - Edit"
tabledata "Shpfy Payout" = IMD,
tabledata "Shpfy Product" = IMD,
tabledata "Shpfy Registered Store New" = imd,
tabledata "Shpfy Sales Channel" = IMD,
tabledata "Shpfy Shipment Method Mapping" = IMD,
tabledata "Shpfy Shop" = IMD,
tabledata "Shpfy Shop Collection Map" = IMD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ permissionset 30100 "Shpfy - Read"
tabledata "Shpfy Refund Line" = R,
tabledata "Shpfy Return Header" = R,
tabledata "Shpfy Return Line" = R,
tabledata "Shpfy Sales Channel" = R,
tabledata "Shpfy Shipment Method Mapping" = R,
tabledata "Shpfy Shop" = R,
tabledata "Shpfy Shop Collection Map" = R,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ codeunit 30176 "Shpfy Product API"
GraphQuery.Append(CommunicationMgt.EscapeGraphQLData(ShopifyProduct.Vendor));
GraphQuery.Append('\"');
end;
GraphQuery.Append(', published: true}) ');
GraphQuery.Append('}) ');
GraphQuery.Append('{product {legacyResourceId, onlineStoreUrl, onlineStorePreviewUrl, createdAt, updatedAt, tags, variants(first: 1) {edges {node {legacyResourceId, createdAt, updatedAt}}}}, userErrors {field, message}}');
GraphQuery.Append('}"}');

Expand Down Expand Up @@ -99,6 +99,8 @@ codeunit 30176 "Shpfy Product API"
VariantApi.AddProductVariant(ShopifyVariant);
end;

PublishProduct(NewShopifyProduct);

exit(NewShopifyProduct.Id);
end;

Expand Down Expand Up @@ -567,4 +569,65 @@ codeunit 30176 "Shpfy Product API"
foreach JOption in JOptions do
Options.Add(JsonHelper.GetValueAsText(JOption, 'id'), JsonHelper.GetValueAsText(JOption, 'name'));
end;

/// <summary>
/// Publish product to selected Shopify Sales Channels
/// </summary>
/// <param name="ShopifyProduct">Shopify product to be published</param>
internal procedure PublishProduct(ShopifyProduct: Record "Shpfy Product")
var
SalesChannel: Record "Shpfy Sales Channel";
GraphQuery: Text;
JResponse: JsonToken;
begin
if ShopifyProduct.Status <> Enum::"Shpfy Product Status"::Active then
exit;

if not GetSalesChannelsToPublishTo(SalesChannel, ShopifyProduct."Shop Code") then
exit;

GraphQuery := CreateProductPublishGraphQuery(ShopifyProduct, SalesChannel);

JResponse := CommunicationMgt.ExecuteGraphQL(GraphQuery);
end;

local procedure GetSalesChannelsToPublishTo(var SalesChannel: Record "Shpfy Sales Channel"; ShopCode: Code[20]): Boolean
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
var
SalesChannelAPI: Codeunit "Shpfy Sales Channel API";
begin
SalesChannel.SetRange("Shop Code", ShopCode);
if SalesChannel.IsEmpty() then
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
SalesChannelAPI.RetrieveSalesChannelsFromShopify(ShopCode);

SalesChannel.SetRange(SalesChannel."Use for publication", true);
if SalesChannel.IsEmpty() then begin
SalesChannel.SetRange("Use for publication");
SalesChannel.SetRange(SalesChannel.Default, true);
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
if SalesChannel.IsEmpty() then
exit(false);
end;

exit(true);
end;

local procedure CreateProductPublishGraphQuery(ShopifyProduct: Record "Shpfy Product"; var SalesChannel: Record "Shpfy Sales Channel"): Text
var
PublicationIds: TextBuilder;
PublicationIdTok: Label '{ publicationId: \"gid://shopify/Publication/%1\"},', Locked = true;
GraphQueryBuilder: TextBuilder;
begin
GraphQueryBuilder.Append('{"query":"mutation {publishablePublish(id: \"gid://shopify/Product/');
GraphQueryBuilder.Append(Format(ShopifyProduct.Id));
GraphQueryBuilder.Append('\" ');
GraphQueryBuilder.Append('input: [');
SalesChannel.FindSet();
repeat
PublicationIds.Append(StrSubstNo(PublicationIdTok, Format(SalesChannel.Id)));
until SalesChannel.Next() = 0;
GraphQueryBuilder.Append(PublicationIds.ToText().TrimEnd(','));
GraphQueryBuilder.Append('])');
GraphQueryBuilder.Append('{userErrors {field, message}}');
GraphQueryBuilder.Append('}"}');
exit(GraphQueryBuilder.ToText());
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
namespace Microsoft.Integration.Shopify;

/// <summary>
/// Codeunit Shpfy Sales Channel API (ID 30372).
/// </summary>
codeunit 30372 "Shpfy Sales Channel API"
{
Access = Internal;

var
JsonHelper: Codeunit "Shpfy Json Helper";
CommunicationMgt: Codeunit "Shpfy Communication Mgt.";

/// <summary>
/// Retrieves the sales channels from Shopify and updates the table with the new sales channels.
/// </summary>
/// <param name="ShopCode">The code of the shop.</param>
internal procedure RetrieveSalesChannelsFromShopify(ShopCode: Code[20])
var
GraphQLType: Enum "Shpfy GraphQL Type";
JResponse: JsonToken;
JPublications: JsonArray;
begin
CommunicationMgt.SetShop(ShopCode);
JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType::GetSalesChannels);
if JsonHelper.GetJsonArray(JResponse, JPublications, 'data.publications.edges') then
ProcessPublications(JPublications, ShopCode);
end;

/// <summary>
/// Processes the sales channels from Shopify.
/// </summary>
/// <param name="JPublications">The array of sales channels data from Shopify.</param>
/// <param name="ShopCode">The code of the shop.</param>
internal procedure ProcessPublications(JPublications: JsonArray; ShopCode: Code[20])
var
CurrentChannels: List of [BigInteger];
begin
CurrentChannels := CollectChannels(ShopCode);
InsertNewChannels(JPublications, ShopCode, CurrentChannels);
RemoveNotExistingChannels(CurrentChannels);
end;

local procedure CollectChannels(ShopCode: Code[20]): List of [BigInteger]
var
SalesChannel: Record "Shpfy Sales Channel";
Channels: List of [BigInteger];
begin
SalesChannel.SetRange("Shop Code", ShopCode);
if SalesChannel.FindSet() then
repeat
Channels.Add(SalesChannel.Id);
until SalesChannel.Next() = 0;
exit(Channels);
end;

local procedure RemoveNotExistingChannels(CurrentChannels: List of [BigInteger])
var
SalesChannel: Record "Shpfy Sales Channel";
ChannelId: BigInteger;
begin
foreach ChannelId in CurrentChannels do begin
SalesChannel.Get(ChannelId);
SalesChannel.Delete(true);
end;
end;

local procedure InsertNewChannels(JPublications: JsonArray; ShopCode: Code[20]; CurrentChannels: List of [BigInteger])
var
SalesChannel: Record "Shpfy Sales Channel";
JPublication: JsonToken;
ChannelId: BigInteger;
JCatalogEdges: JsonArray;
JCatalogEdge: JsonToken;
Handle: Text;
begin
foreach JPublication in JPublications do begin
ChannelId := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JPublication, '$.node.id'));
if not SalesChannel.Get(ChannelId) then begin
SalesChannel.Init();
SalesChannel.Validate(Id, ChannelId);
JCatalogEdges := JsonHelper.GetJsonArray(JPublication, '$.node.catalog.apps.edges');
JCatalogEdges.Get(0, JCatalogEdge);
SalesChannel.Validate(Name, JsonHelper.GetValueAsText(JCatalogEdge, '$.node.title'));
Handle := JsonHelper.GetValueAsText(JCatalogEdge, '$.node.handle');
if Handle = 'online_store' then
SalesChannel.Default := true;
SalesChannel."Shop Code" := ShopCode;
SalesChannel.Insert(true);
end else
CurrentChannels.Remove(ChannelId);
end;
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Microsoft.Integration.Shopify;

page 30167 "Shpfy Sales Channels"
{
ApplicationArea = All;
Caption = 'Shopify Sales Channels';
PageType = List;
SourceTable = "Shpfy Sales Channel";
InsertAllowed = false;
DeleteAllowed = false;
UsageCategory = None;


layout
{
area(Content)
{
repeater(General)
{
field(Id; Rec.Id) { }
field(Name; Rec.Name) { }
field("Use for publication"; Rec."Use for publication") { }
field(Default; Rec.Default) { }
}
}
}

actions
{
area(Processing)
{
action(GetSalesChannels)
{
ApplicationArea = All;
Caption = 'Get Sales Channels';
Promoted = true;
PromotedOnly = true;
PromotedCategory = Process;
Image = UpdateDescription;
ToolTip = 'Retrieves the sales channels from Shopify.';

trigger OnAction()
var
ShpfySalesChannelAPI: Codeunit "Shpfy Sales Channel API";
begin
ShpfySalesChannelAPI.RetrieveSalesChannelsFromShopify(CopyStr(Rec.GetFilter("Shop Code"), 1, 20));
end;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Microsoft.Integration.Shopify;

/// <summary>
/// Table Shpfy Sales Channel (ID 30159).
/// </summary>
table 30160 "Shpfy Sales Channel"
{
Caption = 'Shpfy Sales Channel';
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
DataClassification = CustomerContent;

fields
{
field(1; Id; BigInteger)
{
Caption = 'Id';
Editable = false;
ToolTip = 'The unique identifier of the sales channel.';
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
}
field(2; Name; Text[100])
{
Caption = 'Name';
Editable = false;
ToolTip = 'The name of the sales channel.';
}
field(3; "Shop Code"; Code[20])
{
Caption = 'Shop Code';
Editable = false;
ToolTip = 'The code of the shop.';
}
field(4; "Use for publication"; Boolean)
{
Caption = 'Use for publication';
ToolTip = 'Indicates if the sales channel is used for new products publication.';
}
field(5; Default; Boolean)
{
Caption = 'Default';
Editable = false;
ToolTip = 'Indicates if the sales channel is the default one. Used for new products publication if any other channel is chosen.';
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
}
}
keys
{
key(PK; Id)
{
Clustered = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <summary>
/// Codeunit Shpfy Sales Channel Helper (ID 139583).
/// </summary>
codeunit 139618 "Shpfy Sales Channel Helper"
{
internal procedure GetDefaultShopifySalesChannelResponse(OnlineStoreId: BigInteger; POSId: BigInteger): JsonArray
var
Any: Codeunit Any;
onbuyuka marked this conversation as resolved.
Show resolved Hide resolved
JPublications: JsonArray;
NodesTxt: Text;
ResponseTok: Label '[ { "node": { "id": "gid://shopify/Publication/%1", "catalog": {"apps": { "edges": [ { "node": { "handle": "online_store", "title": "Online Store" } } ] } } } }, { "node": { "id": "gid://shopify/Publication/%2", "catalog": { "apps": { "edges": [ { "node": {"handle": "pos", "title": "Point of Sale" } } ] } } } } ]', Locked = true;
begin
NodesTxt := StrSubstNo(ResponseTok, OnlineStoreId, POSId);
JPublications.ReadFrom(NodesTxt);
exit(JPublications);
end;
}
Loading
Loading