From b2e45c9ea6797ba459db4599cd3f5cba1ab6af2b Mon Sep 17 00:00:00 2001 From: Frank Embleton Date: Tue, 13 Jan 2026 10:41:04 +0100 Subject: [PATCH] Fix: Map BC country code to ISO code for Shopify Tax Area lookups Fixes #6129 ## Problem The Shopify Connector failed when exporting customers/companies with the EU country code 'EL' for Greece. Business Central uses 'EL' for VIES (VAT Information Exchange System) validation, but Shopify requires the ISO 3166-1 alpha-2 code 'GR'. The Shpfy Tax Area table stores Shopify's ISO country codes, but the export functions were using BC's country code directly for Tax Area lookups, causing province information lookups to fail. ## Solution Modified ShpfyCustomerExport and ShpfyCompanyExport codeunits to: 1. Get the ISO country code from the Country/Region table first 2. Use the ISO code for Tax Area lookups instead of BC's country code 3. Use the ISO code for the Shopify API (already was doing this, but moved it earlier in the process) ## Testing Added new tests to verify the ISO country code mapping works correctly: - UnitTestFillInShopifyCustomerDataISOCountryCodeMapping - UnitTestFillInShopifyCompanyLocationISOCountryCodeMapping Updated existing tests to ensure they set up ISO codes properly. --- .../Codeunits/ShpfyCompanyExport.Codeunit.al | 27 ++-- .../Codeunits/ShpfyCustomerExport.Codeunit.al | 21 ++- .../ShpfyCompanyExportTest.Codeunit.al | 121 +++++++++++++++++- .../ShpfyCustomerExportTest.Codeunit.al | 109 +++++++++++++++- 4 files changed, 256 insertions(+), 22 deletions(-) diff --git a/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al b/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al index f8a7a584c4..732063d6ac 100644 --- a/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al @@ -122,6 +122,7 @@ codeunit 30284 "Shpfy Company Export" TaxRegistrationIdMapping: Interface "Shpfy Tax Registration Id Mapping"; CountyCodeTooLongErr: Text; PaymentTermsId: BigInteger; + ISOCountryCode: Code[10]; begin TempCompanyLocation := CompanyLocation; @@ -132,8 +133,20 @@ codeunit 30284 "Shpfy Company Export" CompanyLocation.City := Customer.City; CompanyLocation.Recipient := Customer.Name; + if (Customer."Country/Region Code" = '') and CompanyInformation.Get() then + Customer."Country/Region Code" := CompanyInformation."Country/Region Code"; + + // Get the ISO country code first, as it's needed for both the Shopify API and Tax Area lookups + // Tax Area table stores Shopify's ISO country codes (e.g., "GR" for Greece), + // while BC may use different codes (e.g., "EL" for Greece in EU/VIES contexts) + if CountryRegion.Get(Customer."Country/Region Code") then begin + CountryRegion.TestField("ISO Code"); + ISOCountryCode := CountryRegion."ISO Code"; + end else + ISOCountryCode := CopyStr(Customer."Country/Region Code", 1, MaxStrLen(ISOCountryCode)); + if Customer.County <> '' then begin - TaxArea.SetRange("Country/Region Code", Customer."Country/Region Code"); + TaxArea.SetRange("Country/Region Code", ISOCountryCode); if not TaxArea.IsEmpty() then case Shop."County Source" of Shop."County Source"::Code: @@ -142,7 +155,7 @@ codeunit 30284 "Shpfy Company Export" CountyCodeTooLongErr := StrSubstNo(CountyCodeTooLongLbl, Customer."No.", Customer.Name, StrLen(Customer.County), MaxStrLen(TaxArea."County Code"), Customer.County, Customer.FieldCaption(County)); Error(CountyCodeTooLongErr); end; - TaxArea.SetRange("Country/Region Code", Customer."Country/Region Code"); + TaxArea.SetRange("Country/Region Code", ISOCountryCode); TaxArea.SetRange("County Code", Customer.County); if TaxArea.FindFirst() then begin CompanyLocation."Province Code" := TaxArea."County Code"; @@ -151,7 +164,7 @@ codeunit 30284 "Shpfy Company Export" end; Shop."County Source"::Name: begin - TaxArea.SetRange("Country/Region Code", Customer."Country/Region Code"); + TaxArea.SetRange("Country/Region Code", ISOCountryCode); TaxArea.SetRange(County, Customer.County); if TaxArea.FindFirst() then begin CompanyLocation."Province Code" := TaxArea."County Code"; @@ -167,13 +180,7 @@ codeunit 30284 "Shpfy Company Export" end; end; - if (Customer."Country/Region Code" = '') and CompanyInformation.Get() then - Customer."Country/Region Code" := CompanyInformation."Country/Region Code"; - - if CountryRegion.Get(Customer."Country/Region Code") then begin - CountryRegion.TestField("ISO Code"); - CompanyLocation."Country/Region Code" := CountryRegion."ISO Code"; - end; + CompanyLocation."Country/Region Code" := ISOCountryCode; CompanyLocation."Phone No." := Customer."Phone No."; diff --git a/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al b/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al index 5043f72eb0..8b16ba09ad 100644 --- a/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al @@ -102,6 +102,7 @@ codeunit 30116 "Shpfy Customer Export" #pragma warning restore AA0073 TaxArea: Record "Shpfy Tax Area"; CountyCodeTooLongErr: Text; + ISOCountryCode: Code[10]; begin xShopifyCustomer := ShopifyCustomer; xCustomerAddress := CustomerAddress; @@ -140,8 +141,17 @@ codeunit 30116 "Shpfy Customer Export" if (Customer."Country/Region Code" = '') and CompanyInformation.Get() then Customer."Country/Region Code" := CompanyInformation."Country/Region Code"; + // Get the ISO country code first, as it's needed for both the Shopify API and Tax Area lookups + // Tax Area table stores Shopify's ISO country codes (e.g., "GR" for Greece), + // while BC may use different codes (e.g., "EL" for Greece in EU/VIES contexts) + if CountryRegion.Get(Customer."Country/Region Code") then begin + CountryRegion.TestField("ISO Code"); + ISOCountryCode := CountryRegion."ISO Code"; + end else + ISOCountryCode := CopyStr(Customer."Country/Region Code", 1, MaxStrLen(ISOCountryCode)); + if Customer.County <> '' then begin - TaxArea.SetRange("Country/Region Code", Customer."Country/Region Code"); + TaxArea.SetRange("Country/Region Code", ISOCountryCode); if not TaxArea.IsEmpty() then case Shop."County Source" of Shop."County Source"::Code: @@ -150,7 +160,7 @@ codeunit 30116 "Shpfy Customer Export" CountyCodeTooLongErr := StrSubstNo(CountyCodeTooLongLbl, Customer."No.", Customer.Name, StrLen(Customer.County), MaxStrLen(TaxArea."County Code"), Customer.County, Customer.FieldCaption(County)); Error(CountyCodeTooLongErr); end; - TaxArea.SetRange("Country/Region Code", Customer."Country/Region Code"); + TaxArea.SetRange("Country/Region Code", ISOCountryCode); TaxArea.SetRange("County Code", Customer.County); if TaxArea.FindFirst() then begin CustomerAddress."Province Code" := TaxArea."County Code"; @@ -159,7 +169,7 @@ codeunit 30116 "Shpfy Customer Export" end; Shop."County Source"::Name: begin - TaxArea.SetRange("Country/Region Code", Customer."Country/Region Code"); + TaxArea.SetRange("Country/Region Code", ISOCountryCode); TaxArea.SetRange(County, Customer.County); if TaxArea.FindFirst() then begin CustomerAddress."Province Code" := TaxArea."County Code"; @@ -175,10 +185,7 @@ codeunit 30116 "Shpfy Customer Export" end; end; - if CountryRegion.Get(Customer."Country/Region Code") then begin - CountryRegion.TestField("ISO Code"); - CustomerAddress."Country/Region Code" := CountryRegion."ISO Code"; - end; + CustomerAddress."Country/Region Code" := ISOCountryCode; CustomerAddress.Phone := Customer."Phone No."; diff --git a/src/Apps/W1/Shopify/Test/Companies/ShpfyCompanyExportTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Companies/ShpfyCompanyExportTest.Codeunit.al index b7fd88c45a..6dda1a7801 100644 --- a/src/Apps/W1/Shopify/Test/Companies/ShpfyCompanyExportTest.Codeunit.al +++ b/src/Apps/W1/Shopify/Test/Companies/ShpfyCompanyExportTest.Codeunit.al @@ -5,6 +5,7 @@ namespace Microsoft.Integration.Shopify.Test; +using Microsoft.Foundation.Address; using Microsoft.Foundation.PaymentTerms; using Microsoft.Integration.Shopify; using Microsoft.Sales.Customer; @@ -33,16 +34,29 @@ codeunit 139636 "Shpfy Company Export Test" procedure UnitTestFillInShopifyCustomerData() var Customer: Record Customer; + CountryRegion: Record "Country/Region"; ShopifyCompany: Record "Shpfy Company"; CompanyLocation: Record "Shpfy Company Location"; ShopifyShop: Record "Shpfy Shop"; Result: Boolean; ShopifyPaymentTermsId: BigInteger; + ExpectedCountryCode: Code[10]; begin // [SCENARIO] Convert an existing company record to a "Shpfy Company" and "Shpfy Company Location" record. // [GIVEN] Customer record Customer.FindFirst(); + + // [GIVEN] Ensure the customer's country has an ISO code set + if CountryRegion.Get(Customer."Country/Region Code") then begin + if CountryRegion."ISO Code" = '' then begin + CountryRegion."ISO Code" := CopyStr(Customer."Country/Region Code", 1, MaxStrLen(CountryRegion."ISO Code")); + CountryRegion.Modify(); + end; + ExpectedCountryCode := CountryRegion."ISO Code"; + end else + ExpectedCountryCode := CopyStr(Customer."Country/Region Code", 1, MaxStrLen(ExpectedCountryCode)); + ShopifyShop := InitializeTest.CreateShop(); ShopifyShop."Name Source" := Enum::"Shpfy Name Source"::CompanyName; ShopifyShop."Name 2 Source" := Enum::"Shpfy Name Source"::None; @@ -68,7 +82,7 @@ codeunit 139636 "Shpfy Company Export Test" LibraryAssert.AreEqual(Customer."Address 2", CompanyLocation."Address 2", 'Address 2'); LibraryAssert.AreEqual(Customer."Post Code", CompanyLocation.Zip, 'Post Code'); LibraryAssert.AreEqual(Customer.City, CompanyLocation.City, 'City'); - LibraryAssert.AreEqual(Customer."Country/Region Code", CompanyLocation."Country/Region Code", 'Country'); + LibraryAssert.AreEqual(ExpectedCountryCode, CompanyLocation."Country/Region Code", 'Country should be ISO code'); LibraryAssert.AreEqual(Customer.Name, CompanyLocation.Recipient, 'Recipient'); LibraryAssert.AreEqual(ShopifyPaymentTermsId, CompanyLocation."Shpfy Payment Terms Id", 'Payment Terms Id should be 0'); end; @@ -108,6 +122,7 @@ codeunit 139636 "Shpfy Company Export Test" procedure UnitTestFillInShopifyCustomerDataCounty() var Customer: Record Customer; + CountryRegion: Record "Country/Region"; CompanyLocation: Record "Shpfy Company Location"; ShopifyShop: Record "Shpfy Shop"; TaxArea: Record "Shpfy Tax Area"; @@ -115,16 +130,30 @@ codeunit 139636 "Shpfy Company Export Test" begin // [SCENARIO] County information is only sent to Shopify if the country has any provinces - // [GIVEN] Customer record + // [GIVEN] Customer record with country code 'US' that has ISO code 'US' Customer.FindFirst(); Customer."Country/Region Code" := 'US'; Customer."County" := 'CA'; Customer.Modify(); + // Ensure the US country has ISO code set + if not CountryRegion.Get('US') then begin + CountryRegion.Init(); + CountryRegion.Code := 'US'; + CountryRegion."ISO Code" := 'US'; + CountryRegion.Insert(); + end else begin + if CountryRegion."ISO Code" = '' then begin + CountryRegion."ISO Code" := 'US'; + CountryRegion.Modify(); + end; + end; + TaxArea."Country/Region Code" := 'US'; TaxArea.County := 'CA'; TaxArea."County Code" := 'CA'; - TaxArea.Insert(); + if not TaxArea.Insert() then + TaxArea.Modify(); ShopifyShop := InitializeTest.CreateShop(); ShopifyShop."Name Source" := Enum::"Shpfy Name Source"::CompanyName; @@ -149,6 +178,20 @@ codeunit 139636 "Shpfy Company Export Test" // [WHEN] Change the county to a country without provinces Customer."Country/Region Code" := 'DE'; Customer.Modify(); + + // Ensure the DE country has ISO code set + if not CountryRegion.Get('DE') then begin + CountryRegion.Init(); + CountryRegion.Code := 'DE'; + CountryRegion."ISO Code" := 'DE'; + CountryRegion.Insert(); + end else begin + if CountryRegion."ISO Code" = '' then begin + CountryRegion."ISO Code" := 'DE'; + CountryRegion.Modify(); + end; + end; + Clear(CompanyLocation); Result := CompanyExport.FillInShopifyCompanyLocation(Customer, CompanyLocation); @@ -158,6 +201,78 @@ codeunit 139636 "Shpfy Company Export Test" LibraryAssert.IsTrue(CompanyLocation."Province Name" = '', 'Province Name'); end; + [Test] + procedure UnitTestFillInShopifyCompanyLocationISOCountryCodeMapping() + var + Customer: Record Customer; + CountryRegion: Record "Country/Region"; + CompanyLocation: Record "Shpfy Company Location"; + ShopifyShop: Record "Shpfy Shop"; + TaxArea: Record "Shpfy Tax Area"; + Result: Boolean; + BCCountryCode: Code[10]; + ISOCountryCode: Code[10]; + begin + // [SCENARIO] When BC uses a different country code than the ISO standard (e.g., "EL" for Greece instead of "GR"), + // the Shopify connector should correctly map to the ISO code for Shopify API and Tax Area lookups. + + // [GIVEN] A country with BC code "EL" and ISO code "GR" (like Greece in EU/VIES context) + BCCountryCode := 'EL'; + ISOCountryCode := 'GR'; + + if not CountryRegion.Get(BCCountryCode) then begin + CountryRegion.Init(); + CountryRegion.Code := BCCountryCode; + CountryRegion.Name := 'Greece'; + CountryRegion."ISO Code" := ISOCountryCode; + CountryRegion.Insert(); + end else begin + CountryRegion."ISO Code" := ISOCountryCode; + CountryRegion.Modify(); + end; + + // [GIVEN] A Tax Area with country code "GR" (Shopify's ISO code) and province info + TaxArea.Init(); + TaxArea."Country/Region Code" := ISOCountryCode; + TaxArea.County := 'Attica'; + TaxArea."County Code" := 'I'; + if not TaxArea.Insert() then + TaxArea.Modify(); + + // [GIVEN] A customer with country code "EL" (BC code) and matching county + Customer.FindFirst(); + Customer."Country/Region Code" := BCCountryCode; + Customer.County := 'Attica'; + Customer.Modify(); + + // [GIVEN] Shop with County Source = Name + ShopifyShop := InitializeTest.CreateShop(); + ShopifyShop."Name Source" := Enum::"Shpfy Name Source"::CompanyName; + ShopifyShop."Name 2 Source" := Enum::"Shpfy Name Source"::None; + ShopifyShop."Contact Source" := Enum::"Shpfy Name Source"::None; + ShopifyShop."County Source" := Enum::"Shpfy County Source"::Name; + ShopifyShop."B2B Enabled" := true; + CompanyLocation.Init(); + + CompanyExport.SetShop(ShopifyShop); + + // [WHEN] Invoke FillInShopifyCompanyLocation + Result := CompanyExport.FillInShopifyCompanyLocation(Customer, CompanyLocation); + + // [THEN] The result is true + LibraryAssert.IsTrue(Result, 'Result should be true'); + + // [THEN] The country code is mapped to the ISO code "GR" (not the BC code "EL") + LibraryAssert.AreEqual(ISOCountryCode, CompanyLocation."Country/Region Code", 'Country/Region Code should be ISO code GR, not BC code EL'); + + // [THEN] The province information is correctly retrieved using the ISO country code + LibraryAssert.AreEqual('I', CompanyLocation."Province Code", 'Province Code should be found using ISO country code'); + LibraryAssert.AreEqual('Attica', CompanyLocation."Province Name", 'Province Name should be found using ISO country code'); + + // Cleanup + TaxArea.Delete(); + end; + local procedure Initialize() begin diff --git a/src/Apps/W1/Shopify/Test/Customers/ShpfyCustomerExportTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Customers/ShpfyCustomerExportTest.Codeunit.al index 8bee14df10..75a2b749a4 100644 --- a/src/Apps/W1/Shopify/Test/Customers/ShpfyCustomerExportTest.Codeunit.al +++ b/src/Apps/W1/Shopify/Test/Customers/ShpfyCustomerExportTest.Codeunit.al @@ -5,6 +5,7 @@ namespace Microsoft.Integration.Shopify.Test; +using Microsoft.Foundation.Address; using Microsoft.Integration.Shopify; using Microsoft.Sales.Customer; using System.TestLibraries.Utilities; @@ -18,6 +19,7 @@ codeunit 139568 "Shpfy Customer Export Test" var LibraryAssert: Codeunit "Library Assert"; CustomerExport: Codeunit "Shpfy Customer Export"; + Any: Codeunit Any; [Test] procedure UnitTestSpiltNameIntoFirstAndLastName() @@ -95,6 +97,7 @@ codeunit 139568 "Shpfy Customer Export Test" procedure UnitTestFillInShopifyCustomerDataCounty() var Customer: Record Customer; + CountryRegion: Record "Country/Region"; ShopifyCustomer: Record "Shpfy Customer"; CustomerAddress: Record "Shpfy Customer Address"; Shop: Record "Shpfy Shop"; @@ -104,16 +107,30 @@ codeunit 139568 "Shpfy Customer Export Test" begin // [SCENARIO] County information is only sent to Shopify if the country has any provinces - // [GIVEN] Customer record + // [GIVEN] Customer record with country code 'US' that has ISO code 'US' Customer.FindFirst(); Customer."Country/Region Code" := 'US'; Customer."County" := 'CA'; Customer.Modify(); + // Ensure the US country has ISO code set + if not CountryRegion.Get('US') then begin + CountryRegion.Init(); + CountryRegion.Code := 'US'; + CountryRegion."ISO Code" := 'US'; + CountryRegion.Insert(); + end else begin + if CountryRegion."ISO Code" = '' then begin + CountryRegion."ISO Code" := 'US'; + CountryRegion.Modify(); + end; + end; + TaxArea."Country/Region Code" := 'US'; TaxArea.County := 'CA'; TaxArea."County Code" := 'CA'; - TaxArea.Insert(); + if not TaxArea.Insert() then + TaxArea.Modify(); Shop := InitializeTest.CreateShop(); Shop."Name Source" := Enum::"Shpfy Name Source"::CompanyName; @@ -137,6 +154,20 @@ codeunit 139568 "Shpfy Customer Export Test" // [WHEN] Change the county to a country without provinces Customer."Country/Region Code" := 'DE'; Customer.Modify(); + + // Ensure the DE country has ISO code set + if not CountryRegion.Get('DE') then begin + CountryRegion.Init(); + CountryRegion.Code := 'DE'; + CountryRegion."ISO Code" := 'DE'; + CountryRegion.Insert(); + end else begin + if CountryRegion."ISO Code" = '' then begin + CountryRegion."ISO Code" := 'DE'; + CountryRegion.Modify(); + end; + end; + Clear(CustomerAddress); Clear(ShopifyCustomer); Result := CustomerExport.FillInShopifyCustomerData(Customer, ShopifyCustomer, CustomerAddress); @@ -146,4 +177,78 @@ codeunit 139568 "Shpfy Customer Export Test" LibraryAssert.IsTrue(CustomerAddress."Province Code" = '', 'Province Code'); LibraryAssert.IsTrue(CustomerAddress."Province Name" = '', 'Province Name'); end; + + [Test] + procedure UnitTestFillInShopifyCustomerDataISOCountryCodeMapping() + var + Customer: Record Customer; + CountryRegion: Record "Country/Region"; + ShopifyCustomer: Record "Shpfy Customer"; + CustomerAddress: Record "Shpfy Customer Address"; + Shop: Record "Shpfy Shop"; + TaxArea: Record "Shpfy Tax Area"; + InitializeTest: Codeunit "Shpfy Initialize Test"; + Result: Boolean; + BCCountryCode: Code[10]; + ISOCountryCode: Code[10]; + begin + // [SCENARIO] When BC uses a different country code than the ISO standard (e.g., "EL" for Greece instead of "GR"), + // the Shopify connector should correctly map to the ISO code for Shopify API and Tax Area lookups. + + // [GIVEN] A country with BC code "EL" and ISO code "GR" (like Greece in EU/VIES context) + BCCountryCode := 'EL'; + ISOCountryCode := 'GR'; + + if not CountryRegion.Get(BCCountryCode) then begin + CountryRegion.Init(); + CountryRegion.Code := BCCountryCode; + CountryRegion.Name := 'Greece'; + CountryRegion."ISO Code" := ISOCountryCode; + CountryRegion.Insert(); + end else begin + CountryRegion."ISO Code" := ISOCountryCode; + CountryRegion.Modify(); + end; + + // [GIVEN] A Tax Area with country code "GR" (Shopify's ISO code) and province info + TaxArea.Init(); + TaxArea."Country/Region Code" := ISOCountryCode; + TaxArea.County := 'Attica'; + TaxArea."County Code" := 'I'; + if not TaxArea.Insert() then + TaxArea.Modify(); + + // [GIVEN] A customer with country code "EL" (BC code) and matching county + Customer.FindFirst(); + Customer."Country/Region Code" := BCCountryCode; + Customer.County := 'Attica'; + Customer.Modify(); + + // [GIVEN] Shop with County Source = Name + Shop := InitializeTest.CreateShop(); + Shop."Name Source" := Enum::"Shpfy Name Source"::CompanyName; + Shop."Name 2 Source" := Enum::"Shpfy Name Source"::None; + Shop."Contact Source" := Enum::"Shpfy Name Source"::None; + Shop."County Source" := Enum::"Shpfy County Source"::Name; + ShopifyCustomer.Init(); + CustomerAddress.Init(); + + CustomerExport.SetShop(Shop); + + // [WHEN] Invoke FillInShopifyCustomerData + Result := CustomerExport.FillInShopifyCustomerData(Customer, ShopifyCustomer, CustomerAddress); + + // [THEN] The result is true + LibraryAssert.IsTrue(Result, 'Result should be true'); + + // [THEN] The country code is mapped to the ISO code "GR" (not the BC code "EL") + LibraryAssert.AreEqual(ISOCountryCode, CustomerAddress."Country/Region Code", 'Country/Region Code should be ISO code GR, not BC code EL'); + + // [THEN] The province information is correctly retrieved using the ISO country code + LibraryAssert.AreEqual('I', CustomerAddress."Province Code", 'Province Code should be found using ISO country code'); + LibraryAssert.AreEqual('Attica', CustomerAddress."Province Name", 'Province Name should be found using ISO country code'); + + // Cleanup + TaxArea.Delete(); + end; }