diff --git a/src/Apps/W1/Shopify/App/app.json b/src/Apps/W1/Shopify/App/app.json
index a6d665c241..df75bcf1ee 100644
--- a/src/Apps/W1/Shopify/App/app.json
+++ b/src/Apps/W1/Shopify/App/app.json
@@ -17,7 +17,7 @@
"idRanges": [
{
"from": 30100,
- "to": 30450
+ "to": 30460
}
],
"internalsVisibleTo": [
diff --git a/src/Apps/W1/Shopify/App/src/Base/Codeunits/ShpfyCommunicationMgt.Codeunit.al b/src/Apps/W1/Shopify/App/src/Base/Codeunits/ShpfyCommunicationMgt.Codeunit.al
index c4f451dea9..1b112cdeb1 100644
--- a/src/Apps/W1/Shopify/App/src/Base/Codeunits/ShpfyCommunicationMgt.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Base/Codeunits/ShpfyCommunicationMgt.Codeunit.al
@@ -22,7 +22,7 @@ codeunit 30103 "Shpfy Communication Mgt."
CommunicationEvents: Codeunit "Shpfy Communication Events";
GraphQLQueries: Codeunit "Shpfy GraphQL Queries";
NextExecutionTime: DateTime;
- VersionTok: Label '2025-07', Locked = true;
+ VersionTok: Label '2026-01', Locked = true;
OutgoingRequestsNotEnabledConfirmLbl: Label 'Importing data to your Shopify shop is not enabled, do you want to go to shop card to enable?';
OutgoingRequestsNotEnabledErr: Label 'Importing data to your Shopify shop is not enabled, navigate to shop card to enable.';
IsTestInProgress: Boolean;
diff --git a/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationAPI.Codeunit.al
index f860462b81..0e2ed89009 100644
--- a/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationAPI.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationAPI.Codeunit.al
@@ -22,25 +22,6 @@ codeunit 30278 "Shpfy Bulk Operation API"
CommunicationMgt.SetShop(Shop);
end;
- internal procedure GetCurrentBulkRequest(var BulkOperationId: BigInteger; var Status: Enum "Shpfy Bulk Operation Status"; var ErrorCode: Text; var CompletedAt: DateTime; var Url: Text; var PartialDataUrl: Text)
- var
- JsonHelper: Codeunit "Shpfy Json Helper";
- GraphQLType: Enum "Shpfy GraphQL Type";
- Parameters: Dictionary of [Text, Text];
- JResponse: JsonToken;
- JBulkOperation: JsonObject;
- begin
- JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType::GetCurrentBulkOperation, Parameters);
- if JsonHelper.GetJsonObject(JResponse, JBulkOperation, 'data.currentBulkOperation') then begin
- BulkOperationId := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JBulkOperation, 'id'));
- Status := ConvertToBulkOperationStatus(JsonHelper.GetValueAsText(JBulkOperation, 'status'));
- ErrorCode := JsonHelper.GetValueAsText(JBulkOperation, 'errorCode');
- CompletedAt := JsonHelper.GetValueAsDateTime(JBulkOperation, 'completedAt');
- Url := JsonHelper.GetValueAsText(JBulkOperation, 'url');
- PartialDataUrl := JsonHelper.GetValueAsText(JBulkOperation, 'partialDataUrl');
- end;
- end;
-
internal procedure GetBulkRequest(BulkOperationId: BigInteger; var Status: Enum "Shpfy Bulk Operation Status"; var ErrorCode: Text; var CompletedAt: DateTime; var Url: Text; var PartialDataUrl: Text)
var
JsonHelper: Codeunit "Shpfy Json Helper";
diff --git a/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationMgt.Codeunit.al b/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationMgt.Codeunit.al
index 3efdaf0142..5b93622889 100644
--- a/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationMgt.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Bulk Operations/Codeunits/ShpfyBulkOperationMgt.Codeunit.al
@@ -11,8 +11,6 @@ codeunit 30270 "Shpfy Bulk Operation Mgt."
{
var
InvalidUserErr: Label 'You must sign in with a Business Central licensed user to enable the feature.';
- CategoryTok: Label 'Shopify Integration', Locked = true;
- BulkOperationsDontMatchLbl: Label 'Searched bulk operation (%1, %2, %3) does not match with current one (%4)', Comment = '%1 = Bulk Operation Id, %2 = Shop Code, %3 = Type, %4 = Bulk Operation Id', Locked = true;
BulkOperationCreatedLbl: Label 'A bulk request was sent to Shopify. You can check the status of the synchronization in the Shopify Bulk Operations page.';
internal procedure EnableBulkOperations(var Shop: Record "Shpfy Shop")
@@ -112,15 +110,14 @@ codeunit 30270 "Shpfy Bulk Operation Mgt."
var
BulkOperation: Record "Shpfy Bulk Operation";
BulkOperationAPI: Codeunit "Shpfy Bulk Operation API";
- BulkOperationId: BigInteger;
ErrorCode: Text;
CompletedAt: DateTime;
Url: Text;
PartialDataUrl: Text;
begin
BulkOperationAPI.SetShop(Shop);
- BulkOperationAPI.GetCurrentBulkRequest(BulkOperationId, BulkOperationStatus, ErrorCode, CompletedAt, Url, PartialDataUrl);
- if BulkOperation.Get(BulkOperationId, Shop.Code, Type) then begin
+ BulkOperationAPI.GetBulkRequest(SearchBulkOperationId, BulkOperationStatus, ErrorCode, CompletedAt, Url, PartialDataUrl);
+ if BulkOperation.Get(SearchBulkOperationId, Shop.Code, Type) then begin
BulkOperation.Status := BulkOperationStatus;
if ErrorCode <> '' then
BulkOperation."Error Code" := CopyStr(ErrorCode, 1, MaxStrLen(BulkOperation."Error Code"));
@@ -131,22 +128,6 @@ codeunit 30270 "Shpfy Bulk Operation Mgt."
if PartialDataUrl <> '' then
BulkOperation."Partial Data Url" := CopyStr(PartialDataUrl, 1, MaxStrLen(BulkOperation."Partial Data Url"));
BulkOperation.Modify(true);
-
- if BulkOperationId <> SearchBulkOperationId then begin
- Session.LogMessage('0000KZC', StrSubstNo(BulkOperationsDontMatchLbl, SearchBulkOperationId, Shop.Code, Type, BulkOperationId), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
- BulkOperationAPI.GetBulkRequest(SearchBulkOperationId, BulkOperationStatus, ErrorCode, CompletedAt, Url, PartialDataUrl);
- BulkOperation.Get(SearchBulkOperationId, Shop.Code, Type);
- BulkOperation.Status := BulkOperationStatus;
- if ErrorCode <> '' then
- BulkOperation."Error Code" := CopyStr(ErrorCode, 1, MaxStrLen(BulkOperation."Error Code"));
- if CompletedAt <> 0DT then
- BulkOperation."Completed At" := CompletedAt;
- if Url <> '' then
- BulkOperation.Url := CopyStr(Url, 1, MaxStrLen(BulkOperation.Url));
- if PartialDataUrl <> '' then
- BulkOperation."Partial Data Url" := CopyStr(PartialDataUrl, 1, MaxStrLen(BulkOperation."Partial Data Url"));
- BulkOperation.Modify(true);
- end;
end;
end;
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLBulkOperations.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLBulkOperations.Codeunit.al
deleted file mode 100644
index cf867bfacf..0000000000
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLBulkOperations.Codeunit.al
+++ /dev/null
@@ -1,29 +0,0 @@
-// ------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-// ------------------------------------------------------------------------------------------------
-
-namespace Microsoft.Integration.Shopify;
-
-codeunit 30277 "Shpfy GQL BulkOperations" implements "Shpfy IGraphQL"
-{
- Access = Internal;
-
- ///
- /// GetGraphQL.
- ///
- /// Return value of type Text.
- internal procedure GetGraphQL(): Text
- begin
- exit('{"query": "query { currentBulkOperation(type: MUTATION) { id status errorCode createdAt completedAt fileSize url partialDataUrl }}"}');
- end;
-
- ///
- /// GetExpectedCost.
- ///
- /// Return value of type Integer.
- internal procedure GetExpectedCost(): Integer
- begin
- exit(10);
- end;
-}
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLModifyInventory.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLModifyInventory.Codeunit.al
index 8c1db4e02f..0e3d7ab1f8 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLModifyInventory.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLModifyInventory.Codeunit.al
@@ -9,7 +9,7 @@ codeunit 30102 "Shpfy GQL Modify Inventory" implements "Shpfy IGraphQL"
{
procedure GetGraphQL(): Text
begin
- exit('{"query":"mutation inventorySetOnHandQuantities($input:InventorySetOnHandQuantitiesInput!) { inventorySetOnHandQuantities(input: $input) { userErrors { field message }}}","variables":{"input":{"reason":"correction","setQuantities":[]}}}');
+ exit('{"query":"mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) { inventorySetQuantities(input: $input) @idempotent(key: \"{{IdempotencyKey}}\") { inventoryAdjustmentGroup { id } userErrors { field message code }}}","variables":{"input":{"name":"on_hand","reason":"correction","quantities":[]}}}');
end;
procedure GetExpectedCost(): Integer
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextPayouts.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextPayouts.Codeunit.al
index 7c671bb64c..feef1f9531 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextPayouts.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextPayouts.Codeunit.al
@@ -18,7 +18,7 @@ codeunit 30392 "Shpfy GQL NextPayouts" implements "Shpfy IGraphQL"
/// Return value of type Text.
procedure GetGraphQL(): Text
begin
- exit('{"query":"{ shopifyPaymentsAccount { payouts(first: 100, query: \"id:>{{SinceId}}\", after: \"{{After}}\") { edges { cursor node { id status summary { adjustmentsFee { amount } adjustmentsGross { amount } chargesFee { amount } chargesGross { amount } refundsFee { amount } refundsFeeGross { amount } reservedFundsFee { amount } reservedFundsGross { amount } retriedPayoutsFee { amount } retriedPayoutsGross { amount currencyCode } } issuedAt net { amount currencyCode } } } pageInfo { hasNextPage } } } }"}');
+ exit('{"query":"{ shopifyPaymentsAccount { payouts(first: 100, query: \"id:>={{SinceId}}\", after: \"{{After}}\") { edges { cursor node { id status externalTraceId summary { adjustmentsFee { amount } adjustmentsGross { amount } chargesFee { amount } chargesGross { amount } refundsFee { amount } refundsFeeGross { amount } reservedFundsFee { amount } reservedFundsGross { amount } retriedPayoutsFee { amount } retriedPayoutsGross { amount currencyCode } } issuedAt net { amount currencyCode } } } pageInfo { hasNextPage } } } }"}');
end;
///
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextReturnLines.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextReturnLines.Codeunit.al
index ea5ae95ea3..9cc8fb62e9 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextReturnLines.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLNextReturnLines.Codeunit.al
@@ -10,7 +10,7 @@ codeunit 30227 "Shpfy GQL NextReturnLines" implements "Shpfy IGraphQL"
internal procedure GetGraphQL(): Text
begin
- exit('{"query":"{ return(id: \"gid://shopify/Return/{{ReturnId}}\") { returnLineItems(first: 10, after:\"{{After}}\") { pageInfo { endCursor hasNextPage } nodes { ... on ReturnLineItem { id quantity returnReason returnReasonNote refundableQuantity refundedQuantity customerNote totalWeight { unit value } withCodeDiscountedTotalPriceSet { presentmentMoney { amount } shopMoney { amount } } fulfillmentLineItem { id lineItem { id } quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount } } discountedTotalSet { presentmentMoney { amount } shopMoney { amount }}}}}}}}"}');
+ exit('{"query":"{ return(id: \"gid://shopify/Return/{{ReturnId}}\") { returnLineItems(first: 10, after:\"{{After}}\") { pageInfo { endCursor hasNextPage } nodes { id quantity returnReasonDefinition { name handle } returnReasonNote refundableQuantity refundedQuantity customerNote ... on UnverifiedReturnLineItem { __typename unitPrice { amount currencyCode } } ... on ReturnLineItem { __typename totalWeight { unit value } withCodeDiscountedTotalPriceSet { presentmentMoney { amount } shopMoney { amount } } fulfillmentLineItem { id lineItem { id } quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount } } discountedTotalSet { presentmentMoney { amount } shopMoney { amount }}}}}}}}"}');
end;
internal procedure GetExpectedCost(): Integer
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLPayouts.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLPayouts.Codeunit.al
index 454f137960..e5f259cc02 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLPayouts.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLPayouts.Codeunit.al
@@ -18,7 +18,7 @@ codeunit 30391 "Shpfy GQL Payouts" implements "Shpfy IGraphQL"
/// Return value of type Text.
procedure GetGraphQL(): Text
begin
- exit('{"query":"{ shopifyPaymentsAccount { payouts(first: 100, query: \"id:>{{SinceId}}\") { edges { cursor node { id status summary { adjustmentsFee { amount } adjustmentsGross { amount } chargesFee { amount } chargesGross { amount } refundsFee { amount } refundsFeeGross { amount } reservedFundsFee { amount } reservedFundsGross { amount } retriedPayoutsFee { amount } retriedPayoutsGross { amount } } issuedAt net { amount currencyCode } } } pageInfo { hasNextPage } } } }"}');
+ exit('{"query":"{ shopifyPaymentsAccount { payouts(first: 100, query: \"id:>={{SinceId}}\") { edges { cursor node { id status externalTraceId summary { adjustmentsFee { amount } adjustmentsGross { amount } chargesFee { amount } chargesGross { amount } refundsFee { amount } refundsFeeGross { amount } reservedFundsFee { amount } reservedFundsGross { amount } retriedPayoutsFee { amount } retriedPayoutsGross { amount currencyCode } } issuedAt net { amount currencyCode } } } pageInfo { hasNextPage } } } }"}');
end;
///
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLReturnLines.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLReturnLines.Codeunit.al
index 8edaca2ecc..c764188a7f 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLReturnLines.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLReturnLines.Codeunit.al
@@ -10,7 +10,7 @@ codeunit 30226 "Shpfy GQL ReturnLines" implements "Shpfy IGraphQL"
internal procedure GetGraphQL(): Text
begin
- exit('{"query":"{ return(id: \"gid://shopify/Return/{{ReturnId}}\") { returnLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { ... on ReturnLineItem { id quantity returnReason returnReasonNote refundableQuantity refundedQuantity customerNote totalWeight { unit value } withCodeDiscountedTotalPriceSet { presentmentMoney { amount } shopMoney { amount } } fulfillmentLineItem { id lineItem { id } quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount } } discountedTotalSet { presentmentMoney { amount } shopMoney { amount }}}}}}}}"}');
+ exit('{"query":"{ return(id: \"gid://shopify/Return/{{ReturnId}}\") { returnLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { id quantity returnReasonDefinition { name handle } returnReasonNote refundableQuantity refundedQuantity customerNote ... on UnverifiedReturnLineItem { __typename unitPrice { amount currencyCode } } ... on ReturnLineItem { __typename totalWeight { unit value } withCodeDiscountedTotalPriceSet { presentmentMoney { amount } shopMoney { amount } } fulfillmentLineItem { id lineItem { id } quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount } } discountedTotalSet { presentmentMoney { amount } shopMoney { amount }}}}}}}}"}');
end;
internal procedure GetExpectedCost(): Integer
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al
index 82d4c56201..caee267ffe 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Codeunits/ShpfyGQLVariantById.Codeunit.al
@@ -18,7 +18,7 @@ codeunit 30150 "Shpfy GQL VariantById" implements "Shpfy IGraphQL"
/// Return value of type Text.
internal procedure GetGraphQL(): Text
begin
- exit('{"query":"{productVariant(id: \"gid://shopify/ProductVariant/{{VariantId}}\") {createdAt updatedAt availableForSale barcode compareAtPrice displayName inventoryPolicy position price sku taxCode 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}}}}}"}');
+ exit('{"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}}}}}"}');
end;
///
diff --git a/src/Apps/W1/Shopify/App/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al b/src/Apps/W1/Shopify/App/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al
index 644499e6cc..e8c769cab4 100644
--- a/src/Apps/W1/Shopify/App/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al
+++ b/src/Apps/W1/Shopify/App/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al
@@ -300,11 +300,6 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL"
Caption = 'Get Next Refund Lines';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL NextRefundLines";
}
- value(57; GetCurrentBulkOperation)
- {
- Caption = 'Get Current Bulk Operation';
- Implementation = "Shpfy IGraphQL" = "Shpfy GQL BulkOperations";
- }
value(58; RunBulkOperationMutation)
{
Caption = 'Run Bulk Operation Mutation';
diff --git a/src/Apps/W1/Shopify/App/src/Inventory/Codeunits/ShpfyInventoryAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Inventory/Codeunits/ShpfyInventoryAPI.Codeunit.al
index 69248f3162..da354bbffd 100644
--- a/src/Apps/W1/Shopify/App/src/Inventory/Codeunits/ShpfyInventoryAPI.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Inventory/Codeunits/ShpfyInventoryAPI.Codeunit.al
@@ -24,11 +24,6 @@ codeunit 30195 "Shpfy Inventory API"
JsonHelper: Codeunit "Shpfy Json Helper";
InventoryIds: List of [Guid];
- ///
- /// Get Stock.
- ///
- /// Parameter of type Record "Shopify Shop Inventory".
- /// Return variable "Stock" of type Decimal.
internal procedure GetStock(ShopInventory: Record "Shpfy Shop Inventory") Stock: Decimal
var
Item: Record Item;
@@ -78,11 +73,6 @@ codeunit 30195 "Shpfy Inventory API"
end;
end;
- ///
- /// Get Id.
- ///
- /// Parameter of type JsonObject.
- /// Return variable "Result" of type BigInteger.
local procedure GetId(JObject: JsonObject) Result: BigInteger
var
JValue: JsonValue;
@@ -97,12 +87,6 @@ codeunit 30195 "Shpfy Inventory API"
end;
end;
- ///
- /// Get Inventory Levels.
- ///
- /// Parameter of type JsonObject.
- /// Parameter of type JsonObject.
- /// Return value of type Boolean.
local procedure GetInventoryLevels(JObject: JsonObject; var JResult: JsonObject): Boolean;
var
JData: JsonObject;
@@ -141,44 +125,96 @@ codeunit 30195 "Shpfy Inventory API"
var
IGraphQL: Interface "Shpfy IGraphQL";
JGraphQL: JsonObject;
- JSetQuantities: JsonArray;
- JSetQuantity: JsonObject;
+ JQuantities: JsonArray;
+ JQuantity: JsonObject;
InputSize: Integer;
begin
if ShopInventory.FindSet() then begin
IGraphQL := Enum::"Shpfy GraphQL Type"::ModifyInventory;
JGraphQL.ReadFrom(IGraphQL.GetGraphQL());
- JSetQuantities := JsonHelper.GetJsonArray(JGraphQL, 'variables.input.setQuantities');
+ JQuantities := JsonHelper.GetJsonArray(JGraphQL, 'variables.input.quantities');
repeat
- JSetQuantity := CalcStock(ShopInventory);
- if JSetQuantity.Keys.Count = 3 then begin
- JSetQuantities.Add(JSetQuantity);
+ JQuantity := CalcStock(ShopInventory);
+ if JQuantity.Keys.Count = 4 then begin
+ JQuantities.Add(JQuantity);
InputSize += 1;
if InputSize = 250 then begin
- ShopifyCommunicationMgt.ExecuteGraphQL(Format(JGraphQL), IGraphQL.GetExpectedCost());
+ ExecuteInventoryGraphQL(JGraphQL, IGraphQL.GetExpectedCost());
Clear(JGraphQL);
JGraphQL.ReadFrom(IGraphQL.GetGraphQL());
- JSetQuantities := JsonHelper.GetJsonArray(JGraphQL, 'variables.input.setQuantities');
+ JQuantities := JsonHelper.GetJsonArray(JGraphQL, 'variables.input.quantities');
InputSize := 0;
end;
end;
until ShopInventory.Next() = 0;
- ShopifyCommunicationMgt.ExecuteGraphQL(Format(JGraphQL), IGraphQL.GetExpectedCost());
+ if InputSize > 0 then
+ ExecuteInventoryGraphQL(JGraphQL, IGraphQL.GetExpectedCost());
end;
end;
- local procedure CalcStock(var ShopInventory: Record "Shpfy Shop Inventory") JSetQuantity: JsonObject
+ local procedure ExecuteInventoryGraphQL(JGraphQL: JsonObject; ExpectedCost: Integer)
+ var
+ JResponse: JsonToken;
+ JUserErrors: JsonArray;
+ GraphQLText: Text;
+ IdempotencyKey: Guid;
+ RetryAttempt: Integer;
+ MaxRetries: Integer;
+ HasConcurrencyError: Boolean;
+ ErrorCode: Text;
+ JError: JsonToken;
+ begin
+ MaxRetries := 3;
+ RetryAttempt := 0;
+
+ repeat
+ HasConcurrencyError := false;
+ IdempotencyKey := CreateGuid();
+ GraphQLText := Format(JGraphQL);
+ GraphQLText := GraphQLText.Replace('{{IdempotencyKey}}', Format(IdempotencyKey, 0, 4).TrimStart('{').TrimEnd('}'));
+
+ JResponse := ShopifyCommunicationMgt.ExecuteGraphQL(GraphQLText, ExpectedCost);
+
+ if JsonHelper.GetJsonArray(JResponse, JUserErrors, 'data.inventorySetQuantities.userErrors') then
+ foreach JError in JUserErrors do begin
+ ErrorCode := JsonHelper.GetValueAsText(JError, 'code');
+ if ErrorCode in ['IDEMPOTENCY_CONCURRENT_REQUEST', 'CHANGE_FROM_QUANTITY_STALE'] then begin
+ HasConcurrencyError := true;
+ break;
+ end;
+ end;
+
+ RetryAttempt += 1;
+ until (not HasConcurrencyError) or (RetryAttempt > MaxRetries);
+
+ if HasConcurrencyError then
+ LogSkippedInventoryUpdate(JGraphQL, ErrorCode);
+ end;
+
+ local procedure LogSkippedInventoryUpdate(JGraphQL: JsonObject; ErrorCode: Text)
+ var
+ SkippedRecord: Codeunit "Shpfy Skipped Record";
+ EmptyRecordId: RecordId;
+ JQuantities: JsonArray;
+ SkippedMsg: Label 'Inventory update skipped after retry due to %1 error', Comment = '%1 = Error code';
+ begin
+ if JsonHelper.GetJsonArray(JGraphQL, JQuantities, 'variables.input.quantities') then
+ if JQuantities.Count > 0 then
+ SkippedRecord.LogSkippedRecord(EmptyRecordId, CopyStr(StrSubstNo(SkippedMsg, ErrorCode), 1, 250), ShopifyShop);
+ end;
+
+ local procedure CalcStock(var ShopInventory: Record "Shpfy Shop Inventory") JQuantity: JsonObject
var
Item: Record Item;
DelShopInventory: Record "Shpfy Shop Inventory";
ShopLocation: Record "Shpfy Shop Location";
ShopifyVariant: Record "Shpfy Variant";
IStockAvailable: Interface "Shpfy IStock Available";
+ JNull: JsonValue;
InventoryItemIdTxt: Label 'gid://shopify/InventoryItem/%1', Locked = true, Comment = '%1 = The inventory Item Id';
LocationIdTxt: Label 'gid://shopify/Location/%1', Locked = true, Comment = '%1 = The Location Id';
-
begin
ShopifyVariant.SetRange(Id, ShopInventory."Variant Id");
if ShopifyVariant.IsEmpty then begin
@@ -196,22 +232,19 @@ codeunit 30195 "Shpfy Inventory API"
if ShopLocation.Get(ShopInventory."Shop Code", ShopInventory."Location Id") then begin
IStockAvailable := ShopLocation."Stock Calculation";
if IStockAvailable.CanHaveStock() then begin
- JSetQuantity.Add('inventoryItemId', StrSubstNo(InventoryItemIdTxt, ShopInventory."Inventory Item Id"));
- JSetQuantity.Add('locationId', StrSubstNo(LocationIdTxt, ShopLocation.Id));
+ JQuantity.Add('inventoryItemId', StrSubstNo(InventoryItemIdTxt, ShopInventory."Inventory Item Id"));
+ JQuantity.Add('locationId', StrSubstNo(LocationIdTxt, ShopLocation.Id));
if ShopInventory.Stock < 0 then
- JSetQuantity.Add('quantity', 0)
+ JQuantity.Add('quantity', 0)
else
- JSetQuantity.Add('quantity', ShopInventory.Stock);
+ JQuantity.Add('quantity', ShopInventory.Stock);
+ JNull.SetValueToNull();
+ JQuantity.Add('changeFromQuantity', JNull);
end;
end;
end;
end;
- ///
- /// Has Next Results.
- ///
- /// Parameter of type JsonObject.
- /// Return value of type Boolean.
local procedure HasNextResults(JObject: JsonObject): Boolean
var
JPageInfo: JsonObject;
@@ -293,10 +326,6 @@ codeunit 30195 "Shpfy Inventory API"
end;
end;
- ///
- /// Import Stock.
- ///
- /// Parameter of type Record "Shopify Shop Location".
internal procedure ImportStock(ShopLocation: Record "Shpfy Shop Location")
var
Parameters: Dictionary of [Text, Text];
@@ -313,10 +342,6 @@ codeunit 30195 "Shpfy Inventory API"
until not HasNextResults(JInventoryLevels);
end;
- ///
- /// Set Shop.
- ///
- /// Parameter of type Code[20].
internal procedure SetShop(ShopCode: Code[20])
begin
if ShopifyShop.Code <> ShopCode then begin
diff --git a/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeArticleRef.Codeunit.al b/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeArticleRef.Codeunit.al
new file mode 100644
index 0000000000..2c769bb89a
--- /dev/null
+++ b/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/IMetafieldType/ShpfyMtfldTypeArticleRef.Codeunit.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Integration.Shopify;
+
+using System.Utilities;
+
+codeunit 30457 "Shpfy Mtfld Type Article Ref" implements "Shpfy IMetafield Type"
+{
+ procedure HasAssistEdit(): Boolean
+ begin
+ exit(false);
+ end;
+
+ procedure IsValidValue(Value: Text): Boolean
+ var
+ Regex: Codeunit Regex;
+ begin
+ exit(Regex.IsMatch(Value, '^gid:\/\/shopify\/Article\/\d+$'));
+ end;
+
+ procedure AssistEdit(var Value: Text[2048]): Boolean
+ begin
+ Value := Value;
+ exit(false);
+ end;
+
+ procedure GetExampleValue(): Text
+ begin
+ exit('gid://shopify/Article/1234567890');
+ end;
+}
diff --git a/src/Apps/W1/Shopify/App/src/Metafields/Enums/ShpfyMetafieldType.Enum.al b/src/Apps/W1/Shopify/App/src/Metafields/Enums/ShpfyMetafieldType.Enum.al
index 46856e0619..77dd4b1fde 100644
--- a/src/Apps/W1/Shopify/App/src/Metafields/Enums/ShpfyMetafieldType.Enum.al
+++ b/src/Apps/W1/Shopify/App/src/Metafields/Enums/ShpfyMetafieldType.Enum.al
@@ -172,4 +172,10 @@ enum 30159 "Shpfy Metafield Type" implements "Shpfy IMetafield Type"
Caption = 'Company';
Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Company Ref";
}
+
+ value(27; article_reference)
+ {
+ Caption = 'Article';
+ Implementation = "Shpfy IMetafield Type" = "Shpfy Mtfld Type Article Ref";
+ }
}
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/App/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al b/src/Apps/W1/Shopify/App/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al
index f4ee09ff69..20b14d631b 100644
--- a/src/Apps/W1/Shopify/App/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al
@@ -172,6 +172,7 @@ codeunit 30246 "Shpfy Create Sales Doc. Refund"
else
if RefundHeader."Return Id" > 0 then begin
ReturnLine.SetRange("Return Id", RefundHeader."Return Id");
+ ReturnLine.SetRange(Type, ReturnLine.Type::Default);
ReturnLine.SetAutoCalcFields("Item No.", "Variant Code", Description, "Unit of Measure Code");
if ReturnLine.FindSet(false) then
CreateSalesLinesFromReturnLines(ReturnLine, RefundHeader, SalesHeader, LineNo);
diff --git a/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnEnumConvertor.Codeunit.al b/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnEnumConvertor.Codeunit.al
index ac154ac6ed..c2ac41b1d6 100644
--- a/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnEnumConvertor.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnEnumConvertor.Codeunit.al
@@ -13,7 +13,6 @@ codeunit 30224 "Shpfy Return Enum Convertor"
var
ReturnDeclineReasons: Dictionary of [Text, Enum "Shpfy Return Decline Reason"];
ReturnStatuses: Dictionary of [Text, Enum "Shpfy Return Status"];
- ReturnReasons: Dictionary of [Text, Enum "Shpfy Return Reason"];
#region "Shpfy Return Decline Reason"
local procedure FillInReturnDeclineReasons()
@@ -37,11 +36,6 @@ codeunit 30224 "Shpfy Return Enum Convertor"
if ReturnDeclineReasons.ContainsKey(Value) then
exit(ReturnDeclineReasons.Get(Value));
end;
-
- internal procedure ConvertToText(Value: Enum "Shpfy Return Decline Reason"): text
- begin
- exit(Value.Names.Get(Value.Ordinals().IndexOf(Value.AsInteger())).Trim().ToUpper().Replace(' ', '_'));
- end;
#endregion "Shpfy Return Decline Reason"
#region "Shpfy Return Status"
@@ -66,39 +60,5 @@ codeunit 30224 "Shpfy Return Enum Convertor"
if ReturnStatuses.ContainsKey(Value) then
exit(ReturnStatuses.Get(Value));
end;
-
- internal procedure ConvertToText(Value: Enum "Shpfy Return Status"): text
- begin
- exit(Value.Names.Get(Value.Ordinals().IndexOf(Value.AsInteger())).Trim().ToUpper().Replace(' ', '_'));
- end;
#endregion "Shpfy Return Status"
-
- #region "Shpfy Return Reason"
- local procedure FillInReturnReasons()
- var
- EnumValues: List of [Integer];
- EnumNames: List of [Text];
- Index: Integer;
- begin
- if ReturnReasons.Count > 0 then
- exit;
-
- EnumValues := Enum::"Shpfy Return Reason".Ordinals();
- EnumNames := Enum::"Shpfy Return Reason".Names();
- for Index := 1 to EnumValues.Count do
- ReturnReasons.Add(EnumNames.Get(Index).Trim().ToUpper().Replace(' ', '_'), Enum::"Shpfy Return Reason".FromInteger(EnumValues.Get(Index)));
- end;
-
- internal procedure ConvertToReturnReason(Value: Text): Enum "Shpfy Return Reason"
- begin
- FillInReturnReasons();
- if ReturnReasons.ContainsKey(Value) then
- exit(ReturnReasons.Get(Value));
- end;
-
- internal procedure ConvertToText(Value: Enum "Shpfy Return Reason"): text
- begin
- exit(Value.Names.Get(Value.Ordinals().IndexOf(Value.AsInteger())).Trim().ToUpper().Replace(' ', '_'));
- end;
- #endregion "Shpfy Return Decline Reason"
}
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al
index 59062881b2..f7a55b1dd8 100644
--- a/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al
@@ -57,8 +57,12 @@ codeunit 30250 "Shpfy Returns API"
LineParameters.Set('After', JsonHelper.GetValueAsText(JResponse, 'data.return.returnLineItems.pageInfo.endCursor'))
else
LineParameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.return.returnLineItems.pageInfo.endCursor'));
- foreach JLine in JLines do
- FillInReturnLine(ReturnId, JLine.AsObject(), ReturnLocations);
+ foreach JLine in JLines do begin
+ if JsonHelper.GetValueAsText(JLine, '__typename') = 'ReturnLineItem' then
+ FillInReturnLine(ReturnId, JLine.AsObject(), ReturnLocations);
+ if JsonHelper.GetValueAsText(JLine, '__typename') = 'UnverifiedReturnLineItem' then
+ FillInUnverifiedReturnLine(ReturnId, JLine.AsObject());
+ end;
until not JsonHelper.GetValueAsBoolean(JResponse, 'data.return.returnLineItems.pageInfo.hasNextPage');
end;
@@ -115,10 +119,10 @@ codeunit 30250 "Shpfy Returns API"
GraphQLType := "Shpfy GraphQL Type"::GetNextReverseFulfillmentOrders;
JOrders := JsonHelper.GetJsonArray(JResponse, 'data.return.reverseFulfillmentOrders.nodes');
- if Parameters.ContainsKey('After') then
- Parameters.Set('After', JsonHelper.GetValueAsText(JResponse, 'data.return.reverseFulfillmentOrders.pageInfo.endCursor'))
+ if LineParameters.ContainsKey('After') then
+ LineParameters.Set('After', JsonHelper.GetValueAsText(JResponse, 'data.return.reverseFulfillmentOrders.pageInfo.endCursor'))
else
- Parameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.return.reverseFulfillmentOrders.pageInfo.endCursor'));
+ LineParameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.return.reverseFulfillmentOrders.pageInfo.endCursor'));
foreach JOrder in JOrders do
GetReturnLocationsFromReturnFulfillOrder(JsonHelper.GetValueAsText(JOrder, 'id'), ReturnLocations);
@@ -140,10 +144,10 @@ codeunit 30250 "Shpfy Returns API"
GraphQLType := "Shpfy GraphQL Type"::GetNextReverseFulfillmentOrders;
JLines := JsonHelper.GetJsonArray(JResponse, 'data.reverseFulfillmentOrder.lineItems.nodes');
- if Parameters.ContainsKey('After') then
- Parameters.Set('After', JsonHelper.GetValueAsText(JResponse, 'data.reverseFulfillmentOrder.lineItems.pageInfo.endCursor'))
+ if LineParameters.ContainsKey('After') then
+ LineParameters.Set('After', JsonHelper.GetValueAsText(JResponse, 'data.reverseFulfillmentOrder.lineItems.pageInfo.endCursor'))
else
- Parameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.reverseFulfillmentOrder.lineItems.pageInfo.endCursor'));
+ LineParameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.reverseFulfillmentOrder.lineItems.pageInfo.endCursor'));
foreach JLine in JLines do
CollectLocationsFromLineDispositions(JLine, ReturnLocations);
@@ -163,7 +167,7 @@ codeunit 30250 "Shpfy Returns API"
if Dispositions.Count = 0 then
exit;
- // If dispositions have different locations (Item was restocked to multiple locations),
+ // If dispositions have different locations (Item was restocked to multiple locations),
// we cannot determine the return location for the line
Dispositions.Get(0, Disposition);
LocationId := JsonHelper.GetValueAsBigInteger(Disposition, 'location.legacyResourceId');
@@ -195,11 +199,13 @@ codeunit 30250 "Shpfy Returns API"
if not ReturnLine.Get(Id) then begin
ReturnLine."Return Line Id" := Id;
ReturnLine."Return Id" := ReturnId;
+ ReturnLine.Type := ReturnLine.Type::Default;
ReturnLine."Fulfillment Line Id" := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JLine, 'fulfillmentLineItem.id'));
ReturnLine."Order Line Id" := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JLine, 'fulfillmentLineItem.lineItem.id'));
ReturnLine.Insert();
end;
- ReturnLine."Return Reason" := ReturnEnumConvertor.ConvertToReturnReason(JsonHelper.GetValueAsText(JLine, 'returnReason'));
+ ReturnLine."Return Reason Name" := CopyStr(JsonHelper.GetValueAsText(JLine, 'returnReasonDefinition.name'), 1, MaxStrLen(ReturnLine."Return Reason Name"));
+ ReturnLine."Return Reason Handle" := CopyStr(JsonHelper.GetValueAsText(JLine, 'returnReasonDefinition.handle'), 1, MaxStrLen(ReturnLine."Return Reason Handle"));
// If item was restocked to multiple locations, we cannot determine the return location for the line
if ReturnLocations.Get(ReturnLine."Order Line Id", ReturnLocation) then
ReturnLine."Location Id" := ReturnLocation;
@@ -219,4 +225,34 @@ codeunit 30250 "Shpfy Returns API"
ReturnLineRecordRef.Close();
DataCapture.Add(Database::"Shpfy Return Line", ReturnLine.SystemId, JLine);
end;
+
+ local procedure FillInUnverifiedReturnLine(ReturnId: BigInteger; JLine: JsonObject)
+ var
+ DataCapture: Record "Shpfy Data Capture";
+ ReturnLine: Record "Shpfy Return Line";
+ ReturnLineRecordRef: RecordRef;
+ Id: BigInteger;
+ begin
+ Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JLine, 'id'));
+ if not ReturnLine.Get(Id) then begin
+ ReturnLine."Return Line Id" := Id;
+ ReturnLine."Return Id" := ReturnId;
+ ReturnLine.Type := ReturnLine.Type::Unverified;
+ ReturnLine.Insert();
+ end;
+ ReturnLine."Return Reason Name" := CopyStr(JsonHelper.GetValueAsText(JLine, 'returnReasonDefinition.name'), 1, MaxStrLen(ReturnLine."Return Reason Name"));
+ ReturnLine."Return Reason Handle" := CopyStr(JsonHelper.GetValueAsText(JLine, 'returnReasonDefinition.handle'), 1, MaxStrLen(ReturnLine."Return Reason Handle"));
+ ReturnLine.SetReturnReasonNote(JsonHelper.GetValueAsText(JLine, 'returnReasonNote'));
+ ReturnLine.SetCustomerNote(JsonHelper.GetValueAsText(JLine, 'customerNote'));
+
+ ReturnLineRecordRef.GetTable(ReturnLine);
+ JsonHelper.GetValueIntoField(JLine, 'quantity', ReturnLineRecordRef, ReturnLine.FieldNo(Quantity));
+ JsonHelper.GetValueIntoField(JLine, 'refundableQuantity', ReturnLineRecordRef, ReturnLine.FieldNo("Refundable Quantity"));
+ JsonHelper.GetValueIntoField(JLine, 'refundedQuantity', ReturnLineRecordRef, ReturnLine.FieldNo("Refunded Quantity"));
+ JsonHelper.GetValueIntoField(JLine, 'unitPrice.amount', ReturnLineRecordRef, ReturnLine.FieldNo("Unit Price"));
+ JsonHelper.GetValueIntoField(JLine, 'unitPrice.currency', ReturnLineRecordRef, ReturnLine.FieldNo("Unit Price Currency"));
+ ReturnLineRecordRef.Modify();
+ ReturnLineRecordRef.Close();
+ DataCapture.Add(Database::"Shpfy Return Line", ReturnLine.SystemId, JLine);
+ end;
}
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnLineType.Enum.al b/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnLineType.Enum.al
new file mode 100644
index 0000000000..cfaa343332
--- /dev/null
+++ b/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnLineType.Enum.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Integration.Shopify;
+
+enum 30170 "Shpfy Return Line Type"
+{
+ value(0; Default)
+ {
+ Caption = 'Default';
+ }
+ value(1; Unverified)
+ {
+ Caption = 'Unverified';
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnReason.Enum.al b/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnReason.Enum.al
index 6ccd9af01d..43411cf2c9 100644
--- a/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnReason.Enum.al
+++ b/src/Apps/W1/Shopify/App/src/Order Returns/Enums/ShpfyReturnReason.Enum.al
@@ -7,7 +7,6 @@ namespace Microsoft.Integration.Shopify;
enum 30138 "Shpfy Return Reason"
{
-
value(0; " ")
{
Caption = ' ';
diff --git a/src/Apps/W1/Shopify/App/src/Order Returns/Pages/ShpfyReturnLines.Page.al b/src/Apps/W1/Shopify/App/src/Order Returns/Pages/ShpfyReturnLines.Page.al
index f4dd4fd96a..60bd3e9424 100644
--- a/src/Apps/W1/Shopify/App/src/Order Returns/Pages/ShpfyReturnLines.Page.al
+++ b/src/Apps/W1/Shopify/App/src/Order Returns/Pages/ShpfyReturnLines.Page.al
@@ -17,6 +17,11 @@ page 30149 "Shpfy Return Lines"
{
repeater(General)
{
+ field(Type; Rec.Type)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the type of return line.';
+ }
field("Item No."; Rec."Item No.")
{
ApplicationArea = All;
@@ -38,6 +43,12 @@ page 30149 "Shpfy Return Lines"
ToolTip = 'Specifies the quantity being returned.';
}
field("Return Reason"; Rec."Return Reason")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the reason for returning the item.';
+ Visible = false;
+ }
+ field("Return Reason Name"; Rec."Return Reason Name")
{
ApplicationArea = All;
ToolTip = 'Specifies the reason for returning the item.';
@@ -67,6 +78,11 @@ page 30149 "Shpfy Return Lines"
ApplicationArea = All;
ToolTip = 'Specifies the unit of measurement.';
}
+ field("Unit Price"; Rec."Unit Price")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the price of a single unit of the item.';
+ }
}
group(ReturnReason)
{
diff --git a/src/Apps/W1/Shopify/App/src/Order Returns/Tables/ShpfyReturnLine.Table.al b/src/Apps/W1/Shopify/App/src/Order Returns/Tables/ShpfyReturnLine.Table.al
index 95a2f5861e..66f5e1142c 100644
--- a/src/Apps/W1/Shopify/App/src/Order Returns/Tables/ShpfyReturnLine.Table.al
+++ b/src/Apps/W1/Shopify/App/src/Order Returns/Tables/ShpfyReturnLine.Table.al
@@ -49,7 +49,7 @@ table 30141 "Shpfy Return Line"
}
field(6; "Return Reason"; Enum "Shpfy Return Reason")
{
- Caption = 'Return Reason';
+ Caption = 'Return Reason (deprecated)';
DataClassification = SystemMetadata;
Editable = false;
}
@@ -104,6 +104,38 @@ table 30141 "Shpfy Return Line"
Caption = 'Customer Note';
DataClassification = SystemMetadata;
}
+ field(15; Type; Enum "Shpfy Return Line Type")
+ {
+ Caption = 'Type';
+ DataClassification = SystemMetadata;
+ Editable = false;
+ }
+ field(16; "Unit Price"; Decimal)
+ {
+ Caption = 'Unit Price';
+ DataClassification = SystemMetadata;
+ Editable = false;
+ AutoFormatType = 1;
+ AutoFormatExpression = "Unit Price Currency";
+ }
+ field(17; "Unit Price Currency"; Code[10])
+ {
+ Caption = 'Unit Price Currency';
+ DataClassification = SystemMetadata;
+ Editable = false;
+ }
+ field(18; "Return Reason Name"; Text[100])
+ {
+ Caption = 'Return Reason';
+ DataClassification = SystemMetadata;
+ Editable = false;
+ }
+ field(19; "Return Reason Handle"; Text[100])
+ {
+ Caption = 'Return Reason Handle';
+ DataClassification = SystemMetadata;
+ Editable = false;
+ }
field(101; "Item No."; Code[20])
{
Caption = 'Item No.';
diff --git a/src/Apps/W1/Shopify/App/src/Payments/Codeunits/ShpfyPaymentsAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Payments/Codeunits/ShpfyPaymentsAPI.Codeunit.al
index 8991768583..8b19c46edc 100644
--- a/src/Apps/W1/Shopify/App/src/Payments/Codeunits/ShpfyPaymentsAPI.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Payments/Codeunits/ShpfyPaymentsAPI.Codeunit.al
@@ -143,6 +143,7 @@ codeunit 30385 "Shpfy Payments API"
JsonHelper.GetValueIntoField(JPayout, 'summary.reservedFundsGross.amount', RecordRef, Payout.FieldNo("Reserved Funds Gross Amount"));
JsonHelper.GetValueIntoField(JPayout, 'summary.retriedPayoutsFee.amount', RecordRef, Payout.FieldNo("Retried Payouts Fee Amount"));
JsonHelper.GetValueIntoField(JPayout, 'summary.retriedPayoutsGross.amount', RecordRef, Payout.FieldNo("Retried Payouts Gross Amount"));
+ JsonHelper.GetValueIntoField(JPayout, 'externalTraceId', RecordRef, Payout.FieldNo("External Trace Id"));
RecordRef.SetTable(Payout);
RecordRef.Close();
Payout.Id := Id;
diff --git a/src/Apps/W1/Shopify/App/src/Payments/Tables/ShpfyPayout.Table.al b/src/Apps/W1/Shopify/App/src/Payments/Tables/ShpfyPayout.Table.al
index 1551727ea9..9fc1f0b9af 100644
--- a/src/Apps/W1/Shopify/App/src/Payments/Tables/ShpfyPayout.Table.al
+++ b/src/Apps/W1/Shopify/App/src/Payments/Tables/ShpfyPayout.Table.al
@@ -112,6 +112,11 @@ table 30125 "Shpfy Payout"
AutoFormatType = 1;
AutoFormatExpression = Currency;
}
+ field(16; "External Trace Id"; Text[250])
+ {
+ Caption = 'External Trace Id';
+ DataClassification = CustomerContent;
+ }
field(101; "Shop Code"; Code[20])
{
Caption = 'Shop Code';
diff --git a/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al b/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al
index d64ce82b40..bea2a0c626 100644
--- a/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al
+++ b/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al
@@ -155,7 +155,6 @@ permissionset 30104 "Shpfy - Objects"
codeunit "Shpfy GQL ApiKey" = X,
codeunit "Shpfy GQL AssignedFFOrders" = X,
codeunit "Shpfy GQL BulkOperation" = X,
- codeunit "Shpfy GQL BulkOperations" = X,
codeunit "Shpfy GQL BulkOpMutation" = X,
codeunit "Shpfy GQL Catalog Markets" = X,
codeunit "Shpfy GQL CatalogPrices" = X,
diff --git a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyCreateProduct.Codeunit.al b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyCreateProduct.Codeunit.al
index f690c24b19..5b1c372562 100644
--- a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyCreateProduct.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyCreateProduct.Codeunit.al
@@ -101,7 +101,6 @@ codeunit 30174 "Shpfy Create Product"
TempShopifyVariant.Title := ItemVariant.Description;
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
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 := ItemUnitofMeasure."Qty. per Unit of Measure" > 0 ? Item."Gross Weight" * ItemUnitofMeasure."Qty. per Unit of Measure" : Item."Gross Weight";
TempShopifyVariant."Option 1 Name" := 'Variant';
@@ -124,7 +123,6 @@ codeunit 30174 "Shpfy Create Product"
TempShopifyVariant.Title := ItemVariant.Description;
TempShopifyVariant."Inventory Policy" := Shop."Default Inventory Policy";
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";
TempShopifyVariant."Option 1 Name" := 'Variant';
@@ -151,7 +149,6 @@ codeunit 30174 "Shpfy Create Product"
TempShopifyVariant.Title := Item.Description;
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 := ItemUnitofMeasure."Qty. per Unit of Measure" > 0 ? Item."Gross Weight" * ItemUnitofMeasure."Qty. per Unit of Measure" : Item."Gross Weight";
TempShopifyVariant."Option 1 Name" := Shop."Option Name for UoM";
@@ -232,7 +229,6 @@ codeunit 30174 "Shpfy Create Product"
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;
diff --git a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al
index dfa49a87fa..54ef9deaab 100644
--- a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al
@@ -359,7 +359,6 @@ codeunit 30178 "Shpfy Product Export"
Shop."SKU Mapping"::"Vendor Item No.":
ShopifyVariant.SKU := Item."Vendor Item No.";
end;
- ShopifyVariant."Tax Code" := Item."Tax Group Code";
ShopifyVariant.Taxable := true;
ShopifyVariant.Weight := ItemUnitofMeasure."Qty. per Unit of Measure" > 0 ? Item."Gross Weight" * ItemUnitofMeasure."Qty. per Unit of Measure" : Item."Gross Weight";
ShopifyVariant."Option 1 Name" := Shop."Option Name for UoM";
@@ -416,7 +415,6 @@ codeunit 30178 "Shpfy Product Export"
Shop."SKU Mapping"::"Vendor Item No.":
ShopifyVariant.SKU := Item."Vendor Item No.";
end;
- ShopifyVariant."Tax Code" := Item."Tax Group Code";
ShopifyVariant.Taxable := true;
ShopifyVariant.Weight := Item."Gross Weight";
if ShopifyVariant."Option 1 Name" = '' then
@@ -470,7 +468,6 @@ codeunit 30178 "Shpfy Product Export"
Shop."SKU Mapping"::"Vendor Item No.":
ShopifyVariant.SKU := Item."Vendor Item No.";
end;
- ShopifyVariant."Tax Code" := Item."Tax Group Code";
ShopifyVariant.Taxable := true;
ShopifyVariant.Weight := ItemUnitofMeasure."Qty. per Unit of Measure" > 0 ? Item."Gross Weight" * ItemUnitofMeasure."Qty. per Unit of Measure" : Item."Gross Weight";
ShopifyVariant."Option 1 Name" := 'Variant';
diff --git a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al
index 0b51046ec7..280984bd90 100644
--- a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al
+++ b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyVariantAPI.Codeunit.al
@@ -97,11 +97,15 @@ codeunit 30189 "Shpfy Variant API"
JResponse: JsonToken;
JVariants: JsonArray;
ReturnQuery: Text;
+ LocationCount: Integer;
+ InventoryQuantitiesCount: Integer;
begin
ReturnQuery := ']) {productVariants {legacyResourceId, createdAt, updatedAt}, userErrors {field, message}}}"}';
if ShopifyVariant.FindSet() then begin
InventoryQuantities := GetInventoryQuantities();
+ LocationCount := GetLocationCount();
+ InventoryQuantitiesCount := 0;
GraphQuery.Append('{"query":"mutation { productVariantsBulkCreate(productId: \"gid://shopify/Product/');
GraphQuery.Append(Format(ProductId));
GraphQuery.Append('\", strategy: ');
@@ -110,10 +114,12 @@ codeunit 30189 "Shpfy Variant API"
repeat
ShopifyVariant."Product Id" := ProductId;
VariantGraphQuery := GetVariantGraphQuery(ShopifyVariant, InventoryQuantities);
- if GraphQuery.Length() + VariantGraphQuery.Length() + StrLen(ReturnQuery) < CommunicationMgt.GetGraphQueryLengthThreshold() then begin
+ if (GraphQuery.Length() + VariantGraphQuery.Length() + StrLen(ReturnQuery) < CommunicationMgt.GetGraphQueryLengthThreshold()) and
+ (InventoryQuantitiesCount + LocationCount <= GetInventoryQuantitiesLimit()) then begin
GraphQuery.Append(VariantGraphQuery.ToText() + ', ');
TempNewShopifyVariant := ShopifyVariant;
TempNewShopifyVariant.Insert();
+ InventoryQuantitiesCount += LocationCount;
end else begin
GraphQuery.Remove(GraphQuery.Length - 1, 2);
GraphQuery.Append(ReturnQuery);
@@ -131,6 +137,7 @@ codeunit 30189 "Shpfy Variant API"
GraphQuery.Append(Format(Strategy));
GraphQuery.Append(', variants: [');
GraphQuery.Append(VariantGraphQuery.ToText() + ', ');
+ InventoryQuantitiesCount := LocationCount;
end;
until ShopifyVariant.Next() = 0;
GraphQuery.Remove(GraphQuery.Length - 1, 2);
@@ -164,12 +171,6 @@ codeunit 30189 "Shpfy Variant API"
end;
if ShopifyVariant.Taxable then
GraphQuery.Append(', taxable: true');
- if ShopifyVariant."Tax Code" <> xShopifyVariant."Tax Code" then begin
- HasChange := true;
- GraphQuery.Append(', taxCode: \"');
- GraphQuery.Append(ShopifyVariant."Tax Code");
- GraphQuery.Append('\"');
- end;
if ShopifyVariant.Price <> xShopifyVariant.Price then begin
HasChange := true;
GraphQuery.Append(', price: \"');
@@ -234,11 +235,6 @@ codeunit 30189 "Shpfy Variant API"
end;
if ShopifyVariant.Taxable then
GraphQuery.Append(', taxable: true');
- if ShopifyVariant."Tax Code" <> '' then begin
- GraphQuery.Append(', taxCode: \"');
- GraphQuery.Append(ShopifyVariant."Tax Code");
- GraphQuery.Append('\"');
- end;
if ShopifyVariant.Price > 0 then begin
GraphQuery.Append(', price: \"');
GraphQuery.Append(Format(ShopifyVariant.Price, 0, 9));
@@ -321,6 +317,21 @@ codeunit 30189 "Shpfy Variant API"
exit(GraphQuery.ToText());
end;
+ local procedure GetLocationCount(): Integer
+ var
+ ShopLocation: Record "Shpfy Shop Location";
+ begin
+ ShopLocation.SetRange("Shop Code", Shop.Code);
+ ShopLocation.SetRange(Active, true);
+ ShopLocation.SetRange("Default Product Location", true);
+ exit(ShopLocation.Count());
+ end;
+
+ local procedure GetInventoryQuantitiesLimit(): Integer
+ begin
+ exit(50000);
+ end;
+
local procedure CreateNewVariant(JVariant: JsonToken; var ShopifyVariant: Record "Shpfy Variant"; ProductId: BigInteger): Boolean
var
NewShopifyVariant: Record "Shpfy Variant";
@@ -730,7 +741,6 @@ codeunit 30189 "Shpfy Variant API"
#pragma warning disable AA0139
ShopifyVariant.Barcode := JsonHelper.GetValueAsText(JVariant, 'barcode', MaxStrLen(ShopifyVariant.Barcode));
ShopifyVariant."Display Name" := JsonHelper.GetValueAsText(JVariant, 'displayName', MaxStrLen(ShopifyVariant."Display Name"));
- ShopifyVariant."Tax Code" := JsonHelper.GetValueAsText(JVariant, 'taxCode', MaxStrLen(ShopifyVariant."Tax Code"));
ShopifyVariant.SKU := JsonHelper.GetValueAsText(JVariant, 'sku', MaxStrLen(ShopifyVariant.SKU));
ShopifyVariant.Title := JsonHelper.GetValueAsText(JVariant, 'title', MaxStrLen(ShopifyVariant.Title));
#pragma warning restore AA0139
diff --git a/src/Apps/W1/Shopify/App/src/Products/Pages/ShpfyVariants.Page.al b/src/Apps/W1/Shopify/App/src/Products/Pages/ShpfyVariants.Page.al
index 32715fd204..be3b408cb0 100644
--- a/src/Apps/W1/Shopify/App/src/Products/Pages/ShpfyVariants.Page.al
+++ b/src/Apps/W1/Shopify/App/src/Products/Pages/ShpfyVariants.Page.al
@@ -144,11 +144,17 @@ page 30127 "Shpfy Variants"
ApplicationArea = All;
ToolTip = 'Specifies whether a tax is charged when the product variant is sold.';
}
+#if not CLEAN28
field(TaxCode; Rec."Tax Code")
{
ApplicationArea = All;
ToolTip = 'Specifies the Avalara tax code for the product variant. This parameter applies only to the stores that have the Avalara AvaTax app installed.';
+ Visible = false;
+ ObsoleteState = Pending;
+ ObsoleteReason = 'Shopify API 2025-10 deprecated taxCode on ProductVariant. This field is no longer available in the API.';
+ ObsoleteTag = '28.0';
}
+#endif
field(UnitCost; Rec."Unit Cost")
{
ApplicationArea = All;
diff --git a/src/Apps/W1/Shopify/App/src/Products/Tables/ShpfyVariant.Table.al b/src/Apps/W1/Shopify/App/src/Products/Tables/ShpfyVariant.Table.al
index 367d41ca53..9538e494cf 100644
--- a/src/Apps/W1/Shopify/App/src/Products/Tables/ShpfyVariant.Table.al
+++ b/src/Apps/W1/Shopify/App/src/Products/Tables/ShpfyVariant.Table.al
@@ -81,11 +81,21 @@ table 30129 "Shpfy Variant"
Caption = 'SKU';
DataClassification = CustomerContent;
}
+#if not CLEANSCHEMA31
field(13; "Tax Code"; Code[20])
{
Caption = 'Tax Code';
DataClassification = CustomerContent;
+ ObsoleteReason = 'Shopify API 2025-10 deprecated taxCode on ProductVariant. This field is no longer available in the API.';
+#if not CLEAN28
+ ObsoleteState = Pending;
+ ObsoleteTag = '28.0';
+#else
+ ObsoleteState = Removed;
+ ObsoleteTag = '31.0';
+#endif
}
+#endif
field(14; Taxable; Boolean)
{
Caption = 'Taxable';
diff --git a/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/BulkOperationCompletedResult.txt b/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/BulkOperationCompletedResult.txt
index e9121aaef4..e38c8a6501 100644
--- a/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/BulkOperationCompletedResult.txt
+++ b/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/BulkOperationCompletedResult.txt
@@ -1 +1 @@
-{ "data": { "node": { "status": "COMPLETED", "errorCode": null, "completedAt": "2021-01-28T19:11:09Z", "url": "", "partialDataUrl": null } }, "extensions": { "cost": { "requestedQueryCost": 1, "actualQueryCost": 1 } } }
\ No newline at end of file
+{ "data": { "node": { "status": "%1", "errorCode": null, "completedAt": "2021-01-28T19:11:09Z", "url": "", "partialDataUrl": null } }, "extensions": { "cost": { "requestedQueryCost": 1, "actualQueryCost": 1 } } }
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/CurrentBulkOperationCompletedResult.txt b/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/CurrentBulkOperationCompletedResult.txt
deleted file mode 100644
index a248d0ccc0..0000000000
--- a/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/CurrentBulkOperationCompletedResult.txt
+++ /dev/null
@@ -1 +0,0 @@
-{ "data": { "currentBulkOperation": { "id": "gid://shopify/BulkOperation/%1", "status": "COMPLETED", "errorCode": null, "createdAt": "2021-01-28T19:10:59Z", "completedAt": "2021-01-28T19:11:09Z", "objectCount": "16", "fileSize": "0", "url": "", "partialDataUrl": null } }, "extensions": { "cost": { "requestedQueryCost": 1, "actualQueryCost": 1 } } }
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/CurrentBulkOperationRunningResult.txt b/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/CurrentBulkOperationRunningResult.txt
deleted file mode 100644
index 9f42cae022..0000000000
--- a/src/Apps/W1/Shopify/Test/.resources/Bulk Operations/CurrentBulkOperationRunningResult.txt
+++ /dev/null
@@ -1 +0,0 @@
-{ "data": { "currentBulkOperation": { "id": "gid://shopify/BulkOperation/%1", "status": "RUNNING", "errorCode": null, "createdAt": "2021-01-28T19:10:59Z", "completedAt": "2021-01-28T19:11:09Z", "objectCount": "16", "fileSize": "0", "url": "", "partialDataUrl": null } }, "extensions": { "cost": { "requestedQueryCost": 1, "actualQueryCost": 1 } } }
\ No newline at end of file
diff --git a/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOpSubscriber.Codeunit.al b/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOpSubscriber.Codeunit.al
index ede01420a6..a9e95100f1 100644
--- a/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOpSubscriber.Codeunit.al
+++ b/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOpSubscriber.Codeunit.al
@@ -59,7 +59,6 @@ codeunit 139615 "Shpfy Bulk Op. Subscriber"
GraphQLQuery: Text;
StagedUploadGQLTxt: Label '{"query": "mutation { stagedUploadsCreate(input', Locked = true;
BulkMutationGQLTxt: Label '{"query": "mutation { bulkOperationRunMutation(mutation', Locked = true;
- CurrentBulkOperationGQLTxt: Label '{"query": "query { currentBulkOperation(type', Locked = true;
BulkOperationGQLTxt: Label '{"query": "query { node(id: \"gid://shopify/BulkOperation/', Locked = true;
GraphQLCmdTxt: Label '/graphql.json', Locked = true;
begin
@@ -73,13 +72,8 @@ codeunit 139615 "Shpfy Bulk Op. Subscriber"
HttpResponseMessage := GetStagedUplodResult();
if GraphQLQuery.StartsWith(BulkMutationGQLTxt) then
HttpResponseMessage := GetBulkMutationResponse();
- if GraphQLQuery.StartsWith(CurrentBulkOperationGQLTxt) then
- if BulkOperationRunning then
- HttpResponseMessage := GetCurrentBulkOperationRunningResult()
- else
- HttpResponseMessage := GetCurrentBulkOperationCompletedResult();
if GraphQLQuery.StartsWith(BulkOperationGQLTxt) then
- HttpResponseMessage := GetBulkOperationCompletedResult();
+ HttpResponseMessage := GetBulkOperation();
end;
end;
end;
@@ -115,30 +109,6 @@ codeunit 139615 "Shpfy Bulk Op. Subscriber"
exit(HttpResponseMessage);
end;
- local procedure GetCurrentBulkOperationCompletedResult(): HttpResponseMessage
- var
- HttpResponseMessage: HttpResponseMessage;
- Body: Text;
- ResInStream: InStream;
- begin
- NavApp.GetResource('Bulk Operations/CurrentBulkOperationCompletedResult.txt', ResInStream, TextEncoding::UTF8);
- ResInStream.ReadText(Body);
- HttpResponseMessage.Content.WriteFrom(StrSubstNo(Body, Format(BulkOperationId)));
- exit(HttpResponseMessage);
- end;
-
- local procedure GetCurrentBulkOperationRunningResult(): HttpResponseMessage
- var
- HttpResponseMessage: HttpResponseMessage;
- Body: Text;
- ResInStream: InStream;
- begin
- NavApp.GetResource('Bulk Operations/CurrentBulkOperationRunningResult.txt', ResInStream, TextEncoding::UTF8);
- ResInStream.ReadText(Body);
- HttpResponseMessage.Content.WriteFrom(StrSubstNo(Body, Format(BulkOperationId)));
- exit(HttpResponseMessage);
- end;
-
local procedure GetJsonlUploadResult(): HttpResponseMessage
var
HttpResponseMessage: HttpResponseMessage;
@@ -162,7 +132,7 @@ codeunit 139615 "Shpfy Bulk Op. Subscriber"
exit(HttpResponseMessage);
end;
- local procedure GetBulkOperationCompletedResult(): HttpResponseMessage
+ local procedure GetBulkOperation(): HttpResponseMessage
var
HttpResponseMessage: HttpResponseMessage;
Body: Text;
@@ -170,6 +140,10 @@ codeunit 139615 "Shpfy Bulk Op. Subscriber"
begin
NavApp.GetResource('Bulk Operations/BulkOperationCompletedResult.txt', ResInStream, TextEncoding::UTF8);
ResInStream.ReadText(Body);
+ if BulkOperationRunning then
+ Body := StrSubstNo(Body, 'RUNNING')
+ else
+ Body := StrSubstNo(Body, 'COMPLETED');
HttpResponseMessage.Content.WriteFrom(Body);
exit(HttpResponseMessage);
end;
diff --git a/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOperationsTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOperationsTest.Codeunit.al
index 4adbbd82b9..475a4be7b2 100644
--- a/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOperationsTest.Codeunit.al
+++ b/src/Apps/W1/Shopify/Test/Bulk Operations/Codeunits/ShpfyBulkOperationsTest.Codeunit.al
@@ -47,7 +47,7 @@ codeunit 139633 "Shpfy Bulk Operations Test"
ShopifyVariant: Record "Shpfy Variant";
begin
BulkOperation.DeleteAll();
- BulkOpSubscriber.SetBulkOperationRunning(false);
+ // BulkOpSubscriber.SetBulkOperationRunning(false);
BulkOpSubscriber.SetBulkUploadFail(false);
ShopifyVariant.DeleteAll();
end;
diff --git a/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventoryExportTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventoryExportTest.Codeunit.al
new file mode 100644
index 0000000000..da16f8e172
--- /dev/null
+++ b/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventoryExportTest.Codeunit.al
@@ -0,0 +1,370 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Integration.Shopify.Test;
+
+using Microsoft.Integration.Shopify;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using System.TestLibraries.Utilities;
+
+///
+/// Codeunit Shpfy Inventory Export Test (ID 139501).
+/// Tests for inventory export functionality including idempotency and retry logic.
+///
+codeunit 139594 "Shpfy Inventory Export Test"
+{
+ Subtype = Test;
+ TestType = IntegrationTest;
+ TestPermissions = Disabled;
+
+ var
+ Any: Codeunit Any;
+ LibraryAssert: Codeunit "Library Assert";
+ LibraryInventory: Codeunit "Library - Inventory";
+ IsInitialized: Boolean;
+ NextId: BigInteger;
+
+ local procedure Initialize()
+ begin
+ if IsInitialized then
+ exit;
+ IsInitialized := true;
+ Codeunit.Run(Codeunit::"Shpfy Initialize Test");
+ end;
+
+ [Test]
+ procedure UnitTestExportStockSuccess()
+ var
+ Shop: Record "Shpfy Shop";
+ ShopLocation: Record "Shpfy Shop Location";
+ Item: Record Item;
+ ShopifyProduct: Record "Shpfy Product";
+ ShopInventory: Record "Shpfy Shop Inventory";
+ InventorySubscriber: Codeunit "Shpfy Inventory Subscriber";
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ InventoryAPI: Codeunit "Shpfy Inventory API";
+ StockCalculate: Enum "Shpfy Stock Calculation";
+ begin
+ // [SCENARIO] Export stock successfully updates inventory in Shopify
+ // [GIVEN] A ShopInventory record with stock different from Shopify stock
+ Initialize();
+
+ Shop := CommunicationMgt.GetShopRecord();
+ CreateShopLocation(ShopLocation, Shop.Code, StockCalculate::"Projected Available Balance Today");
+ CreateItem(Item);
+ UpdateItemInventory(Item, 10);
+ CreateShopifyProduct(ShopifyProduct, ShopInventory, Item.SystemId, Shop.Code, ShopLocation.Id);
+ ShopInventory."Shopify Stock" := 5; // Different from calculated stock to trigger export
+ ShopInventory.Modify();
+
+ // [GIVEN] The inventory subscriber is configured to return success
+ BindSubscription(InventorySubscriber);
+ InventorySubscriber.SetRetryScenario(Enum::"Shpfy Inventory Retry Scenario"::Success);
+ InventoryAPI.SetShop(Shop.Code);
+
+ // [WHEN] ExportStock is called
+ ShopInventory.SetRange("Shop Code", Shop.Code);
+ ShopInventory.SetRange("Variant Id", ShopInventory."Variant Id");
+ InventoryAPI.ExportStock(ShopInventory);
+
+ // [THEN] The mutation was executed successfully (verified by subscriber not throwing error)
+ UnbindSubscription(InventorySubscriber);
+ end;
+
+ [Test]
+ procedure UnitTestExportStockRetryOnIdempotencyConcurrentRequest()
+ var
+ Shop: Record "Shpfy Shop";
+ ShopLocation: Record "Shpfy Shop Location";
+ Item: Record Item;
+ ShopifyProduct: Record "Shpfy Product";
+ ShopInventory: Record "Shpfy Shop Inventory";
+ InventorySubscriber: Codeunit "Shpfy Inventory Subscriber";
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ InventoryAPI: Codeunit "Shpfy Inventory API";
+ StockCalculate: Enum "Shpfy Stock Calculation";
+ begin
+ // [SCENARIO] Export stock retries on IDEMPOTENCY_CONCURRENT_REQUEST error
+ // [GIVEN] A ShopInventory record with stock different from Shopify stock
+ Initialize();
+
+ Shop := CommunicationMgt.GetShopRecord();
+ CreateShopLocation(ShopLocation, Shop.Code, StockCalculate::"Projected Available Balance Today");
+ CreateItem(Item);
+ UpdateItemInventory(Item, 15);
+ CreateShopifyProduct(ShopifyProduct, ShopInventory, Item.SystemId, Shop.Code, ShopLocation.Id);
+ ShopInventory."Shopify Stock" := 5;
+ ShopInventory.Modify();
+
+ // [GIVEN] The inventory subscriber is configured to fail once with IDEMPOTENCY_CONCURRENT_REQUEST then succeed
+ BindSubscription(InventorySubscriber);
+ InventorySubscriber.SetRetryScenario(Enum::"Shpfy Inventory Retry Scenario"::FailOnceThenSucceed);
+ InventorySubscriber.SetErrorCode('IDEMPOTENCY_CONCURRENT_REQUEST');
+ InventoryAPI.SetShop(Shop.Code);
+
+ // [WHEN] ExportStock is called
+ ShopInventory.SetRange("Shop Code", Shop.Code);
+ ShopInventory.SetRange("Variant Id", ShopInventory."Variant Id");
+ InventoryAPI.ExportStock(ShopInventory);
+
+ // [THEN] The mutation was retried and succeeded (2 calls total)
+ LibraryAssert.AreEqual(2, InventorySubscriber.GetCallCount(), 'Expected 2 GraphQL calls (1 failure + 1 retry success)');
+
+ UnbindSubscription(InventorySubscriber);
+ end;
+
+ [Test]
+ procedure UnitTestExportStockRetryOnChangeFromQuantityStale()
+ var
+ Shop: Record "Shpfy Shop";
+ ShopLocation: Record "Shpfy Shop Location";
+ Item: Record Item;
+ ShopifyProduct: Record "Shpfy Product";
+ ShopInventory: Record "Shpfy Shop Inventory";
+ InventorySubscriber: Codeunit "Shpfy Inventory Subscriber";
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ InventoryAPI: Codeunit "Shpfy Inventory API";
+ StockCalculate: Enum "Shpfy Stock Calculation";
+ begin
+ // [SCENARIO] Export stock retries on CHANGE_FROM_QUANTITY_STALE error
+ // [GIVEN] A ShopInventory record with stock different from Shopify stock
+ Initialize();
+
+ Shop := CommunicationMgt.GetShopRecord();
+ CreateShopLocation(ShopLocation, Shop.Code, StockCalculate::"Projected Available Balance Today");
+ CreateItem(Item);
+ UpdateItemInventory(Item, 20);
+ CreateShopifyProduct(ShopifyProduct, ShopInventory, Item.SystemId, Shop.Code, ShopLocation.Id);
+ ShopInventory."Shopify Stock" := 10;
+ ShopInventory.Modify();
+
+ // [GIVEN] The inventory subscriber is configured to fail once with CHANGE_FROM_QUANTITY_STALE then succeed
+ BindSubscription(InventorySubscriber);
+ InventorySubscriber.SetRetryScenario(Enum::"Shpfy Inventory Retry Scenario"::FailOnceThenSucceed);
+ InventorySubscriber.SetErrorCode('CHANGE_FROM_QUANTITY_STALE');
+ InventoryAPI.SetShop(Shop.Code);
+
+ // [WHEN] ExportStock is called
+ ShopInventory.SetRange("Shop Code", Shop.Code);
+ ShopInventory.SetRange("Variant Id", ShopInventory."Variant Id");
+ InventoryAPI.ExportStock(ShopInventory);
+
+ // [THEN] The mutation was retried and succeeded (2 calls total)
+ LibraryAssert.AreEqual(2, InventorySubscriber.GetCallCount(), 'Expected 2 GraphQL calls (1 failure + 1 retry success)');
+
+ UnbindSubscription(InventorySubscriber);
+ end;
+
+ [Test]
+ procedure UnitTestExportStockLogsSkippedRecordAfterMaxRetries()
+ var
+ Shop: Record "Shpfy Shop";
+ ShopLocation: Record "Shpfy Shop Location";
+ Item: Record Item;
+ ShopifyProduct: Record "Shpfy Product";
+ ShopInventory: Record "Shpfy Shop Inventory";
+ SkippedRecord: Record "Shpfy Skipped Record";
+ InventorySubscriber: Codeunit "Shpfy Inventory Subscriber";
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ InventoryAPI: Codeunit "Shpfy Inventory API";
+ StockCalculate: Enum "Shpfy Stock Calculation";
+ SkippedCountBefore: Integer;
+ SkippedCountAfter: Integer;
+ begin
+ // [SCENARIO] Export stock logs skipped record when max retries exceeded
+ // [GIVEN] A ShopInventory record with stock different from Shopify stock
+ Initialize();
+
+ Shop := CommunicationMgt.GetShopRecord();
+ CreateShopLocation(ShopLocation, Shop.Code, StockCalculate::"Projected Available Balance Today");
+ CreateItem(Item);
+ UpdateItemInventory(Item, 25);
+ CreateShopifyProduct(ShopifyProduct, ShopInventory, Item.SystemId, Shop.Code, ShopLocation.Id);
+ ShopInventory."Shopify Stock" := 15;
+ ShopInventory.Modify();
+
+ // [GIVEN] Count of skipped records before export
+ SkippedCountBefore := SkippedRecord.Count();
+
+ // [GIVEN] The inventory subscriber is configured to always fail with concurrency error
+ BindSubscription(InventorySubscriber);
+ InventorySubscriber.SetRetryScenario(Enum::"Shpfy Inventory Retry Scenario"::AlwaysFail);
+ InventorySubscriber.SetErrorCode('IDEMPOTENCY_CONCURRENT_REQUEST');
+ InventoryAPI.SetShop(Shop.Code);
+
+ // [WHEN] ExportStock is called
+ ShopInventory.SetRange("Shop Code", Shop.Code);
+ ShopInventory.SetRange("Variant Id", ShopInventory."Variant Id");
+ InventoryAPI.ExportStock(ShopInventory);
+
+ // [THEN] A skipped record was logged
+ SkippedCountAfter := SkippedRecord.Count();
+ LibraryAssert.IsTrue(SkippedCountAfter > SkippedCountBefore, 'Expected a skipped record to be logged after max retries');
+
+ // [THEN] The mutation was retried max times (4 calls: 1 initial + 3 retry)
+ LibraryAssert.AreEqual(4, InventorySubscriber.GetCallCount(), 'Expected 4 GraphQL calls (1 initial + 3 retry)');
+
+ UnbindSubscription(InventorySubscriber);
+ end;
+
+ [Test]
+ procedure UnitTestCalcStockIncludesChangeFromQuantityNull()
+ var
+ Shop: Record "Shpfy Shop";
+ ShopLocation: Record "Shpfy Shop Location";
+ Item: Record Item;
+ ShopifyProduct: Record "Shpfy Product";
+ ShopInventory: Record "Shpfy Shop Inventory";
+ InventorySubscriber: Codeunit "Shpfy Inventory Subscriber";
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ InventoryAPI: Codeunit "Shpfy Inventory API";
+ StockCalculate: Enum "Shpfy Stock Calculation";
+ LastGraphQLRequest: Text;
+ begin
+ // [SCENARIO] CalcStock includes changeFromQuantity: null in the GraphQL mutation
+ // [GIVEN] A ShopInventory record with stock different from Shopify stock
+ Initialize();
+
+ Shop := CommunicationMgt.GetShopRecord();
+ CreateShopLocation(ShopLocation, Shop.Code, StockCalculate::"Projected Available Balance Today");
+ CreateItem(Item);
+ UpdateItemInventory(Item, 30);
+ CreateShopifyProduct(ShopifyProduct, ShopInventory, Item.SystemId, Shop.Code, ShopLocation.Id);
+ ShopInventory."Shopify Stock" := 20;
+ ShopInventory.Modify();
+
+ // [GIVEN] The inventory subscriber captures the GraphQL request
+ BindSubscription(InventorySubscriber);
+ InventorySubscriber.SetRetryScenario(Enum::"Shpfy Inventory Retry Scenario"::Success);
+ InventoryAPI.SetShop(Shop.Code);
+
+ // [WHEN] ExportStock is called
+ ShopInventory.SetRange("Shop Code", Shop.Code);
+ ShopInventory.SetRange("Variant Id", ShopInventory."Variant Id");
+ InventoryAPI.ExportStock(ShopInventory);
+
+ // [THEN] The GraphQL request contains changeFromQuantity: null
+ LastGraphQLRequest := InventorySubscriber.GetLastGraphQLRequest();
+ LibraryAssert.IsTrue(LastGraphQLRequest.Contains('"changeFromQuantity":null'), 'Expected changeFromQuantity: null in GraphQL request');
+
+ UnbindSubscription(InventorySubscriber);
+ end;
+
+ [Test]
+ procedure UnitTestIdempotencyKeyIsGenerated()
+ var
+ Shop: Record "Shpfy Shop";
+ ShopLocation: Record "Shpfy Shop Location";
+ Item: Record Item;
+ ShopifyProduct: Record "Shpfy Product";
+ ShopInventory: Record "Shpfy Shop Inventory";
+ InventorySubscriber: Codeunit "Shpfy Inventory Subscriber";
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ InventoryAPI: Codeunit "Shpfy Inventory API";
+ StockCalculate: Enum "Shpfy Stock Calculation";
+ LastGraphQLRequest: Text;
+ begin
+ // [SCENARIO] Idempotency key is generated and included in the GraphQL mutation
+ // [GIVEN] A ShopInventory record with stock different from Shopify stock
+ Initialize();
+
+ Shop := CommunicationMgt.GetShopRecord();
+ CreateShopLocation(ShopLocation, Shop.Code, StockCalculate::"Projected Available Balance Today");
+ CreateItem(Item);
+ UpdateItemInventory(Item, 35);
+ CreateShopifyProduct(ShopifyProduct, ShopInventory, Item.SystemId, Shop.Code, ShopLocation.Id);
+ ShopInventory."Shopify Stock" := 25;
+ ShopInventory.Modify();
+
+ // [GIVEN] The inventory subscriber captures the GraphQL request
+ BindSubscription(InventorySubscriber);
+ InventorySubscriber.SetRetryScenario(Enum::"Shpfy Inventory Retry Scenario"::Success);
+ InventoryAPI.SetShop(Shop.Code);
+
+ // [WHEN] ExportStock is called
+ ShopInventory.SetRange("Shop Code", Shop.Code);
+ ShopInventory.SetRange("Variant Id", ShopInventory."Variant Id");
+ InventoryAPI.ExportStock(ShopInventory);
+
+ // [THEN] The GraphQL request contains @idempotent directive with a GUID key
+ LastGraphQLRequest := InventorySubscriber.GetLastGraphQLRequest();
+ LibraryAssert.IsTrue(LastGraphQLRequest.Contains('@idempotent(key:'), 'Expected @idempotent directive in GraphQL request');
+
+ UnbindSubscription(InventorySubscriber);
+ end;
+
+ local procedure CreateItem(var Item: Record Item)
+ begin
+ LibraryInventory.CreateItemWithoutVAT(Item);
+ end;
+
+ local procedure CreateShopifyProduct(var ShopifyProduct: Record "Shpfy Product"; var ShopInventory: Record "Shpfy Shop Inventory"; ItemSystemId: Guid; ShopCode: Code[20]; ShopLocationId: BigInteger)
+ var
+ ShopifyVariant: Record "Shpfy Variant";
+ ProductId: BigInteger;
+ VariantId: BigInteger;
+ InventoryItemId: BigInteger;
+ begin
+ ProductId := GetNextId();
+ VariantId := GetNextId();
+ InventoryItemId := GetNextId();
+
+ ShopifyProduct.Init();
+ ShopifyProduct.Id := ProductId;
+ ShopifyProduct."Item SystemId" := ItemSystemId;
+ ShopifyProduct."Shop Code" := ShopCode;
+ ShopifyProduct.Insert();
+
+ ShopifyVariant.Init();
+ ShopifyVariant.Id := VariantId;
+ ShopifyVariant."Product Id" := ShopifyProduct.Id;
+ ShopifyVariant."Item SystemId" := ItemSystemId;
+ ShopifyVariant."Shop Code" := ShopCode;
+ ShopifyVariant.Insert();
+
+ ShopInventory.Init();
+ ShopInventory."Inventory Item Id" := InventoryItemId;
+ ShopInventory."Shop Code" := ShopCode;
+ ShopInventory."Location Id" := ShopLocationId;
+ ShopInventory."Product Id" := ShopifyProduct.Id;
+ ShopInventory."Variant Id" := ShopifyVariant.Id;
+ ShopInventory.Insert();
+ end;
+
+ local procedure GetNextId(): BigInteger
+ begin
+ NextId += 1;
+ exit(NextId);
+ end;
+
+ local procedure CreateShopLocation(var ShopLocation: Record "Shpfy Shop Location"; ShopCode: Code[20]; StockCalculation: Enum "Shpfy Stock Calculation")
+ begin
+ ShopLocation.SetRange("Shop Code", ShopCode);
+ ShopLocation.SetRange(Active, true);
+ if ShopLocation.FindFirst() then begin
+ ShopLocation."Stock Calculation" := StockCalculation;
+ ShopLocation."Default Product Location" := true;
+ ShopLocation.Modify();
+ exit;
+ end;
+
+ ShopLocation.Init();
+ ShopLocation."Shop Code" := ShopCode;
+ ShopLocation.Id := Any.IntegerInRange(10000, 999999);
+ ShopLocation."Stock Calculation" := StockCalculation;
+ ShopLocation.Active := true;
+ ShopLocation."Default Product Location" := true;
+ ShopLocation.Insert();
+ end;
+
+ local procedure UpdateItemInventory(Item: Record Item; Qty: Decimal)
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ begin
+ LibraryInventory.CreateItemJournalLineInItemTemplate(ItemJournalLine, Item."No.", '', '', Qty);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+}
diff --git a/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventoryRetryScenario.Enum.al b/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventoryRetryScenario.Enum.al
new file mode 100644
index 0000000000..7bc3a3a128
--- /dev/null
+++ b/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventoryRetryScenario.Enum.al
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Integration.Shopify.Test;
+
+///
+/// Enum Shpfy Inventory Retry Scenario (ID 139617).
+/// Scenarios for simulating inventory API retry behavior in tests.
+///
+enum 139617 "Shpfy Inventory Retry Scenario"
+{
+ Extensible = false;
+
+ value(0; Success)
+ {
+ Caption = 'Success';
+ }
+ value(1; FailOnceThenSucceed)
+ {
+ Caption = 'Fail Once Then Succeed';
+ }
+ value(2; AlwaysFail)
+ {
+ Caption = 'Always Fail';
+ }
+}
diff --git a/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventorySubscriber.Codeunit.al b/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventorySubscriber.Codeunit.al
new file mode 100644
index 0000000000..b942c6f134
--- /dev/null
+++ b/src/Apps/W1/Shopify/Test/Inventory/ShpfyInventorySubscriber.Codeunit.al
@@ -0,0 +1,104 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Integration.Shopify.Test;
+
+using Microsoft.Integration.Shopify;
+
+///
+/// Codeunit Shpfy Inventory Subscriber (ID 139593).
+/// Mock subscriber for inventory API tests to simulate GraphQL responses.
+///
+codeunit 139593 "Shpfy Inventory Subscriber"
+{
+ SingleInstance = true;
+ EventSubscriberInstance = Manual;
+
+ var
+ RetryScenario: Enum "Shpfy Inventory Retry Scenario";
+ ErrorCode: Text;
+ CallCount: Integer;
+ LastGraphQLRequest: Text;
+
+ internal procedure SetRetryScenario(NewScenario: Enum "Shpfy Inventory Retry Scenario")
+ begin
+ RetryScenario := NewScenario;
+ CallCount := 0;
+ LastGraphQLRequest := '';
+ end;
+
+ internal procedure SetErrorCode(NewErrorCode: Text)
+ begin
+ ErrorCode := NewErrorCode;
+ end;
+
+ internal procedure GetCallCount(): Integer
+ begin
+ exit(CallCount);
+ end;
+
+ internal procedure GetLastGraphQLRequest(): Text
+ begin
+ exit(LastGraphQLRequest);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Shpfy Communication Events", 'OnClientSend', '', true, false)]
+ local procedure OnClientSend(HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ begin
+ MakeResponse(HttpRequestMessage, HttpResponseMessage);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Shpfy Communication Events", 'OnGetContent', '', true, false)]
+ local procedure OnGetContent(HttpResponseMessage: HttpResponseMessage; var Response: Text)
+ begin
+ HttpResponseMessage.Content.ReadAs(Response);
+ end;
+
+ local procedure MakeResponse(HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage)
+ var
+ Uri: Text;
+ GraphQLQuery: Text;
+ InventorySetQuantitiesGQLTxt: Label 'inventorySetQuantities', Locked = true;
+ GraphQLCmdTxt: Label '/graphql.json', Locked = true;
+ begin
+ case HttpRequestMessage.Method of
+ 'POST':
+ begin
+ Uri := HttpRequestMessage.GetRequestUri();
+ if Uri.EndsWith(GraphQLCmdTxt) then
+ if HttpRequestMessage.Content.ReadAs(GraphQLQuery) then begin
+ LastGraphQLRequest := GraphQLQuery;
+ if GraphQLQuery.Contains(InventorySetQuantitiesGQLTxt) then begin
+ CallCount += 1;
+ HttpResponseMessage := GetInventoryResponse();
+ end;
+ end;
+ end;
+ end;
+ end;
+
+ local procedure GetInventoryResponse(): HttpResponseMessage
+ var
+ HttpResponseMessage: HttpResponseMessage;
+ ResponseJson: Text;
+ SuccessResponseTxt: Label '{"data":{"inventorySetQuantities":{"inventoryAdjustmentGroup":{"id":"gid://shopify/InventoryAdjustmentGroup/12345"},"userErrors":[]}}}', Locked = true;
+ ErrorResponseTxt: Label '{"data":{"inventorySetQuantities":{"inventoryAdjustmentGroup":null,"userErrors":[{"field":["input"],"message":"Concurrent request detected","code":"%1"}]}}}', Comment = '%1 = Error code', Locked = true;
+ begin
+ case RetryScenario of
+ RetryScenario::Success:
+ ResponseJson := SuccessResponseTxt;
+ RetryScenario::FailOnceThenSucceed:
+ if CallCount <= 1 then
+ ResponseJson := StrSubstNo(ErrorResponseTxt, ErrorCode)
+ else
+ ResponseJson := SuccessResponseTxt;
+ RetryScenario::AlwaysFail:
+ ResponseJson := StrSubstNo(ErrorResponseTxt, ErrorCode);
+ end;
+
+ HttpResponseMessage.Content.WriteFrom(ResponseJson);
+ exit(HttpResponseMessage);
+ end;
+}
diff --git a/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundTest.Codeunit.al
index 3f2359b693..a21e7a7c66 100644
--- a/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundTest.Codeunit.al
+++ b/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundTest.Codeunit.al
@@ -466,6 +466,7 @@ codeunit 139611 "Shpfy Order Refund Test"
OrderRefundsHelper.SetDefaultSeed();
ReturnId := OrderRefundsHelper.CreateReturn(OrderId);
OrderRefundsHelper.CreateReturnLine(ReturnId, OrderId, '');
+ OrderRefundsHelper.CreateUnverifiedReturnLine(ReturnId, '');
end;
local procedure CreateLocation(var Location: Record Location)
diff --git a/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundsHelper.Codeunit.al b/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundsHelper.Codeunit.al
index f68b72c226..ef86b01aa4 100644
--- a/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundsHelper.Codeunit.al
+++ b/src/Apps/W1/Shopify/Test/Order Refunds/ShpfyOrderRefundsHelper.Codeunit.al
@@ -43,10 +43,12 @@ codeunit 139564 "Shpfy Order Refunds Helper"
ReturnId := CreateReturn(OrderId);
CreateReturnLine(ReturnId, ShopifyIds.Get('OrderLine').Get(1), 'DEFECTIVE');
+ CreateUnverifiedReturnLine(ReturnId, 'DEFECTIVE');
ShopifyIds.Get('Return').Add(ReturnId);
ReturnId := CreateReturn(OrderId);
CreateReturnLine(ReturnId, ShopifyIds.Get('OrderLine').Get(2), 'NOT_AS_DESCRIBED');
+ CreateUnverifiedReturnLine(ReturnId, 'NOT_AS_DESCRIBED');
ShopifyIds.Get('Return').Add(ReturnId);
RefundId := CreateRefundHeader(OrderId, ShopifyIds.Get('Return').Get(1), 156.38);
@@ -184,16 +186,17 @@ codeunit 139564 "Shpfy Order Refunds Helper"
exit(ReturnHeader."Return Id");
end;
- internal procedure CreateReturnLine(ReturnOrderId: BigInteger; OrderLineId: BigInteger; ReturnReason: Text): BigInteger
+ internal procedure CreateReturnLine(ReturnOrderId: BigInteger; OrderLineId: BigInteger; ReturnReasonHandle: Text): BigInteger
var
ReturnLine: Record "Shpfy Return Line";
- ReturnEnumConvertor: Codeunit "Shpfy Return Enum Convertor";
begin
ReturnLine."Return Line Id" := Any.IntegerInRange(100000, 999999);
ReturnLine."Return Id" := ReturnOrderId;
+ ReturnLine.Type := ReturnLine.Type::Default;
ReturnLine."Fulfillment Line Id" := Any.IntegerInRange(100000, 999999);
ReturnLine."Order Line Id" := OrderLineId;
- ReturnLine."Return Reason" := ReturnEnumConvertor.ConvertToReturnReason(ReturnReason);
+ ReturnLine."Return Reason Handle" := CopyStr(ReturnReasonHandle, 1, MaxStrLen(ReturnLine."Return Reason Handle"));
+ ReturnLine."Return Reason Name" := CopyStr(GetReturnReasonNameFromHandle(ReturnReasonHandle), 1, MaxStrLen(ReturnLine."Return Reason Name"));
ReturnLine.Quantity := 1;
ReturnLine."Refundable Quantity" := 0;
ReturnLine."Refunded Quantity" := 1;
@@ -204,6 +207,50 @@ codeunit 139564 "Shpfy Order Refunds Helper"
exit(ReturnLine."Return Line Id");
end;
+ internal procedure CreateUnverifiedReturnLine(ReturnId: BigInteger; ReturnReasonHandle: Text): BigInteger
+ var
+ ReturnLine: Record "Shpfy Return Line";
+ begin
+ ReturnLine."Return Line Id" := Any.IntegerInRange(100000, 999999);
+ ReturnLine."Return Id" := ReturnId;
+ ReturnLine.Type := ReturnLine.Type::Unverified;
+ ReturnLine."Return Reason Handle" := CopyStr(ReturnReasonHandle, 1, MaxStrLen(ReturnLine."Return Reason Handle"));
+ ReturnLine."Return Reason Name" := CopyStr(GetReturnReasonNameFromHandle(ReturnReasonHandle), 1, MaxStrLen(ReturnLine."Return Reason Name"));
+ ReturnLine.Quantity := 1;
+ ReturnLine."Refundable Quantity" := 1;
+ ReturnLine."Refunded Quantity" := 0;
+ ReturnLine."Unit Price" := 156.38;
+ ReturnLine.Insert();
+ exit(ReturnLine."Return Line Id");
+ end;
+
+ local procedure GetReturnReasonNameFromHandle(Handle: Text): Text
+ begin
+ // Map handle values to human-readable names (simulating Shopify's returnReasonDefinition)
+ case Handle of
+ 'DEFECTIVE':
+ exit('Defective');
+ 'NOT_AS_DESCRIBED':
+ exit('Not as described');
+ 'WRONG_ITEM':
+ exit('Wrong item');
+ 'SIZE_TOO_SMALL':
+ exit('Size too small');
+ 'SIZE_TOO_LARGE':
+ exit('Size too large');
+ 'STYLE':
+ exit('Style');
+ 'COLOR':
+ exit('Color');
+ 'OTHER':
+ exit('Other');
+ 'UNKNOWN':
+ exit('Unknown');
+ else
+ exit(Handle);
+ end;
+ end;
+
internal procedure CreateRefundHeader(OrderId: BigInteger; ReturnId: BigInteger; Amount: Decimal): BigInteger
var
RefundHeader: Record "Shpfy Refund Header";
diff --git a/src/Apps/W1/Shopify/Test/Payments/ShpfyPaymentsTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Payments/ShpfyPaymentsTest.Codeunit.al
index 4112e94df3..155e3ba3a1 100644
--- a/src/Apps/W1/Shopify/Test/Payments/ShpfyPaymentsTest.Codeunit.al
+++ b/src/Apps/W1/Shopify/Test/Payments/ShpfyPaymentsTest.Codeunit.al
@@ -31,6 +31,33 @@ codeunit 139566 "Shpfy Payments Test"
isInitialized := true;
end;
+ [Test]
+ procedure UnitTestImportPayoutWithExternalTraceId()
+ var
+ Payout: Record "Shpfy Payout";
+ PaymentsAPI: Codeunit "Shpfy Payments API";
+ Id: BigInteger;
+ ExpectedExternalTraceId: Text;
+ JPayout: JsonObject;
+ begin
+ // [SCENARIO] Import payout correctly imports the externalTraceId field (2026-01 API)
+ Initialize();
+
+ // [GIVEN] A random Generated Payout with externalTraceId
+ Id := Any.IntegerInRange(10000, 99999);
+ ExpectedExternalTraceId := Any.AlphanumericText(50);
+ JPayout := GetRandomPayout(Id, ExpectedExternalTraceId);
+
+ // [WHEN] Invoke the function ImportPayout(JPayout)
+ PaymentsAPI.SetShop(Shop);
+ PaymentsAPI.ImportPayout(JPayout);
+
+ // [THEN] We must find the "Shpfy Payout" record with the correct externalTraceId and Shop Code
+ LibraryAssert.IsTrue(Payout.Get(Id), 'Get "Shpfy Payout" record');
+ LibraryAssert.AreEqual(ExpectedExternalTraceId, Payout."External Trace Id", 'External Trace Id should match');
+ LibraryAssert.AreEqual(Shop.Code, Payout."Shop Code", 'Shop Code should match');
+ end;
+
[Test]
procedure UnitTestImportPayoutBackfillsShopCode()
var
@@ -44,7 +71,7 @@ codeunit 139566 "Shpfy Payments Test"
// [GIVEN] An existing payout record imported without a shop context (blank Shop Code)
Id := Any.IntegerInRange(10000, 99999);
- JPayout := GetRandomPayout(Id);
+ JPayout := GetRandomPayout(Id, Any.AlphanumericText(50));
PaymentsAPI.ImportPayout(JPayout);
LibraryAssert.IsTrue(Payout.Get(Id), 'Payout should be created');
LibraryAssert.AreEqual('', Payout."Shop Code", 'Shop Code should initially be blank');
@@ -58,7 +85,7 @@ codeunit 139566 "Shpfy Payments Test"
LibraryAssert.AreEqual(Shop.Code, Payout."Shop Code", 'Shop Code should be backfilled on existing payout');
end;
- local procedure GetRandomPayout(Id: BigInteger): JsonObject
+ local procedure GetRandomPayout(Id: BigInteger; ExternalTraceId: Text): JsonObject
var
JPayout: JsonObject;
JNet: JsonObject;
@@ -68,11 +95,12 @@ codeunit 139566 "Shpfy Payments Test"
begin
JPayout.Add('id', StrSubstNo(PayoutGidTxt, Id));
JPayout.Add('status', 'SCHEDULED');
+ JPayout.Add('externalTraceId', ExternalTraceId);
JPayout.Add('issuedAt', Format(Today, 0, 9));
JNet.Add('amount', Any.DecimalInRange(1000, 2));
JNet.Add('currencyCode', 'USD');
JPayout.Add('net', JNet);
-
+
// Add summary with fee/gross amounts
JAmount.Add('amount', 0);
JSummary.Add('adjustmentsFee', JAmount);
@@ -86,7 +114,7 @@ codeunit 139566 "Shpfy Payments Test"
JSummary.Add('retriedPayoutsFee', JAmount);
JSummary.Add('retriedPayoutsGross', JAmount);
JPayout.Add('summary', JSummary);
-
+
exit(JPayout);
end;
diff --git a/src/Apps/W1/Shopify/Test/app.json b/src/Apps/W1/Shopify/Test/app.json
index d627290a69..055702a85a 100644
--- a/src/Apps/W1/Shopify/Test/app.json
+++ b/src/Apps/W1/Shopify/Test/app.json
@@ -57,7 +57,7 @@
"to": 134247
},
{
- "from": 139539,
+ "from": 139537,
"to": 139549
},
{
@@ -72,6 +72,10 @@
"from": 139576,
"to": 139589
},
+ {
+ "from": 139593,
+ "to": 139594
+ },
{
"from": 139601,
"to": 139609