Skip to content

Commit fd23b55

Browse files
tinestaricTine StaricJesperSchulzonbuyuka
authored
[Shopify] Add items as product variants (#26712)
This pull request does not have a related issue as it's part of delivery for development agreed directly with @AndreiPanko Added an action to Variants subpage to add BC Items as additional Variants to an existing Shopify Product. If the parent product only had a default variant before the addition, it is deleted from both Shopify and BC. The variants are added under the existing the Product Option (ex. Color, Material, or Title, if product only had the default variant) Items cannot be added if the shop has "UOM as Variant" enabled, or if the parent product has more than one option defined. Fixes #26819 Fixes [AB#468218](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/468218) --------- Co-authored-by: Tine Staric <[email protected]> Co-authored-by: Jesper Schulz-Wedde <[email protected]> Co-authored-by: Onat Buyukakkus <[email protected]>
1 parent eb8dd44 commit fd23b55

9 files changed

+403
-86
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Microsoft.Integration.Shopify;
2+
3+
/// <summary>
4+
/// Codeunit Shpfy GQL GetProductOptions (ID 30345) implements Interface Shpfy IGraphQL.
5+
/// </summary>
6+
codeunit 30345 "Shpfy GQL GetProductOptions" implements "Shpfy IGraphQL"
7+
{
8+
Access = Internal;
9+
10+
/// <summary>
11+
/// GetGraphQL.
12+
/// </summary>
13+
/// <returns>Return value of type Text.</returns>
14+
internal procedure GetGraphQL(): Text
15+
begin
16+
exit('{"query":"{product(id: \"gid://shopify/Product/{{ProductId}}\") {id title options {id name}}}"}');
17+
end;
18+
19+
/// <summary>
20+
/// GetExpectedCost.
21+
/// </summary>
22+
/// <returns>Return value of type Integer.</returns>
23+
internal procedure GetExpectedCost(): Integer
24+
begin
25+
exit(2);
26+
end;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Microsoft.Integration.Shopify;
2+
3+
/// <summary>
4+
/// Codeunit Shpfy GQL ProductVariantDelete (ID 30344) implements Interface Shpfy IGraphQL.
5+
/// </summary>
6+
codeunit 30344 "Shpfy GQL ProductVariantDelete" implements "Shpfy IGraphQL"
7+
{
8+
Access = Internal;
9+
10+
/// <summary>
11+
/// GetGraphQL.
12+
/// </summary>
13+
/// <returns>Return value of type Text.</returns>
14+
internal procedure GetGraphQL(): Text
15+
begin
16+
exit('{"query":"mutation {productVariantDelete(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {deletedProductVariantId userErrors{field message}}}"}');
17+
end;
18+
19+
/// <summary>
20+
/// GetExpectedCost.
21+
/// </summary>
22+
/// <returns>Return value of type Integer.</returns>
23+
internal procedure GetExpectedCost(): Integer
24+
begin
25+
exit(10);
26+
end;
27+
}

Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al

+10
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,16 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL"
405405
Caption = 'Get Order Transactions';
406406
Implementation = "Shpfy IGraphQL" = "Shpfy GQL OrderTransactions";
407407
}
408+
value(85; ProductVariantDelete)
409+
{
410+
Caption = 'Product Variant Delete';
411+
Implementation = "Shpfy IGraphQL" = "Shpfy GQL ProductVariantDelete";
412+
}
413+
value(86; GetProductOptions)
414+
{
415+
Caption = 'Get Product Options';
416+
Implementation = "Shpfy IGraphQL" = "Shpfy GQL GetProductOptions";
417+
}
408418
value(87; GetReverseFulfillmentOrders)
409419
{
410420
Caption = 'Get Reverse Fulfillment Orders';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
namespace Microsoft.Integration.Shopify;
2+
3+
using Microsoft.Inventory.Item;
4+
5+
codeunit 30343 "Shpfy Create Item As Variant"
6+
{
7+
TableNo = Item;
8+
Access = Internal;
9+
10+
var
11+
Shop: Record "Shpfy Shop";
12+
ShopifyProduct: Record "Shpfy Product";
13+
CreateProduct: Codeunit "Shpfy Create Product";
14+
VariantApi: Codeunit "Shpfy Variant API";
15+
ProductApi: Codeunit "Shpfy Product API";
16+
DefaultVariantId: BigInteger;
17+
18+
trigger OnRun()
19+
begin
20+
CreateVariantFromItem(Rec);
21+
end;
22+
23+
/// <summary>
24+
/// Creates a variant from a given item and adds it to the parent product.
25+
/// </summary>
26+
/// <param name="Item">The item to be added as a variant.</param>
27+
internal procedure CreateVariantFromItem(var Item: Record "Item")
28+
var
29+
TempShopifyVariant: Record "Shpfy Variant" temporary;
30+
begin
31+
CreateProduct.CreateTempShopifyVariantFromItem(Item, TempShopifyVariant);
32+
TempShopifyVariant."Product Id" := ShopifyProduct."Id";
33+
TempShopifyVariant.Title := Item."No.";
34+
TempShopifyVariant."Option 1 Name" := 'Variant';
35+
TempShopifyVariant."Option 1 Value" := Item."No.";
36+
37+
if VariantApi.AddProductVariant(TempShopifyVariant) then begin
38+
ShopifyProduct."Has Variants" := true;
39+
ShopifyProduct.Modify(true);
40+
end;
41+
end;
42+
43+
44+
/// <summary>
45+
/// Checks if items can be added as variants to the product. The items cannot be added as variants if:
46+
/// - The product has more than one option.
47+
/// - The UoM as Variant setting is enabled.
48+
/// </summary>
49+
internal procedure CheckProductAndShopSettings()
50+
var
51+
MultipleOptionsErr: Label 'The product has more than one option. Items cannot be added as variants to a product with multiple options.';
52+
UOMAsVariantEnabledErr: Label 'Items cannot be added as variants to a product with the "%1" setting enabled for this store.', Comment = '%1 - UoM as Variant field caption';
53+
Options: Dictionary of [Text, Text];
54+
begin
55+
if Shop."UoM as Variant" then
56+
Error(UOMAsVariantEnabledErr, Shop.FieldCaption("UoM as Variant"));
57+
58+
Options := ProductApi.GetProductOptions(ShopifyProduct.Id);
59+
60+
if Options.Count > 1 then
61+
Error(MultipleOptionsErr);
62+
end;
63+
64+
/// <summary>
65+
/// Finds the default variant ID for the product if the product has no variants.
66+
/// If new variants will be added, the default variant will be removed.
67+
/// </summary>
68+
internal procedure FindDefaultVariantId()
69+
var
70+
ProductVariantIds: Dictionary of [BigInteger, DateTime];
71+
begin
72+
if not ShopifyProduct."Has Variants" then begin
73+
VariantApi.RetrieveShopifyProductVariantIds(ShopifyProduct, ProductVariantIds);
74+
DefaultVariantId := ProductVariantIds.Keys.Get(1);
75+
end;
76+
end;
77+
78+
/// <summary>
79+
/// Removes the default variant if new variants were added to the product.
80+
/// </summary>
81+
internal procedure RemoveDefaultVariant()
82+
var
83+
ShopifyVariant: Record "Shpfy Variant";
84+
begin
85+
if (DefaultVariantId <> 0) and ShopifyProduct."Has Variants" then begin
86+
VariantApi.DeleteProductVariant(DefaultVariantId);
87+
if ShopifyVariant.Get(DefaultVariantId) then
88+
ShopifyVariant.Delete(true);
89+
end;
90+
end;
91+
92+
/// <summary>
93+
/// Sets the parent product to which the variant will be added.
94+
/// </summary>
95+
/// <param name="ShopifyProductId">The parent Shopify product ID.</param>
96+
internal procedure SetParentProduct(ShopifyProductId: BigInteger)
97+
begin
98+
ShopifyProduct.Get(ShopifyProductId);
99+
SetShop(ShopifyProduct."Shop Code");
100+
end;
101+
102+
local procedure SetShop(ShopCode: Code[20])
103+
begin
104+
Shop.Get(ShopCode);
105+
VariantApi.SetShop(Shop);
106+
ProductApi.SetShop(Shop);
107+
CreateProduct.SetShop(Shop);
108+
end;
109+
110+
}

Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyCreateProduct.Codeunit.al

+51-81
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,7 @@ codeunit 30174 "Shpfy Create Product"
8787
ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, ItemUnitofMeasure.Code, TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
8888
TempShopifyVariant.Title := ItemVariant.Description;
8989
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
90-
case Shop."SKU Mapping" of
91-
Shop."SKU Mapping"::"Bar Code":
92-
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
93-
Shop."SKU Mapping"::"Item No.":
94-
TempShopifyVariant.SKU := Item."No.";
95-
Shop."SKU Mapping"::"Variant Code":
96-
if ItemVariant.Code <> '' then
97-
TempShopifyVariant.SKU := ItemVariant.Code;
98-
Shop."SKU Mapping"::"Item No. + Variant Code":
99-
if ItemVariant.Code <> '' then
100-
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
101-
else
102-
TempShopifyVariant.SKU := Item."No.";
103-
Shop."SKU Mapping"::"Vendor Item No.":
104-
TempShopifyVariant.SKU := Item."Vendor Item No.";
105-
end;
90+
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", ItemVariant.Code, Item."Vendor Item No.");
10691
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
10792
TempShopifyVariant.Taxable := true;
10893
TempShopifyVariant.Weight := Item."Gross Weight";
@@ -125,22 +110,7 @@ codeunit 30174 "Shpfy Create Product"
125110
ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, Item."Sales Unit of Measure", TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
126111
TempShopifyVariant.Title := ItemVariant.Description;
127112
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
128-
case Shop."SKU Mapping" of
129-
Shop."SKU Mapping"::"Bar Code":
130-
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
131-
Shop."SKU Mapping"::"Item No.":
132-
TempShopifyVariant.SKU := Item."No.";
133-
Shop."SKU Mapping"::"Variant Code":
134-
if ItemVariant.Code <> '' then
135-
TempShopifyVariant.SKU := ItemVariant.Code;
136-
Shop."SKU Mapping"::"Item No. + Variant Code":
137-
if ItemVariant.Code <> '' then
138-
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
139-
else
140-
TempShopifyVariant.SKU := Item."No.";
141-
Shop."SKU Mapping"::"Vendor Item No.":
142-
TempShopifyVariant.SKU := CopyStr(GetVendorItemNo(Item."No.", ItemVariant.Code, Item."Sales Unit of Measure"), 1, MaxStrLen(TempShopifyVariant.SKU));
143-
end;
113+
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", ItemVariant.Code, GetVendorItemNo(Item."No.", ItemVariant.Code, Item."Sales Unit of Measure"));
144114
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
145115
TempShopifyVariant.Taxable := true;
146116
TempShopifyVariant.Weight := Item."Gross Weight";
@@ -157,6 +127,7 @@ codeunit 30174 "Shpfy Create Product"
157127
ItemUnitofMeasure.SetRange("Item No.", Item."No.");
158128
if ItemUnitofMeasure.FindSet(false) then
159129
repeat
130+
TempShopifyProduct."Has Variants" := true;
160131
Id += 1;
161132
Clear(TempShopifyVariant);
162133
TempShopifyVariant.Id := Id;
@@ -165,22 +136,7 @@ codeunit 30174 "Shpfy Create Product"
165136
ProductPriceCalc.CalcPrice(Item, '', ItemUnitofMeasure.Code, TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
166137
TempShopifyVariant.Title := Item.Description;
167138
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
168-
case Shop."SKU Mapping" of
169-
Shop."SKU Mapping"::"Bar Code":
170-
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
171-
Shop."SKU Mapping"::"Item No.":
172-
TempShopifyVariant.SKU := Item."No.";
173-
Shop."SKU Mapping"::"Variant Code":
174-
if ItemVariant.Code <> '' then
175-
TempShopifyVariant.SKU := ItemVariant.Code;
176-
SHop."SKU Mapping"::"Item No. + Variant Code":
177-
if ItemVariant.Code <> '' then
178-
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
179-
else
180-
TempShopifyVariant.SKU := Item."No.";
181-
Shop."SKU Mapping"::"Vendor Item No.":
182-
TempShopifyVariant.SKU := Item."Vendor Item No.";
183-
end;
139+
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", '', Item."Vendor Item No.");
184140
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
185141
TempShopifyVariant.Taxable := true;
186142
TempShopifyVariant.Weight := Item."Gross Weight";
@@ -191,39 +147,11 @@ codeunit 30174 "Shpfy Create Product"
191147
TempShopifyVariant."UoM Option Id" := 1;
192148
TempShopifyVariant.Insert(false);
193149
until ItemUnitofMeasure.Next() = 0;
194-
end else begin
195-
Clear(TempShopifyVariant);
196-
TempShopifyVariant."Available For Sales" := true;
197-
TempShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", '', Item."Sales Unit of Measure"), 1, MaxStrLen(TempShopifyVariant.Barcode));
198-
ProductPriceCalc.CalcPrice(Item, '', Item."Sales Unit of Measure", TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
199-
TempShopifyVariant.Title := ItemVariant.Description;
200-
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
201-
case Shop."SKU Mapping" of
202-
Shop."SKU Mapping"::"Bar Code":
203-
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
204-
Shop."SKU Mapping"::"Item No.":
205-
TempShopifyVariant.SKU := Item."No.";
206-
Shop."SKU Mapping"::"Variant Code":
207-
if ItemVariant.Code <> '' then
208-
TempShopifyVariant.SKU := ItemVariant.Code;
209-
SHop."SKU Mapping"::"Item No. + Variant Code":
210-
if ItemVariant.Code <> '' then
211-
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
212-
else
213-
TempShopifyVariant.SKU := Item."No.";
214-
Shop."SKU Mapping"::"Vendor Item No.":
215-
TempShopifyVariant.SKU := Item."Vendor Item No.";
216-
end;
217-
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
218-
TempShopifyVariant.Taxable := true;
219-
TempShopifyVariant.Weight := Item."Gross Weight";
220-
TempShopifyVariant."Shop Code" := Shop.Code;
221-
TempShopifyVariant."Item SystemId" := Item.SystemId;
222-
TempShopifyVariant.Insert(false);
223-
end;
150+
end else
151+
CreateTempShopifyVariantFromItem(Item, TempShopifyVariant);
152+
224153
TempShopifyProduct.Insert(false);
225154
Events.OnAfterCreateTempShopifyProduct(Item, TempShopifyProduct, TempShopifyVariant, TempShopifyTag);
226-
227155
end;
228156

229157
/// <summary>
@@ -246,8 +174,8 @@ codeunit 30174 "Shpfy Create Product"
246174
/// <param name="ItemNo">Parameter of type Code[20].</param>
247175
/// <param name="VariantCode">Parameter of type Code[10].</param>
248176
/// <param name="UoM">Parameter of type Code[10].</param>
249-
/// <returns>Return value of type Text.</returns>
250-
local procedure GetVendorItemNo(ItemNo: Code[20]; VariantCode: Code[10]; UoM: Code[10]): Text;
177+
/// <returns>Return value of type Code[50].</returns>
178+
local procedure GetVendorItemNo(ItemNo: Code[20]; VariantCode: Code[10]; UoM: Code[10]): Code[50];
251179
var
252180
Item: Record Item;
253181
ItemReferenceMgt: Codeunit "Shpfy Item Reference Mgt.";
@@ -256,6 +184,48 @@ codeunit 30174 "Shpfy Create Product"
256184
exit(ItemReferenceMgt.GetItemReference(ItemNo, VariantCode, UoM, "Item Reference Type"::Vendor, Item."Vendor No."));
257185
end;
258186

187+
local procedure GetVariantSKU(BarCode: Text[50]; ItemNo: Text[20]; VariantCode: Text[10]; VendorItemNo: Text[50]): Text[50]
188+
begin
189+
case Shop."SKU Mapping" of
190+
Shop."SKU Mapping"::"Bar Code":
191+
exit(BarCode);
192+
Shop."SKU Mapping"::"Item No.":
193+
exit(ItemNo);
194+
Shop."SKU Mapping"::"Variant Code":
195+
if VariantCode <> '' then
196+
exit(VariantCode);
197+
Shop."SKU Mapping"::"Item No. + Variant Code":
198+
if VariantCode <> '' then
199+
exit(ItemNo + Shop."SKU Field Separator" + VariantCode)
200+
else
201+
exit(ItemNo);
202+
Shop."SKU Mapping"::"Vendor Item No.":
203+
exit(VendorItemNo);
204+
end;
205+
end;
206+
207+
/// <summary>
208+
/// Creates a temporary Shopify variant with information from an item.
209+
/// </summary>
210+
/// <param name="Item">The item to create the variant from.</param>
211+
/// <param name="TempShopifyVariant">The temporary Shopify variant record set where the variant will be inserted.</param>
212+
internal procedure CreateTempShopifyVariantFromItem(Item: Record Item; var TempShopifyVariant: Record "Shpfy Variant" temporary)
213+
begin
214+
Clear(TempShopifyVariant);
215+
TempShopifyVariant."Available For Sales" := true;
216+
TempShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", '', Item."Sales Unit of Measure"), 1, MaxStrLen(TempShopifyVariant.Barcode));
217+
ProductPriceCalc.CalcPrice(Item, '', Item."Sales Unit of Measure", TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
218+
TempShopifyVariant.Title := ''; // Title will be assigned to "Default Title" in Shopify as no Options are set.
219+
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
220+
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", '', Item."Vendor Item No.");
221+
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
222+
TempShopifyVariant.Taxable := true;
223+
TempShopifyVariant.Weight := Item."Gross Weight";
224+
TempShopifyVariant."Shop Code" := Shop.Code;
225+
TempShopifyVariant."Item SystemId" := Item.SystemId;
226+
TempShopifyVariant.Insert(false);
227+
end;
228+
259229
/// <summary>
260230
/// Set Shop.
261231
/// </summary>

Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductAPI.Codeunit.al

+20
Original file line numberDiff line numberDiff line change
@@ -608,4 +608,24 @@ codeunit 30176 "Shpfy Product API"
608608
begin
609609
exit(Value.Names.Get(Value.Ordinals.IndexOf(Value.AsInteger())).ToUpper());
610610
end;
611+
612+
/// <summary>
613+
/// Gets all the Product Options for a product.
614+
/// </summary>
615+
/// <param name="ShopifyProductId">Shopify product ID for which the options are to be retrieved.</param>
616+
/// <returns>Dictionary of option IDs and option names.</returns>
617+
internal procedure GetProductOptions(ShopifyProductId: BigInteger) Options: Dictionary of [text, Text]
618+
var
619+
Parameters: Dictionary of [Text, Text];
620+
JResponse: JsonToken;
621+
JOptions: JsonArray;
622+
JOption: JsonToken;
623+
begin
624+
Parameters.Add('ProductId', Format(ShopifyProductId));
625+
JResponse := CommunicationMgt.ExecuteGraphQL(Enum::"Shpfy GraphQL Type"::GetProductOptions, Parameters);
626+
627+
JsonHelper.GetJsonArray(JResponse, JOptions, 'data.product.options');
628+
foreach JOption in JOptions do
629+
Options.Add(JsonHelper.GetValueAsText(JOption, 'id'), JsonHelper.GetValueAsText(JOption, 'name'));
630+
end;
611631
}

0 commit comments

Comments
 (0)