Skip to content

Commit 02951bd

Browse files
onbuyukaclaude
andauthored
[Shopify] Fix duplicate Sync Prices on B2B Catalogs and GraphQL quote escaping (#7585)
## Summary - Add confirmation dialog on the **Sync Prices** field validation in B2B Catalogs. When enabling Sync Prices on a catalog that already has it enabled for another company (same catalog ID, different company), the user is prompted to disable it on the other line(s). This prevents conflicting pricing configurations that are silently ignored during sync. - Fix doubled single quotes (`''` → `'`) in 3 GraphQL resource files (`GetCatalogs`, `GetNextCatalogs`, `HasFulfillmentService`) introduced during the PR #7198 migration from AL codeunits to `.graphql` resource files. In AL string literals `''` is an escape for `'`, but in plain-text resource files it's literal, causing Shopify to reject the query parameters. ## Test plan - [x] `SyncPricesConfirmDisablesOtherLine` — user confirms, other catalog line gets Sync Prices disabled - [x] `SyncPricesCancelKeepsBothUnchanged` — user cancels, no changes - [x] `SyncPricesNoConfirmWhenNoDuplicate` — no duplicate, no dialog, field enables normally Fixes [AB#630273](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/630273) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4d872f9 commit 02951bd

5 files changed

Lines changed: 204 additions & 3 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# cost: 14
2-
{"query": "{catalogs(first:25, query: \"company_id:''{{CompanyId}}'' status:ACTIVE\"){ pageInfo{hasNextPage} edges{cursor node{ id title priceList { currency }}}}}"}
2+
{"query": "{catalogs(first:25, query: \"company_id:'{{CompanyId}}' status:ACTIVE\"){ pageInfo{hasNextPage} edges{cursor node{ id title priceList { currency }}}}}"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# cost: 14
2-
{"query": "{catalogs(first:25, after:\"{{After}}\", query: \"company_id:''{{CompanyId}}'' status:ACTIVE\"){ pageInfo{hasNextPage} edges{cursor node{ id title priceList { currency }}}}}"}
2+
{"query": "{catalogs(first:25, after:\"{{After}}\", query: \"company_id:'{{CompanyId}}' status:ACTIVE\"){ pageInfo{hasNextPage} edges{cursor node{ id title priceList { currency }}}}}"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# cost: 6
2-
{"query":"{ locations(first: 1, includeLegacy: true, query: \"name:''{{Name}}''\") { nodes { name }}}"}
2+
{"query":"{ locations(first: 1, includeLegacy: true, query: \"name:'{{Name}}'\") { nodes { name }}}"}

src/Apps/W1/Shopify/App/src/Catalogs/Tables/ShpfyCatalog.Table.al

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ table 30152 "Shpfy Catalog"
132132
Caption = 'Sync Prices';
133133
DataClassification = CustomerContent;
134134
ToolTip = 'Specifies if the prices are synced to Shopify.';
135+
136+
trigger OnValidate()
137+
begin
138+
if "Sync Prices" then
139+
CheckDuplicateSyncPrices();
140+
end;
135141
}
136142
field(17; "Customer No."; Code[20])
137143
{
@@ -173,4 +179,42 @@ table 30152 "Shpfy Catalog"
173179
MarketCatalogRelation.SetRange("Catalog Id", Id);
174180
MarketCatalogRelation.DeleteAll(true);
175181
end;
182+
183+
local procedure CheckDuplicateSyncPrices()
184+
var
185+
OtherCatalog: Record "Shpfy Catalog";
186+
DisableSyncPricesOnOtherLinesQst: Label 'Catalog "%1" already has Sync Prices enabled for company(s) %2. Only one pricing configuration per catalog is used during sync.\Do you want to disable Sync Prices on the other line(s) and enable it on the current one?\Consider using Market Catalogs if you want to link the same catalog to multiple B2B companies.', Comment = '%1 = Catalog Name, %2 = Company Name(s)';
187+
begin
188+
if not GuiAllowed then
189+
exit;
190+
191+
OtherCatalog.SetRange(Id, Id);
192+
OtherCatalog.SetRange("Sync Prices", true);
193+
OtherCatalog.SetFilter("Company SystemId", '<>%1', "Company SystemId");
194+
if OtherCatalog.IsEmpty() then
195+
exit;
196+
197+
if not Confirm(DisableSyncPricesOnOtherLinesQst, false, Name, GetCompanyNamesForCatalogs(OtherCatalog)) then begin
198+
"Sync Prices" := false;
199+
exit;
200+
end;
201+
202+
OtherCatalog.ModifyAll("Sync Prices", false);
203+
end;
204+
205+
local procedure GetCompanyNamesForCatalogs(var CatalogFilter: Record "Shpfy Catalog"): Text
206+
var
207+
Names: TextBuilder;
208+
Separator: Text;
209+
begin
210+
Separator := '';
211+
if CatalogFilter.FindSet() then
212+
repeat
213+
CatalogFilter.CalcFields("Company Name");
214+
Names.Append(Separator);
215+
Names.Append(CatalogFilter."Company Name");
216+
Separator := ', ';
217+
until CatalogFilter.Next() = 0;
218+
exit(Names.ToText());
219+
end;
176220
}

src/Apps/W1/Shopify/Test/Catalogs/ShpfyCatalogPricesTest.Codeunit.al

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,166 @@ codeunit 139646 "Shpfy Catalog Prices Test"
473473
LibraryAssert.AreNearlyEqual(InitPrice * (1 - InitDiscountPerc / 100), Price, 0.01, 'Accurate calculation of discounted price should be verified.');
474474
end;
475475

476+
[Test]
477+
[HandlerFunctions('ActivateConfirmHandler')]
478+
procedure SyncPricesConfirmDisablesOtherLine()
479+
var
480+
Shop: Record "Shpfy Shop";
481+
Catalog1: Record "Shpfy Catalog";
482+
Catalog2: Record "Shpfy Catalog";
483+
ShopifyCompany1: Record "Shpfy Company";
484+
ShopifyCompany2: Record "Shpfy Company";
485+
InitializeTest: Codeunit "Shpfy Initialize Test";
486+
CatalogId: BigInteger;
487+
begin
488+
// [SCENARIO] When enabling Sync Prices on a catalog line where another line for the same
489+
// catalog ID already has Sync Prices enabled, the user confirms to disable the other line.
490+
491+
// [GIVEN] Two companies sharing the same catalog ID, first has Sync Prices = true
492+
Shop := InitializeTest.CreateShop();
493+
ShopifyCompany1.DeleteAll();
494+
CatalogId := Any.IntegerInRange(100000, 999999);
495+
496+
ShopifyCompany1.Init();
497+
ShopifyCompany1.Id := Any.IntegerInRange(1000, 99999);
498+
ShopifyCompany1.Name := 'Company A';
499+
ShopifyCompany1.Insert();
500+
501+
ShopifyCompany2.Init();
502+
ShopifyCompany2.Id := Any.IntegerInRange(1000, 99999);
503+
ShopifyCompany2.Name := 'Company B';
504+
ShopifyCompany2.Insert();
505+
506+
Catalog1.Init();
507+
Catalog1.Id := CatalogId;
508+
Catalog1."Company SystemId" := ShopifyCompany1.SystemId;
509+
Catalog1.Name := 'Test Catalog';
510+
Catalog1."Shop Code" := Shop.Code;
511+
Catalog1."Catalog Type" := "Shpfy Catalog Type"::Company;
512+
Catalog1."Sync Prices" := true;
513+
Catalog1.Insert();
514+
515+
Catalog2.Init();
516+
Catalog2.Id := CatalogId;
517+
Catalog2."Company SystemId" := ShopifyCompany2.SystemId;
518+
Catalog2.Name := 'Test Catalog';
519+
Catalog2."Shop Code" := Shop.Code;
520+
Catalog2."Catalog Type" := "Shpfy Catalog Type"::Company;
521+
Catalog2.Insert();
522+
523+
// [WHEN] User enables Sync Prices on Catalog2 (confirm handler replies true)
524+
Catalog2.Validate("Sync Prices", true);
525+
Catalog2.Modify(true);
526+
527+
// [THEN] Catalog2 has Sync Prices = true
528+
LibraryAssert.IsTrue(Catalog2."Sync Prices", 'Catalog2 should have Sync Prices enabled');
529+
530+
// [THEN] Catalog1 has Sync Prices = false (disabled by the confirmation logic)
531+
Catalog1.Get(CatalogId, ShopifyCompany1.SystemId);
532+
LibraryAssert.IsFalse(Catalog1."Sync Prices", 'Catalog1 should have Sync Prices disabled');
533+
end;
534+
535+
[Test]
536+
[HandlerFunctions('CancelConfirmHandler')]
537+
procedure SyncPricesCancelKeepsBothUnchanged()
538+
var
539+
Shop: Record "Shpfy Shop";
540+
Catalog1: Record "Shpfy Catalog";
541+
Catalog2: Record "Shpfy Catalog";
542+
ShopifyCompany1: Record "Shpfy Company";
543+
ShopifyCompany2: Record "Shpfy Company";
544+
InitializeTest: Codeunit "Shpfy Initialize Test";
545+
CatalogId: BigInteger;
546+
begin
547+
// [SCENARIO] When enabling Sync Prices on a catalog line where another line for the same
548+
// catalog ID already has Sync Prices enabled, the user cancels.
549+
550+
// [GIVEN] Two companies sharing the same catalog ID, first has Sync Prices = true
551+
Shop := InitializeTest.CreateShop();
552+
ShopifyCompany1.DeleteAll();
553+
CatalogId := Any.IntegerInRange(100000, 999999);
554+
555+
ShopifyCompany1.Init();
556+
ShopifyCompany1.Id := Any.IntegerInRange(1000, 99999);
557+
ShopifyCompany1.Name := 'Company A';
558+
ShopifyCompany1.Insert();
559+
560+
ShopifyCompany2.Init();
561+
ShopifyCompany2.Id := Any.IntegerInRange(1000, 99999);
562+
ShopifyCompany2.Name := 'Company B';
563+
ShopifyCompany2.Insert();
564+
565+
Catalog1.Init();
566+
Catalog1.Id := CatalogId;
567+
Catalog1."Company SystemId" := ShopifyCompany1.SystemId;
568+
Catalog1.Name := 'Test Catalog';
569+
Catalog1."Shop Code" := Shop.Code;
570+
Catalog1."Catalog Type" := "Shpfy Catalog Type"::Company;
571+
Catalog1."Sync Prices" := true;
572+
Catalog1.Insert();
573+
574+
Catalog2.Init();
575+
Catalog2.Id := CatalogId;
576+
Catalog2."Company SystemId" := ShopifyCompany2.SystemId;
577+
Catalog2.Name := 'Test Catalog';
578+
Catalog2."Shop Code" := Shop.Code;
579+
Catalog2."Catalog Type" := "Shpfy Catalog Type"::Company;
580+
Catalog2.Insert();
581+
582+
// [WHEN] User enables Sync Prices on Catalog2 (confirm handler replies false)
583+
Catalog2.Validate("Sync Prices", true);
584+
585+
// [THEN] Catalog2 has Sync Prices = false (reverted by cancel)
586+
LibraryAssert.IsFalse(Catalog2."Sync Prices", 'Catalog2 should remain with Sync Prices disabled');
587+
588+
// [THEN] Catalog1 still has Sync Prices = true (unchanged)
589+
Catalog1.Get(CatalogId, ShopifyCompany1.SystemId);
590+
LibraryAssert.IsTrue(Catalog1."Sync Prices", 'Catalog1 should remain with Sync Prices enabled');
591+
end;
592+
593+
[Test]
594+
procedure SyncPricesNoConfirmWhenNoDuplicate()
595+
var
596+
Shop: Record "Shpfy Shop";
597+
Catalog: Record "Shpfy Catalog";
598+
ShopifyCompany: Record "Shpfy Company";
599+
InitializeTest: Codeunit "Shpfy Initialize Test";
600+
begin
601+
// [SCENARIO] No confirmation when there's no other line with the same catalog ID
602+
// having Sync Prices enabled.
603+
604+
// [GIVEN] A single catalog record with Sync Prices = false
605+
Shop := InitializeTest.CreateShop();
606+
607+
ShopifyCompany.Init();
608+
ShopifyCompany.Id := Any.IntegerInRange(1000, 99999);
609+
ShopifyCompany.Name := 'Company A';
610+
ShopifyCompany.Insert();
611+
612+
Catalog.Init();
613+
Catalog.Id := Any.IntegerInRange(100000, 999999);
614+
Catalog."Company SystemId" := ShopifyCompany.SystemId;
615+
Catalog.Name := 'Test Catalog';
616+
Catalog."Shop Code" := Shop.Code;
617+
Catalog."Catalog Type" := "Shpfy Catalog Type"::Company;
618+
Catalog.Insert();
619+
620+
// [WHEN] User enables Sync Prices (no other line exists with same catalog ID)
621+
Catalog.Validate("Sync Prices", true);
622+
623+
// [THEN] Sync Prices = true, no confirmation dialog appeared
624+
LibraryAssert.IsTrue(Catalog."Sync Prices", 'Sync Prices should be enabled');
625+
end;
626+
476627
[ConfirmHandler]
477628
procedure ActivateConfirmHandler(Question: Text[1024]; var Reply: Boolean)
478629
begin
479630
Reply := true;
480631
end;
632+
633+
[ConfirmHandler]
634+
procedure CancelConfirmHandler(Question: Text[1024]; var Reply: Boolean)
635+
begin
636+
Reply := false;
637+
end;
481638
}

0 commit comments

Comments
 (0)