Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# cost: 13
{"query":"{productVariant(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {createdAt updatedAt availableForSale barcode compareAtPrice displayName inventoryPolicy position price sku taxable title product{id}selectedOptions{name value} inventoryItem{countryCodeOfOrigin createdAt id inventoryHistoryUrl legacyResourceId measurement { weight { value }} provinceCodeOfOrigin requiresShipping sku tracked updatedAt unitCost { amount currencyCode }} metafields(first: 50) {edges {node {id namespace type legacyResourceId key value}}}}}"}
{"query":"{productVariant(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {createdAt updatedAt availableForSale barcode compareAtPrice displayName inventoryPolicy position price sku taxable title product{id}selectedOptions{name value} inventoryItem{countryCodeOfOrigin harmonizedSystemCode createdAt id inventoryHistoryUrl legacyResourceId measurement { weight { value }} provinceCodeOfOrigin requiresShipping sku tracked updatedAt unitCost { amount currencyCode }} metafields(first: 50) {edges {node {id namespace type legacyResourceId key value}}}}}"}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ codeunit 30104 "Shpfy Filter Mgt."
begin
exit(CleanFilterValue(CopyStr(Value, 1, MaxLength)));
end;

internal procedure KeepDigits(Value: Text): Text;
begin
exit(DelChr(Value, '=', DelChr(Value, '=', '0123456789')));
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Integration.Shopify;

using Microsoft.Finance.Currency;
using Microsoft.Foundation.UOM;
using Microsoft.Inventory.Intrastat;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Item.Catalog;
using Microsoft.Purchases.Vendor;
Expand All @@ -25,6 +26,7 @@ codeunit 30171 "Shpfy Create Item"
tabledata "Item Unit of Measure" = rim,
tabledata "Item Variant" = rim,
tabledata "Item Vendor" = rim,
tabledata "Tariff Number" = r,
tabledata "Unit of Measure" = rim,
tabledata Vendor = rim;
TableNo = "Shpfy Variant";
Expand Down Expand Up @@ -233,6 +235,7 @@ codeunit 30171 "Shpfy Create Item"
ItemVariant: Record "Item Variant";
Vendor: Record Vendor;
CurrencyExchangeRate: Record "Currency Exchange Rate";
ProcessOrder: Codeunit "Shpfy Process Order";
CurrentTemplateCode: Code[20];
ItemNo: Code[20];
Code: Text;
Expand Down Expand Up @@ -272,6 +275,11 @@ codeunit 30171 "Shpfy Create Item"
else
Item.Validate("Unit Price", Round(CurrencyExchangeRate.ExchangeAmtFCYToLCY(WorkDate(), Shop."Currency Code", ShopifyVariant.Price, CurrencyExchangeRate.ExchangeRate(WorkDate(), Shop."Currency Code"))));

if Shop."Sync HS Code and Country" then begin
Item.Validate("Tariff No.", GetTariffNo(ShopifyVariant."Tariff No."));
Item.Validate("Country/Region of Origin Code", ProcessOrder.GetCountryCode(ShopifyVariant."Country/Region of Origin Code"));
end;

if ShopifyProduct."Product Type" <> '' then begin
ItemCategory.SetFilter(Description, FilterMgt.CleanFilterValue(ShopifyProduct."Product Type", MaxStrLen(ItemCategory.Description)));
if ItemCategory.FindFirst() then
Expand Down Expand Up @@ -474,6 +482,37 @@ codeunit 30171 "Shpfy Create Item"
end;
end;

/// <summary>
/// Get Tariff No.
/// Returns the tariff number only when it already exists in Business Central, so importing a
/// product from Shopify does not create new Tariff Number records. Shopify returns the harmonized
/// system code without separators (e.g. "610443" instead of "6104.43"), so when there is no exact
/// match the lookup falls back to comparing the digits of the existing BC Tariff Numbers.
/// </summary>
/// <param name="TariffNo">Parameter of type Code[20]: the harmonized system code received from Shopify.</param>
/// <returns>Return value of type Code[20]: the matching Tariff Number, or empty if it does not exist.</returns>
internal procedure GetTariffNo(TariffNo: Code[20]): Code[20]
var
TariffNumber: Record "Tariff Number";
Digits: Text;
begin
if TariffNo = '' then
exit('');
if TariffNumber.Get(TariffNo) then
exit(TariffNo);

Digits := FilterMgt.KeepDigits(TariffNo);
if Digits = '' then
exit('');
TariffNumber.SetLoadFields("No.");
if TariffNumber.FindSet() then
repeat
if FilterMgt.KeepDigits(TariffNumber."No.") = Digits then
exit(TariffNumber."No.");
until TariffNumber.Next() = 0;
exit('');
end;

/// <summary>
/// Create Items from Shopify Products.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ codeunit 30188 "Shpfy Update Item"
ProductEvents.OnBeforeUpdateItem(Shop, ShopifyProduct, Rec, Item, IsHandled);

if not IsHandled then
if DoUpdateItem(ShopifyProduct, Item) then
if DoUpdateItem(ShopifyProduct, Rec, Item) then
ProductEvents.OnAfterUpdateItem(Shop, ShopifyProduct, Rec, Item);

IsHandled := false;
Expand All @@ -63,14 +63,19 @@ codeunit 30188 "Shpfy Update Item"
/// Do Update Item.
/// </summary>
/// <param name="ShopifyProduct">Parameter of type Record "Shopify Product".</param>
/// <param name="ShopifyVariant">Parameter of type Record "Shopify Variant".</param>
/// <param name="Item">Parameter of type Record Item.</param>
/// <returns>Return value of type Boolean.</returns>
local procedure DoUpdateItem(var ShopifyProduct: Record "Shpfy Product"; var Item: Record Item): Boolean
local procedure DoUpdateItem(var ShopifyProduct: Record "Shpfy Product"; var ShopifyVariant: Record "Shpfy Variant"; var Item: Record Item): Boolean
var
ItemCategory: Record "Item Category";
Vendor: Record Vendor;
CreateItem: Codeunit "Shpfy Create Item";
ProcessOrder: Codeunit "Shpfy Process Order";
IsModified: Boolean;
IsModifiedByEvent: Boolean;
TariffNo: Code[20];
CountryRegionCode: Code[10];
begin
if Item.Description <> ShopifyProduct.Title then begin
IsModified := true;
Expand All @@ -92,6 +97,20 @@ codeunit 30188 "Shpfy Update Item"
Item."Vendor No." := Vendor."No.";
end;
end;
if Shop."Sync HS Code and Country" then begin
// Only apply a value that resolves to an existing BC Tariff Number / Country/Region, so an
// unmapped or blank Shopify value never wipes an existing value on the Business Central item.
TariffNo := CreateItem.GetTariffNo(ShopifyVariant."Tariff No.");
if (TariffNo <> '') and (Item."Tariff No." <> TariffNo) then begin
Item.Validate("Tariff No.", TariffNo);
IsModified := true;
end;
CountryRegionCode := ProcessOrder.GetCountryCode(ShopifyVariant."Country/Region of Origin Code");
if (CountryRegionCode <> '') and (Item."Country/Region of Origin Code" <> CountryRegionCode) then begin
Item.Validate("Country/Region of Origin Code", CountryRegionCode);
IsModified := true;
end;
end;
ProductEvents.OnDoUpdateItemBeforeModify(Shop, ShopifyProduct, Item, IsModifiedByEvent);
if IsModified or IsModifiedByEvent then
exit(Item.Modify());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ codeunit 30189 "Shpfy Variant API"
var
Shop: Record "Shpfy Shop";
CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
FilterMgt: Codeunit "Shpfy Filter Mgt.";
JsonHelper: Codeunit "Shpfy Json Helper";
ProductEvents: Codeunit "Shpfy Product Events";
MetafieldAPI: Codeunit "Shpfy Metafield API";
Expand Down Expand Up @@ -189,7 +190,7 @@ codeunit 30189 "Shpfy Variant API"
HasChange := true;
GraphQuery.Append(', compareAtPrice: null');
end;
if (ShopifyVariant."Unit Cost" <> xShopifyVariant."Unit Cost") or (ShopifyVariant.Weight <> xShopifyVariant.Weight) or (ShopifyVariant.SKU <> xShopifyVariant.SKU) or (ShopifyVariant."Tariff No." <> xShopifyVariant."Tariff No.") or (ShopifyVariant."Country/Region of Origin Code" <> xShopifyVariant."Country/Region of Origin Code") then begin
if (ShopifyVariant."Unit Cost" <> xShopifyVariant."Unit Cost") or (ShopifyVariant.Weight <> xShopifyVariant.Weight) or (ShopifyVariant.SKU <> xShopifyVariant.SKU) or HasTariffNoChanged(ShopifyVariant."Tariff No.", xShopifyVariant."Tariff No.") or (ShopifyVariant."Country/Region of Origin Code" <> xShopifyVariant."Country/Region of Origin Code") then begin
HasChange := true;
GraphQuery.Append(', inventoryItem: {tracked: ');
if Shop."Inventory Tracked" then
Expand All @@ -206,7 +207,7 @@ codeunit 30189 "Shpfy Variant API"
GraphQuery.Append(ShopifyVariant.SKU);
GraphQuery.Append('\"');
end;
if ShopifyVariant."Tariff No." <> xShopifyVariant."Tariff No." then begin
if HasTariffNoChanged(ShopifyVariant."Tariff No.", xShopifyVariant."Tariff No.") then begin
HasChange := true;
GraphQuery.Append(', harmonizedSystemCode: \"');
GraphQuery.Append(CommunicationMgt.EscapeGraphQLData(ShopifyVariant."Tariff No."));
Expand Down Expand Up @@ -237,6 +238,14 @@ codeunit 30189 "Shpfy Variant API"
exit(GraphQuery);
end;

local procedure HasTariffNoChanged(NewTariffNo: Code[20]; OldTariffNo: Code[20]): Boolean
begin
// Shopify stores the harmonized system code without separators, so a Business Central Tariff No.
// such as "6104.43" and the value read back from Shopify ("610443") represent the same code.
// Only treat the tariff as changed when the digits differ, to avoid redundant updates.
exit(FilterMgt.KeepDigits(NewTariffNo) <> FilterMgt.KeepDigits(OldTariffNo));
end;

local procedure GetVariantGraphQuery(var ShopifyVariant: Record "Shpfy Variant"; InventoryQuantities: Text): TextBuilder
var
GraphQuery: TextBuilder;
Expand Down Expand Up @@ -816,6 +825,8 @@ codeunit 30189 "Shpfy Variant API"
end;
end;
end;
JsonHelper.GetValueIntoField(JVariant, 'inventoryItem.harmonizedSystemCode', RecordRef, ShopifyVariant.FieldNo("Tariff No."));
JsonHelper.GetValueIntoField(JVariant, 'inventoryItem.countryCodeOfOrigin', RecordRef, ShopifyVariant.FieldNo("Country/Region of Origin Code"));
RecordRef.SetTable(ShopifyVariant);
ShopifyVariant."UoM Option Id" := 0;
if Shop."UoM as Variant" then
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"data":{"productVariant":{"createdAt":"2025-07-09T06:42:59Z","updatedAt":"2025-07-09T07:40:06Z","availableForSale":true,"barcode":null,"compareAtPrice":"85.00","displayName":"Test Variant","inventoryPolicy":"DENY","position":1,"price":"99.99","sku":null,"taxCode":"","taxable":true,"title":"Test1","product":{"id":"gid://shopify/Product/{{ProductId}}"},"selectedOptions":[{"name":"Test","value":"Test"}],"inventoryItem":{"countryCodeOfOrigin":null,"createdAt":"2025-07-09T06:42:59Z","id":"gid://shopify/InventoryItem/{{VariantId}}","inventoryHistoryUrl":null,"legacyResourceId":"53463462936913","measurement":{"weight":{"value":0.0}},"provinceCodeOfOrigin":null,"requiresShipping":true,"sku":null,"tracked":false,"updatedAt":"2025-07-09T06:42:59Z","unitCost":null},"metafields":{"edges":[]}}},"extensions":{"cost":{"requestedQueryCost":13,"actualQueryCost":6,"throttleStatus":{"maximumAvailable":2000.0,"currentlyAvailable":1994,"restoreRate":100.0}}}}
{"data":{"productVariant":{"createdAt":"2025-07-09T06:42:59Z","updatedAt":"2025-07-09T07:40:06Z","availableForSale":true,"barcode":null,"compareAtPrice":"85.00","displayName":"Test Variant","inventoryPolicy":"DENY","position":1,"price":"99.99","sku":null,"taxCode":"","taxable":true,"title":"Test1","product":{"id":"gid://shopify/Product/{{ProductId}}"},"selectedOptions":[{"name":"Test","value":"Test"}],"inventoryItem":{"countryCodeOfOrigin":"US","harmonizedSystemCode":"610443","createdAt":"2025-07-09T06:42:59Z","id":"gid://shopify/InventoryItem/{{VariantId}}","inventoryHistoryUrl":null,"legacyResourceId":"53463462936913","measurement":{"weight":{"value":0.0}},"provinceCodeOfOrigin":null,"requiresShipping":true,"sku":null,"tracked":false,"updatedAt":"2025-07-09T06:42:59Z","unitCost":null},"metafields":{"edges":[]}}},"extensions":{"cost":{"requestedQueryCost":13,"actualQueryCost":6,"throttleStatus":{"maximumAvailable":2000.0,"currentlyAvailable":1994,"restoreRate":100.0}}}}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
namespace Microsoft.Integration.Shopify.Test;

using Microsoft.Integration.Shopify;
using Microsoft.Inventory.Intrastat;
using Microsoft.Inventory.Item;
using System.TestLibraries.Utilities;

codeunit 139552 "Shpfy Create Item API Test"
Expand Down Expand Up @@ -156,6 +158,62 @@ codeunit 139552 "Shpfy Create Item API Test"
LibraryAssert.IsTrue(Product."Error Message".contains(AutoCreateUnknownItemsDisabledErr), '"Error Message" must contain error text');
end;

[Test]
[HandlerFunctions('GetProductsHttpHandler')]
procedure UnitTestImportProductSyncsHSCodeAndCountryOfOrigin()
var
Product: Record "Shpfy Product";
Item: Record Item;
ShopifyVariant: Record "Shpfy Variant";
ProductInitTest: Codeunit "Shpfy Product Init Test";
CreateItem: Codeunit "Shpfy Create Item";
begin
Initialize();

// [SCENARIO 642046] Importing a product from Shopify imports the HS code (Tariff No.) and Country/Region of Origin onto the Shopify variant and the created item, matching a BC Tariff Number even though Shopify returns the code without separators.

// [GIVEN] A shop with "Sync HS Code and Country" enabled.
Shop."Auto Create Unknown Items" := true;
Shop."Sync HS Code and Country" := true;
Shop.Modify(false);

// [GIVEN] A BC Tariff Number stored with a separator, matching the digits Shopify returns ("610443").
CreateTariffNumber('6104.43');

// [GIVEN] Register Expected Outbound API Requests.
RegExpectedOutboundHttpRequestsForGetProducts();

// [GIVEN] A Shopify variant record of a standard shopify product. (The variant record always exists, even if the products don't have any variants.)
ShopifyVariant := ProductInitTest.CreateStandardProduct(Shop);
ProductId := ShopifyVariant."Product Id";
VariantId := ShopifyVariant."Id";

// [WHEN] Invoke ShpfyCreateItem.CreateItemFromShopifyProduct to import the product details and create the item.
Product.Get(ShopifyVariant."Product Id");
CreateItem.CreateItemFromShopifyProduct(Product);

// [THEN] The Shopify variant mirrors the separator-less HS code and the Country/Region of Origin Code from Shopify.
ShopifyVariant.Get(VariantId);
LibraryAssert.AreEqual('610443', ShopifyVariant."Tariff No.", 'Variant "Tariff No." must mirror the separator-less HS code from Shopify.');
LibraryAssert.AreEqual('US', ShopifyVariant."Country/Region of Origin Code", 'Variant "Country/Region of Origin Code" must be imported from Shopify.');

// [THEN] The created item is matched to the BC Tariff Number stored with a separator.
Product.Get(ShopifyVariant."Product Id");
LibraryAssert.IsTrue(Item.GetBySystemId(Product."Item SystemId"), 'Get Item');
LibraryAssert.AreEqual('6104.43', Item."Tariff No.", 'Item "Tariff No." must match the BC Tariff Number.');
end;

local procedure CreateTariffNumber(No: Code[20])
var
TariffNumber: Record "Tariff Number";
begin
if not TariffNumber.Get(No) then begin
TariffNumber.Init();
TariffNumber."No." := No;
TariffNumber.Insert();
end;
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Shpfy Product Events", OnBeforeCreateItem, '', true, false)]
local procedure OnBeforeCreateItem()
begin
Expand Down
Loading
Loading