Skip to content

Commit

Permalink
[Shopify] Add items as product variants (#26712)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
4 people authored Aug 1, 2024
1 parent eb8dd44 commit fd23b55
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Microsoft.Integration.Shopify;

/// <summary>
/// Codeunit Shpfy GQL GetProductOptions (ID 30345) implements Interface Shpfy IGraphQL.
/// </summary>
codeunit 30345 "Shpfy GQL GetProductOptions" implements "Shpfy IGraphQL"
{
Access = Internal;

/// <summary>
/// GetGraphQL.
/// </summary>
/// <returns>Return value of type Text.</returns>
internal procedure GetGraphQL(): Text
begin
exit('{"query":"{product(id: \"gid://shopify/Product/{{ProductId}}\") {id title options {id name}}}"}');
end;

/// <summary>
/// GetExpectedCost.
/// </summary>
/// <returns>Return value of type Integer.</returns>
internal procedure GetExpectedCost(): Integer
begin
exit(2);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Microsoft.Integration.Shopify;

/// <summary>
/// Codeunit Shpfy GQL ProductVariantDelete (ID 30344) implements Interface Shpfy IGraphQL.
/// </summary>
codeunit 30344 "Shpfy GQL ProductVariantDelete" implements "Shpfy IGraphQL"
{
Access = Internal;

/// <summary>
/// GetGraphQL.
/// </summary>
/// <returns>Return value of type Text.</returns>
internal procedure GetGraphQL(): Text
begin
exit('{"query":"mutation {productVariantDelete(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {deletedProductVariantId userErrors{field message}}}"}');
end;

/// <summary>
/// GetExpectedCost.
/// </summary>
/// <returns>Return value of type Integer.</returns>
internal procedure GetExpectedCost(): Integer
begin
exit(10);
end;
}
10 changes: 10 additions & 0 deletions Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,16 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL"
Caption = 'Get Order Transactions';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL OrderTransactions";
}
value(85; ProductVariantDelete)
{
Caption = 'Product Variant Delete';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL ProductVariantDelete";
}
value(86; GetProductOptions)
{
Caption = 'Get Product Options';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL GetProductOptions";
}
value(87; GetReverseFulfillmentOrders)
{
Caption = 'Get Reverse Fulfillment Orders';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
namespace Microsoft.Integration.Shopify;

using Microsoft.Inventory.Item;

codeunit 30343 "Shpfy Create Item As Variant"
{
TableNo = Item;
Access = Internal;

var
Shop: Record "Shpfy Shop";
ShopifyProduct: Record "Shpfy Product";
CreateProduct: Codeunit "Shpfy Create Product";
VariantApi: Codeunit "Shpfy Variant API";
ProductApi: Codeunit "Shpfy Product API";
DefaultVariantId: BigInteger;

trigger OnRun()
begin
CreateVariantFromItem(Rec);
end;

/// <summary>
/// Creates a variant from a given item and adds it to the parent product.
/// </summary>
/// <param name="Item">The item to be added as a variant.</param>
internal procedure CreateVariantFromItem(var Item: Record "Item")
var
TempShopifyVariant: Record "Shpfy Variant" temporary;
begin
CreateProduct.CreateTempShopifyVariantFromItem(Item, TempShopifyVariant);
TempShopifyVariant."Product Id" := ShopifyProduct."Id";
TempShopifyVariant.Title := Item."No.";
TempShopifyVariant."Option 1 Name" := 'Variant';
TempShopifyVariant."Option 1 Value" := Item."No.";

if VariantApi.AddProductVariant(TempShopifyVariant) then begin
ShopifyProduct."Has Variants" := true;
ShopifyProduct.Modify(true);
end;
end;


/// <summary>
/// Checks if items can be added as variants to the product. The items cannot be added as variants if:
/// - The product has more than one option.
/// - The UoM as Variant setting is enabled.
/// </summary>
internal procedure CheckProductAndShopSettings()
var
MultipleOptionsErr: Label 'The product has more than one option. Items cannot be added as variants to a product with multiple options.';
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';
Options: Dictionary of [Text, Text];
begin
if Shop."UoM as Variant" then
Error(UOMAsVariantEnabledErr, Shop.FieldCaption("UoM as Variant"));

Options := ProductApi.GetProductOptions(ShopifyProduct.Id);

if Options.Count > 1 then
Error(MultipleOptionsErr);
end;

/// <summary>
/// Finds the default variant ID for the product if the product has no variants.
/// If new variants will be added, the default variant will be removed.
/// </summary>
internal procedure FindDefaultVariantId()
var
ProductVariantIds: Dictionary of [BigInteger, DateTime];
begin
if not ShopifyProduct."Has Variants" then begin
VariantApi.RetrieveShopifyProductVariantIds(ShopifyProduct, ProductVariantIds);
DefaultVariantId := ProductVariantIds.Keys.Get(1);
end;
end;

/// <summary>
/// Removes the default variant if new variants were added to the product.
/// </summary>
internal procedure RemoveDefaultVariant()
var
ShopifyVariant: Record "Shpfy Variant";
begin
if (DefaultVariantId <> 0) and ShopifyProduct."Has Variants" then begin
VariantApi.DeleteProductVariant(DefaultVariantId);
if ShopifyVariant.Get(DefaultVariantId) then
ShopifyVariant.Delete(true);
end;
end;

/// <summary>
/// Sets the parent product to which the variant will be added.
/// </summary>
/// <param name="ShopifyProductId">The parent Shopify product ID.</param>
internal procedure SetParentProduct(ShopifyProductId: BigInteger)
begin
ShopifyProduct.Get(ShopifyProductId);
SetShop(ShopifyProduct."Shop Code");
end;

local procedure SetShop(ShopCode: Code[20])
begin
Shop.Get(ShopCode);
VariantApi.SetShop(Shop);
ProductApi.SetShop(Shop);
CreateProduct.SetShop(Shop);
end;

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,7 @@ codeunit 30174 "Shpfy Create Product"
ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, ItemUnitofMeasure.Code, TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
TempShopifyVariant.Title := ItemVariant.Description;
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
case Shop."SKU Mapping" of
Shop."SKU Mapping"::"Bar Code":
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
Shop."SKU Mapping"::"Item No.":
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := ItemVariant.Code;
Shop."SKU Mapping"::"Item No. + Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
else
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Vendor Item No.":
TempShopifyVariant.SKU := Item."Vendor Item No.";
end;
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", ItemVariant.Code, Item."Vendor Item No.");
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
TempShopifyVariant.Taxable := true;
TempShopifyVariant.Weight := Item."Gross Weight";
Expand All @@ -125,22 +110,7 @@ codeunit 30174 "Shpfy Create Product"
ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, Item."Sales Unit of Measure", TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
TempShopifyVariant.Title := ItemVariant.Description;
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
case Shop."SKU Mapping" of
Shop."SKU Mapping"::"Bar Code":
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
Shop."SKU Mapping"::"Item No.":
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := ItemVariant.Code;
Shop."SKU Mapping"::"Item No. + Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
else
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Vendor Item No.":
TempShopifyVariant.SKU := CopyStr(GetVendorItemNo(Item."No.", ItemVariant.Code, Item."Sales Unit of Measure"), 1, MaxStrLen(TempShopifyVariant.SKU));
end;
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", ItemVariant.Code, GetVendorItemNo(Item."No.", ItemVariant.Code, Item."Sales Unit of Measure"));
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
TempShopifyVariant.Taxable := true;
TempShopifyVariant.Weight := Item."Gross Weight";
Expand All @@ -157,6 +127,7 @@ codeunit 30174 "Shpfy Create Product"
ItemUnitofMeasure.SetRange("Item No.", Item."No.");
if ItemUnitofMeasure.FindSet(false) then
repeat
TempShopifyProduct."Has Variants" := true;
Id += 1;
Clear(TempShopifyVariant);
TempShopifyVariant.Id := Id;
Expand All @@ -165,22 +136,7 @@ codeunit 30174 "Shpfy Create Product"
ProductPriceCalc.CalcPrice(Item, '', ItemUnitofMeasure.Code, TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
TempShopifyVariant.Title := Item.Description;
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
case Shop."SKU Mapping" of
Shop."SKU Mapping"::"Bar Code":
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
Shop."SKU Mapping"::"Item No.":
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := ItemVariant.Code;
SHop."SKU Mapping"::"Item No. + Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
else
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Vendor Item No.":
TempShopifyVariant.SKU := Item."Vendor Item No.";
end;
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", '', Item."Vendor Item No.");
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
TempShopifyVariant.Taxable := true;
TempShopifyVariant.Weight := Item."Gross Weight";
Expand All @@ -191,39 +147,11 @@ codeunit 30174 "Shpfy Create Product"
TempShopifyVariant."UoM Option Id" := 1;
TempShopifyVariant.Insert(false);
until ItemUnitofMeasure.Next() = 0;
end else begin
Clear(TempShopifyVariant);
TempShopifyVariant."Available For Sales" := true;
TempShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", '', Item."Sales Unit of Measure"), 1, MaxStrLen(TempShopifyVariant.Barcode));
ProductPriceCalc.CalcPrice(Item, '', Item."Sales Unit of Measure", TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
TempShopifyVariant.Title := ItemVariant.Description;
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
case Shop."SKU Mapping" of
Shop."SKU Mapping"::"Bar Code":
TempShopifyVariant.SKU := TempShopifyVariant.Barcode;
Shop."SKU Mapping"::"Item No.":
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := ItemVariant.Code;
SHop."SKU Mapping"::"Item No. + Variant Code":
if ItemVariant.Code <> '' then
TempShopifyVariant.SKU := Item."No." + Shop."SKU Field Separator" + ItemVariant.Code
else
TempShopifyVariant.SKU := Item."No.";
Shop."SKU Mapping"::"Vendor Item No.":
TempShopifyVariant.SKU := Item."Vendor Item No.";
end;
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
TempShopifyVariant.Taxable := true;
TempShopifyVariant.Weight := Item."Gross Weight";
TempShopifyVariant."Shop Code" := Shop.Code;
TempShopifyVariant."Item SystemId" := Item.SystemId;
TempShopifyVariant.Insert(false);
end;
end else
CreateTempShopifyVariantFromItem(Item, TempShopifyVariant);

TempShopifyProduct.Insert(false);
Events.OnAfterCreateTempShopifyProduct(Item, TempShopifyProduct, TempShopifyVariant, TempShopifyTag);

end;

/// <summary>
Expand All @@ -246,8 +174,8 @@ codeunit 30174 "Shpfy Create Product"
/// <param name="ItemNo">Parameter of type Code[20].</param>
/// <param name="VariantCode">Parameter of type Code[10].</param>
/// <param name="UoM">Parameter of type Code[10].</param>
/// <returns>Return value of type Text.</returns>
local procedure GetVendorItemNo(ItemNo: Code[20]; VariantCode: Code[10]; UoM: Code[10]): Text;
/// <returns>Return value of type Code[50].</returns>
local procedure GetVendorItemNo(ItemNo: Code[20]; VariantCode: Code[10]; UoM: Code[10]): Code[50];
var
Item: Record Item;
ItemReferenceMgt: Codeunit "Shpfy Item Reference Mgt.";
Expand All @@ -256,6 +184,48 @@ codeunit 30174 "Shpfy Create Product"
exit(ItemReferenceMgt.GetItemReference(ItemNo, VariantCode, UoM, "Item Reference Type"::Vendor, Item."Vendor No."));
end;

local procedure GetVariantSKU(BarCode: Text[50]; ItemNo: Text[20]; VariantCode: Text[10]; VendorItemNo: Text[50]): Text[50]
begin
case Shop."SKU Mapping" of
Shop."SKU Mapping"::"Bar Code":
exit(BarCode);
Shop."SKU Mapping"::"Item No.":
exit(ItemNo);
Shop."SKU Mapping"::"Variant Code":
if VariantCode <> '' then
exit(VariantCode);
Shop."SKU Mapping"::"Item No. + Variant Code":
if VariantCode <> '' then
exit(ItemNo + Shop."SKU Field Separator" + VariantCode)
else
exit(ItemNo);
Shop."SKU Mapping"::"Vendor Item No.":
exit(VendorItemNo);
end;
end;

/// <summary>
/// Creates a temporary Shopify variant with information from an item.
/// </summary>
/// <param name="Item">The item to create the variant from.</param>
/// <param name="TempShopifyVariant">The temporary Shopify variant record set where the variant will be inserted.</param>
internal procedure CreateTempShopifyVariantFromItem(Item: Record Item; var TempShopifyVariant: Record "Shpfy Variant" temporary)
begin
Clear(TempShopifyVariant);
TempShopifyVariant."Available For Sales" := true;
TempShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", '', Item."Sales Unit of Measure"), 1, MaxStrLen(TempShopifyVariant.Barcode));
ProductPriceCalc.CalcPrice(Item, '', Item."Sales Unit of Measure", TempShopifyVariant."Unit Cost", TempShopifyVariant.Price, TempShopifyVariant."Compare at Price");
TempShopifyVariant.Title := ''; // Title will be assigned to "Default Title" in Shopify as no Options are set.
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
TempShopifyVariant.SKU := GetVariantSKU(TempShopifyVariant.Barcode, Item."No.", '', Item."Vendor Item No.");
TempShopifyVariant."Tax Code" := Item."Tax Group Code";
TempShopifyVariant.Taxable := true;
TempShopifyVariant.Weight := Item."Gross Weight";
TempShopifyVariant."Shop Code" := Shop.Code;
TempShopifyVariant."Item SystemId" := Item.SystemId;
TempShopifyVariant.Insert(false);
end;

/// <summary>
/// Set Shop.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,4 +608,24 @@ codeunit 30176 "Shpfy Product API"
begin
exit(Value.Names.Get(Value.Ordinals.IndexOf(Value.AsInteger())).ToUpper());
end;

/// <summary>
/// Gets all the Product Options for a product.
/// </summary>
/// <param name="ShopifyProductId">Shopify product ID for which the options are to be retrieved.</param>
/// <returns>Dictionary of option IDs and option names.</returns>
internal procedure GetProductOptions(ShopifyProductId: BigInteger) Options: Dictionary of [text, Text]
var
Parameters: Dictionary of [Text, Text];
JResponse: JsonToken;
JOptions: JsonArray;
JOption: JsonToken;
begin
Parameters.Add('ProductId', Format(ShopifyProductId));
JResponse := CommunicationMgt.ExecuteGraphQL(Enum::"Shpfy GraphQL Type"::GetProductOptions, Parameters);

JsonHelper.GetJsonArray(JResponse, JOptions, 'data.product.options');
foreach JOption in JOptions do
Options.Add(JsonHelper.GetValueAsText(JOption, 'id'), JsonHelper.GetValueAsText(JOption, 'name'));
end;
}
Loading

0 comments on commit fd23b55

Please sign in to comment.