From 096df31242a9aeeb7a8e0c2bcee0bb049296197f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tine=20Stari=C4=8D?= <42935028+tinestaric@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:39:00 +0300 Subject: [PATCH] [Shopify] Shopify return location on refunds (#26728) This pull request does not have a related issue as it's part of delivery for development agreed directly with @AndreiPanko Added a setting to the shopify shop to use original return location (From shopify) or a default return location set in the Shopify shop when creating Credit Memos for Refunds. Modified the return and refund creation to pull original location from shopify refund or return orders. When an item on a single return order in shopify is restocked to multiple locations, we cannot determine a single location to put on a return line. If there is any reason that Original location couldn't be determined then the Default Return Location will be used during the creation of a Credit Memo. Fixes #26819 Fixes [AB#540965](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/540965) --------- Co-authored-by: Tine Staric --- .../Enums/ShpfyReturnLocationPriority.Enum.al | 16 ++++ .../app/src/Base/Pages/ShpfyShopCard.Page.al | 6 ++ .../app/src/Base/Tables/ShpfyShop.Table.al | 11 ++- .../ShpfyGQLNextRefundLines.Codeunit.al | 2 +- .../ShpfyGQLNextRevFulfillOrdLns.Codeunit.al | 27 ++++++ .../ShpfyGQLNextRevFulfillOrders.Codeunit.al | 28 ++++++ .../Codeunits/ShpfyGQLRefundLines.Codeunit.al | 2 +- .../ShpfyGQLRevFulfillOrderLines.Codeunit.al | 27 ++++++ .../ShpfyGQLRevFulfillOrders.Codeunit.al | 28 ++++++ .../GraphQL/Enums/ShpfyGraphQLType.Enum.al | 20 ++++ .../Codeunits/ShpfyRefundsAPI.Codeunit.al | 30 +++++- .../Tables/ShpfyRefundLine.Table.al | 6 ++ .../ShpfyCreateSalesDocRefund.Codeunit.al | 18 +++- .../Codeunits/ShpfyReturnsAPI.Codeunit.al | 96 ++++++++++++++++++- .../Tables/ShpfyReturnLine.Table.al | 6 ++ 15 files changed, 311 insertions(+), 12 deletions(-) create mode 100644 Apps/W1/Shopify/app/src/Base/Enums/ShpfyReturnLocationPriority.Enum.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrdLns.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrders.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrderLines.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrders.Codeunit.al diff --git a/Apps/W1/Shopify/app/src/Base/Enums/ShpfyReturnLocationPriority.Enum.al b/Apps/W1/Shopify/app/src/Base/Enums/ShpfyReturnLocationPriority.Enum.al new file mode 100644 index 0000000000..06e112299d --- /dev/null +++ b/Apps/W1/Shopify/app/src/Base/Enums/ShpfyReturnLocationPriority.Enum.al @@ -0,0 +1,16 @@ +namespace Microsoft.Integration.Shopify; + +enum 30162 "Shpfy Return Location Priority" +{ + Access = Public; + Extensible = false; + + value(0; "Default Return Location") + { + Caption = 'Default Return Location'; + } + value(1; "Original -> Default Location") + { + Caption = 'Original -> Default Location'; + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al index 18fa93e076..3c71c716ed 100644 --- a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al +++ b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al @@ -540,6 +540,12 @@ page 30101 "Shpfy Shop Card" ShowCaption = false; Visible = IsReturnRefundsVisible; + field("Return Location Priority"; Rec."Return Location Priority") + { + ApplicationArea = All; + Caption = 'Return Location Priority'; + ToolTip = 'Specifies the priority of the return location.'; + } field("Location Code of Returns"; Rec."Return Location") { ApplicationArea = All; diff --git a/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al b/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al index d521572415..05bf12e5ad 100644 --- a/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al +++ b/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al @@ -233,8 +233,8 @@ table 30102 "Shpfy Shop" ObsoleteState = Pending; ObsoleteTag = '24.0'; #else - ObsoleteState = Removed; - ObsoleteTag = '27.0'; + ObsoleteState = Removed; + ObsoleteTag = '27.0'; #endif } field(30; "Shopify Can Update Customer"; Boolean) @@ -511,7 +511,7 @@ table 30102 "Shpfy Shop" } field(73; "Return Location"; Code[10]) { - Caption = 'Return Location'; + Caption = 'Default Return Location'; DataClassification = CustomerContent; TableRelation = Location where("Use As In-Transit" = const(false)); } @@ -778,6 +778,11 @@ table 30102 "Shpfy Shop" end; #endif } + field(128; "Return Location Priority"; Enum "Shpfy Return Location Priority") + { + Caption = 'Return Location Priority'; + DataClassification = CustomerContent; + } field(200; "Shop Id"; Integer) { DataClassification = SystemMetadata; diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRefundLines.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRefundLines.Codeunit.al index 69a0ca1fd1..426d81629f 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRefundLines.Codeunit.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRefundLines.Codeunit.al @@ -5,7 +5,7 @@ codeunit 30232 "Shpfy GQL NextRefundLines" implements "Shpfy IGraphQL" internal procedure GetGraphQL(): Text begin - exit('{"query":"{ refund(id: \"gid://shopify/Refund/{{RefundId}}\") { refundLineItems(first: 10, after:\"{{After}}\") { pageInfo { endCursor hasNextPage } nodes { lineItem { id } quantity restockType restocked priceSet { presentmentMoney { amount } shopMoney { amount }} subtotalSet { presentmentMoney { amount } shopMoney { amount }} totalTaxSet { presentmentMoney { amount } shopMoney { amount }}}}}}"}'); + exit('{"query":"{ refund(id: \"gid://shopify/Refund/{{RefundId}}\") { refundLineItems(first: 10, after:\"{{After}}\") { pageInfo { endCursor hasNextPage } nodes { lineItem { id } quantity restockType location { legacyResourceId } restocked priceSet { presentmentMoney { amount } shopMoney { amount }} subtotalSet { presentmentMoney { amount } shopMoney { amount }} totalTaxSet { presentmentMoney { amount } shopMoney { amount }}}}}}"}'); end; internal procedure GetExpectedCost(): Integer diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrdLns.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrdLns.Codeunit.al new file mode 100644 index 0000000000..a5e508b6a2 --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrdLns.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL NextRevFulfillOrdLns (ID 30349) implements Interface Shpfy IGraphQL. +/// +codeunit 30349 "Shpfy GQL NextRevFulfillOrdLns" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query":"{ reverseFulfillmentOrder(id: \"{{FulfillOrderId}}\") { lineItems(first: 10, after:\"{{After}}\") { pageInfo { endCursor hasNextPage } nodes { id fulfillmentLineItem { id lineItem { id name } } dispositions { id quantity type location { id legacyResourceId } } } } } }"}'); + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(15); + end; +} diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrders.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrders.Codeunit.al new file mode 100644 index 0000000000..126f0ffab7 --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLNextRevFulfillOrders.Codeunit.al @@ -0,0 +1,28 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL NextRevFulfillOrders (ID 30347) implements Interface Shpfy IGraphQL. +/// +codeunit 30347 "Shpfy GQL NextRevFulfillOrders" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query":"{ return(id: \"gid://shopify/Return/{{ReturnId}}\") { reverseFulfillmentOrders(first: 10, after:\"{{After}}\") { pageInfo { endCursor hasNextPage } nodes { id } } } }"}'); + + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(7); + end; +} diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRefundLines.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRefundLines.Codeunit.al index bb11635ff8..29649cb17f 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRefundLines.Codeunit.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRefundLines.Codeunit.al @@ -5,7 +5,7 @@ codeunit 30230 "Shpfy GQL RefundLines" implements "Shpfy IGraphQL" internal procedure GetGraphQL(): Text begin - exit('{"query":"{ refund(id: \"gid://shopify/Refund/{{RefundId}}\") { refundLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { lineItem { id } quantity restockType restocked priceSet { presentmentMoney { amount } shopMoney { amount }} subtotalSet { presentmentMoney { amount } shopMoney { amount }} totalTaxSet { presentmentMoney { amount } shopMoney { amount }}}}}}"}'); + exit('{"query":"{ refund(id: \"gid://shopify/Refund/{{RefundId}}\") { refundLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { lineItem { id } quantity restockType location { legacyResourceId } restocked priceSet { presentmentMoney { amount } shopMoney { amount }} subtotalSet { presentmentMoney { amount } shopMoney { amount }} totalTaxSet { presentmentMoney { amount } shopMoney { amount }}}}}}"}'); end; internal procedure GetExpectedCost(): Integer diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrderLines.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrderLines.Codeunit.al new file mode 100644 index 0000000000..f4e6ac63e5 --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrderLines.Codeunit.al @@ -0,0 +1,27 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL RevFulfillOrderLines (ID 30348) implements Interface Shpfy IGraphQL. +/// +codeunit 30348 "Shpfy GQL RevFulfillOrderLines" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query":"{ reverseFulfillmentOrder(id: \"{{FulfillOrderId}}\") { id lineItems(first: 10) { nodes { id fulfillmentLineItem { id lineItem { id name } } dispositions { id quantity type location { id legacyResourceId } } } } } }"}'); + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(15); + end; +} diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrders.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrders.Codeunit.al new file mode 100644 index 0000000000..29dd45361e --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLRevFulfillOrders.Codeunit.al @@ -0,0 +1,28 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy GQL RevFulfillOrders (ID 30346) implements Interface Shpfy IGraphQL. +/// +codeunit 30346 "Shpfy GQL RevFulfillOrders" implements "Shpfy IGraphQL" +{ + Access = Internal; + + /// + /// GetGraphQL. + /// + /// Return value of type Text. + internal procedure GetGraphQL(): Text + begin + exit('{"query":"{ return(id: \"gid://shopify/Return/{{ReturnId}}\") { reverseFulfillmentOrders(first: 10) { pageInfo { endCursor hasNextPage } nodes { id } } } }"}'); + + end; + + /// + /// GetExpectedCost. + /// + /// Return value of type Integer. + internal procedure GetExpectedCost(): Integer + begin + exit(7); + end; +} diff --git a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al index d6d2c8d75a..031d9283ae 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al @@ -405,4 +405,24 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL" Caption = 'Get Order Transactions'; Implementation = "Shpfy IGraphQL" = "Shpfy GQL OrderTransactions"; } + value(87; GetReverseFulfillmentOrders) + { + Caption = 'Get Reverse Fulfillment Orders'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL RevFulfillOrders"; + } + value(88; GetNextReverseFulfillmentOrders) + { + Caption = 'Get Next Reverse Fulfillment Orders'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL NextRevFulfillOrders"; + } + value(89; GetReverseFulfillmentOrderLines) + { + Caption = 'Get Reverse Fulfillment Order Lines'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL RevFulfillOrderLines"; + } + value(90; GetNextReverseFulfillmentOrderLines) + { + Caption = 'Get Next Reverse Fulfillment Order Lines'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL NextRevFulfillOrdLns"; + } } diff --git a/Apps/W1/Shopify/app/src/Order Refunds/Codeunits/ShpfyRefundsAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Order Refunds/Codeunits/ShpfyRefundsAPI.Codeunit.al index a6c5ab8c21..6b5b9e943c 100644 --- a/Apps/W1/Shopify/app/src/Order Refunds/Codeunits/ShpfyRefundsAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Order Refunds/Codeunits/ShpfyRefundsAPI.Codeunit.al @@ -31,11 +31,14 @@ codeunit 30228 "Shpfy Refunds API" RefundHeader: Record "Shpfy Refund Header"; GraphQLType: Enum "Shpfy GraphQL Type"; Parameters: Dictionary of [text, Text]; + ReturnLocations: Dictionary of [BigInteger, BigInteger]; JResponse: JsonToken; JLines: JsonArray; JLine: JsonToken; begin GetRefundHeader(RefundId, UpdatedAt, RefundHeader); + ReturnLocations := CollectReturnLocations(RefundHeader."Return Id"); + Parameters.Add('RefundId', Format(RefundId)); GraphQLType := "Shpfy GraphQL Type"::GetRefundLines; repeat @@ -46,8 +49,9 @@ codeunit 30228 "Shpfy Refunds API" Parameters.Set('After', JsonHelper.GetValueAsText(JResponse, 'data.refund.refundLineItems.pageInfo.endCursor')) else Parameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.refund.refundLineItems.pageInfo.endCursor')); + foreach JLine in JLines do - FillInRefundLine(RefundId, JLine.AsObject(), IsNonZeroOrReturnRefund(RefundHeader)); + FillInRefundLine(RefundId, JLine.AsObject(), IsNonZeroOrReturnRefund(RefundHeader), ReturnLocations); until not JsonHelper.GetValueAsBoolean(JResponse, 'data.refund.refundLineItems.pageInfo.hasNextPage'); end; @@ -88,21 +92,34 @@ codeunit 30228 "Shpfy Refunds API" DataCapture.Add(Database::"Shpfy Refund Header", RefundHeader.SystemId, JResponse); end; - local procedure FillInRefundLine(RefundId: BigInteger; JLine: JsonObject; NonZeroOrReturnRefund: Boolean) + + local procedure CollectReturnLocations(ReturnId: BigInteger): Dictionary of [BigInteger, BigInteger] + var + ReturnsAPI: Codeunit "Shpfy Returns API"; + begin + if ReturnId <> 0 then + exit(ReturnsAPI.GetReturnLocations(ReturnId)); + end; + + local procedure FillInRefundLine(RefundId: BigInteger; JLine: JsonObject; NonZeroOrReturnRefund: Boolean; ReturnLocations: Dictionary of [BigInteger, BigInteger]) var DataCapture: Record "Shpfy Data Capture"; RefundLine: Record "Shpfy Refund Line"; RefundLineRecordRef: RecordRef; Id: BigInteger; + ReturnLocation: BigInteger; begin Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JLine, 'lineItem.id')); + if not RefundLine.Get(RefundId, Id) then begin RefundLine."Refund Line Id" := Id; RefundLine."Refund Id" := RefundId; RefundLine."Order Line Id" := Id; RefundLine.Insert(); end; + RefundLine."Restock Type" := RefundEnumConvertor.ConvertToReStockType(JsonHelper.GetValueAsText(JLine, 'restockType')); + RefundLineRecordRef.GetTable(RefundLine); JsonHelper.GetValueIntoField(JLine, 'quantity', RefundLineRecordRef, RefundLine.FieldNo(Quantity)); JsonHelper.GetValueIntoField(JLine, 'restocked', RefundLineRecordRef, RefundLine.FieldNo(Restocked)); @@ -113,8 +130,17 @@ codeunit 30228 "Shpfy Refunds API" JsonHelper.GetValueIntoField(JLine, 'totalTaxSet.shopMoney.amount', RefundLineRecordRef, RefundLine.FieldNo("Total Tax Amount")); JsonHelper.GetValueIntoField(JLine, 'totalTaxSet.presentmentMoney.amount', RefundLineRecordRef, RefundLine.FieldNo("Presentment Total Tax Amount")); RefundLineRecordRef.SetTable(RefundLine); + RefundLine."Can Create Credit Memo" := NonZeroOrReturnRefund; + RefundLine."Location Id" := JsonHelper.GetValueAsBigInteger(JLine, 'location.legacyResourceId'); + + // If refund was created from a return, the location needs to come from the return + // If Item was restocked to multiple locations, the return location is not known + if (RefundLine."Location Id" = 0) and (ReturnLocations.Get(RefundLine."Order Line Id", ReturnLocation)) then + RefundLine."Location Id" := ReturnLocation; + RefundLine.Modify(); + RefundLineRecordRef.Close(); DataCapture.Add(Database::"Shpfy Refund Line", RefundLine.SystemId, JLine); end; diff --git a/Apps/W1/Shopify/app/src/Order Refunds/Tables/ShpfyRefundLine.Table.al b/Apps/W1/Shopify/app/src/Order Refunds/Tables/ShpfyRefundLine.Table.al index 65d2030dfc..59248c70da 100644 --- a/Apps/W1/Shopify/app/src/Order Refunds/Tables/ShpfyRefundLine.Table.al +++ b/Apps/W1/Shopify/app/src/Order Refunds/Tables/ShpfyRefundLine.Table.al @@ -115,6 +115,12 @@ table 30145 "Shpfy Refund Line" CalcFormula = lookup("Shpfy Order Line"."Gift Card" where("Line Id" = field("Order Line Id"))); Editable = false; } + field(105; "Location Id"; BigInteger) + { + Caption = 'Location Id'; + DataClassification = SystemMetadata; + Editable = false; + } } keys { diff --git a/Apps/W1/Shopify/app/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al b/Apps/W1/Shopify/app/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al index 4c05820e44..8d2a888d1c 100644 --- a/Apps/W1/Shopify/app/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Order Return Refund Processing/Codeunits/ShpfyCreateSalesDocRefund.Codeunit.al @@ -111,7 +111,6 @@ codeunit 30246 "Shpfy Create Sales Doc. Refund" SalesHeader.Validate("Document Date", DT2Date(RefundHeader."Created At")); if OrderMgt.FindTaxArea(OrderHeader, ShopifyTaxArea) and (ShopifyTaxArea."Tax Area Code" <> '') then SalesHeader.Validate("Tax Area Code", ShopifyTaxArea."Tax Area Code"); - SalesHeader.Validate("Location Code", Shop."Return Location"); end; SalesHeader."Shpfy Refund Id" := RefundHeader."Refund Id"; SalesHeader.Modify(true); @@ -143,6 +142,7 @@ codeunit 30246 "Shpfy Create Sales Doc. Refund" RefundLine: Record "Shpfy Refund Line"; ReturnLine: Record "Shpfy Return Line"; GiftCard: Record "Shpfy Gift Card"; + ShopLocation: Record "Shpfy Shop Location"; LineNo: Integer; OpenAmount: Decimal; IsHandled: Boolean; @@ -181,7 +181,13 @@ codeunit 30246 "Shpfy Create Sales Doc. Refund" SalesLine.Validate("No.", RefundLine."Item No."); if RefundLine."Variant Code" <> '' then SalesLine.Validate("Variant Code", RefundLine."Variant Code"); - SalesLine.Validate("Location Code", Shop."Return Location"); + + if ShopLocation.Get(Shop.Code, RefundLine."Location Id") then + SalesLine.Validate("Location Code", ShopLocation."Default Location Code"); + + If (Shop."Return Location Priority" = "Shpfy Return Location Priority"::"Default Return Location") or (SalesLine."Location Code" = '') then + SalesLine.Validate("Location Code", Shop."Return Location"); + end; SalesLine.Validate(Quantity, RefundLine.Quantity); SalesLine.Validate("Unit Price", RefundLine.Amount); @@ -233,7 +239,13 @@ codeunit 30246 "Shpfy Create Sales Doc. Refund" SalesLine.Validate("No.", ReturnLine."Item No."); if ReturnLine."Variant Code" <> '' then SalesLine.Validate("Variant Code", ReturnLine."Variant Code"); - SalesLine.Validate("Location Code", Shop."Return Location"); + + if ShopLocation.Get(Shop.Code, ReturnLine."Location Id") then + SalesLine.Validate("Location Code", ShopLocation."Default Location Code"); + + If (Shop."Return Location Priority" = "Shpfy Return Location Priority"::"Default Return Location") or (SalesLine."Location Code" = '') then + SalesLine.Validate("Location Code", Shop."Return Location"); + SalesLine.Validate(Quantity, ReturnLine.Quantity); SalesLine.Validate("Unit Price", ReturnLine."Discounted Total Amount" / ReturnLine.Quantity); end; diff --git a/Apps/W1/Shopify/app/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al index 0855c28be4..5de1dc01bb 100644 --- a/Apps/W1/Shopify/app/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Order Returns/Codeunits/ShpfyReturnsAPI.Codeunit.al @@ -32,11 +32,14 @@ codeunit 30250 "Shpfy Returns API" var GraphQLType: Enum "Shpfy GraphQL Type"; LineParameters: Dictionary of [text, Text]; + ReturnLocations: Dictionary of [BigInteger, BigInteger]; JResponse: JsonToken; JLines: JsonArray; JLine: JsonToken; begin GetReturnHeader(ReturnId); + ReturnLocations := GetReturnLocations(ReturnId); + LineParameters.Add('ReturnId', Format(ReturnId)); GraphQLType := "Shpfy GraphQL Type"::GetReturnLines; repeat @@ -48,7 +51,7 @@ codeunit 30250 "Shpfy Returns API" else LineParameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.return.returnLineItems.pageInfo.endCursor')); foreach JLine in JLines do - FillInReturnLine(ReturnId, JLine.AsObject()); + FillInReturnLine(ReturnId, JLine.AsObject(), ReturnLocations); until not JsonHelper.GetValueAsBoolean(JResponse, 'data.return.returnLineItems.pageInfo.hasNextPage'); end; @@ -81,12 +84,96 @@ codeunit 30250 "Shpfy Returns API" DataCapture.Add(Database::"Shpfy Return Header", ReturnHeader.SystemId, JResponse); end; - local procedure FillInReturnLine(ReturnId: BigInteger; JLine: JsonObject) + /// + /// Get the return locations for return lines. + /// + /// Id of the return. + /// + /// If item was restocked to multiple locations, we cannot determine the return location for the return line, + /// and the order line id will not be included in the return locations. + /// + /// Dictionary of Order Line Id and Location Id. + internal procedure GetReturnLocations(ReturnId: BigInteger) ReturnLocations: Dictionary of [BigInteger, BigInteger] + var + GraphQLType: Enum "Shpfy GraphQL Type"; + LineParameters: Dictionary of [text, Text]; + JResponse: JsonToken; + JOrders: JsonArray; + JOrder: JsonToken; + begin + LineParameters.Add('ReturnId', Format(ReturnId)); + GraphQLType := "Shpfy GraphQL Type"::GetReverseFulfillmentOrders; + repeat + JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType, LineParameters); + + 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')) + else + Parameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.return.reverseFulfillmentOrders.pageInfo.endCursor')); + + foreach JOrder in JOrders do + GetReturnLocationsFromReturnFulfillOrder(JsonHelper.GetValueAsText(JOrder, 'id'), ReturnLocations); + until not JsonHelper.GetValueAsBoolean(JResponse, 'data.return.reverseFulfillmentOrders.pageInfo.hasNextPage'); + end; + + local procedure GetReturnLocationsFromReturnFulfillOrder(FulfillOrderId: Text; var ReturnLocations: Dictionary of [BigInteger, BigInteger]) + var + GraphQLType: Enum "Shpfy GraphQL Type"; + LineParameters: Dictionary of [text, Text]; + JResponse: JsonToken; + JLines: JsonArray; + JLine: JsonToken; + begin + LineParameters.Add('FulfillOrderId', FulfillOrderId); + GraphQLType := "Shpfy GraphQL Type"::GetReverseFulfillmentOrderLines; + repeat + JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType, LineParameters); + + 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')) + else + Parameters.Add('After', JsonHelper.GetValueAsText(JResponse, 'data.reverseFulfillmentOrder.lineItems.pageInfo.endCursor')); + + foreach JLine in JLines do + CollectLocationsFromLineDispositions(JLine, ReturnLocations); + until not JsonHelper.GetValueAsBoolean(JResponse, 'data.reverseFulfillmentOrder.lineItems.pageInfo.hasNextPage'); + end; + + local procedure CollectLocationsFromLineDispositions(JLine: JsonToken; ReturnLocations: Dictionary of [BigInteger, BigInteger]) + var + OrderLineId: BigInteger; + LocationId: BigInteger; + Dispositions: JsonArray; + Disposition: JsonToken; + begin + OrderLineId := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JLine, 'fulfillmentLineItem.lineItem.id')); + + Dispositions := JsonHelper.GetJsonArray(JLine, 'dispositions'); + if Dispositions.Count = 0 then + exit; + + // 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'); + foreach Disposition in Dispositions do + if LocationId <> JsonHelper.GetValueAsBigInteger(Disposition, 'location.legacyResourceId') then + exit; + + ReturnLocations.Add(OrderLineId, LocationId); + end; + + local procedure FillInReturnLine(ReturnId: BigInteger; JLine: JsonObject; ReturnLocations: Dictionary of [BigInteger, BigInteger]) var DataCapture: Record "Shpfy Data Capture"; ReturnLine: Record "Shpfy Return Line"; ReturnLineRecordRef: RecordRef; Id: BigInteger; + ReturnLocation: BigInteger; begin Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JLine, 'id')); if not ReturnLine.Get(Id) then begin @@ -97,8 +184,13 @@ codeunit 30250 "Shpfy Returns API" ReturnLine.Insert(); end; ReturnLine."Return Reason" := ReturnEnumConvertor.ConvertToReturnReason(JsonHelper.GetValueAsText(JLine, 'returnReason')); + // 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; + 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")); diff --git a/Apps/W1/Shopify/app/src/Order Returns/Tables/ShpfyReturnLine.Table.al b/Apps/W1/Shopify/app/src/Order Returns/Tables/ShpfyReturnLine.Table.al index 9150f4d570..3598ff4855 100644 --- a/Apps/W1/Shopify/app/src/Order Returns/Tables/ShpfyReturnLine.Table.al +++ b/Apps/W1/Shopify/app/src/Order Returns/Tables/ShpfyReturnLine.Table.al @@ -112,6 +112,12 @@ table 30141 "Shpfy Return Line" FieldClass = FlowField; CalcFormula = lookup("Shpfy Order Line"."Variant Code" where("Line Id" = field("Order Line Id"))); } + field(104; "Location Id"; BigInteger) + { + Caption = 'Location Id'; + DataClassification = SystemMetadata; + Editable = false; + } } keys {