Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,8 @@ table 30144 "Shpfy FulFillment Order Line"
key(Key4; "Shopify Order Id", "Line Item Id")
{
}
key(Key5; "Shopify Location Id", "Shopify Fulfillment Order Id")
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,19 @@ codeunit 30190 "Shpfy Export Shipments"
internal procedure CreateFulfillmentOrderRequest(SalesShipmentHeader: Record "Sales Shipment Header"; Shop: Record "Shpfy Shop"; var AssignedFulfillmentOrderIds: Dictionary of [BigInteger, Code[20]]) Requests: List of [Text];
var
SalesShipmentLine: Record "Sales Shipment Line";
ShippingAgent: Record "Shipping Agent";
FulfillmentOrderLine: Record "Shpfy FulFillment Order Line";
TempFulfillmentOrderLine: Record "Shpfy FulFillment Order Line" temporary;
TrackingCompany: Enum "Shpfy Tracking Companies";
PrevFulfillmentOrderId: BigInteger;
IsHandled: Boolean;
PrevLocationId: BigInteger;
EmptyFulfillment: Boolean;
TrackingUrl: Text;
GraphQueryStart: Text;
GraphQuery: TextBuilder;
LineCount: Integer;
GraphQueries: List of [Text];
UnfulfillableOrders: List of [BigInteger];
begin
Clear(PrevFulfillmentOrderId);
PrevLocationId := -1;

SalesShipmentLine.Reset();
SalesShipmentLine.SetRange("Document No.", SalesShipmentHeader."No.");
Expand All @@ -102,46 +100,8 @@ codeunit 30190 "Shpfy Export Shipments"
until SalesShipmentLine.Next() = 0;

TempFulfillmentOrderLine.Reset();
TempFulfillmentOrderLine.SetCurrentKey("Shopify Fulfillment Order Id");
TempFulfillmentOrderLine.SetCurrentKey("Shopify Location Id", "Shopify Fulfillment Order Id");
if TempFulfillmentOrderLine.FindSet() then begin
GraphQuery.Append('{"query": "mutation {fulfillmentCreate( fulfillment: {');
if GetNotifyCustomer(Shop, SalesShipmentHeader, TempFulfillmentOrderLine."Shopify Location Id") then
GraphQuery.Append('notifyCustomer: true, ')
else
GraphQuery.Append('notifyCustomer: false, ');
if SalesShipmentHeader."Package Tracking No." <> '' then begin
GraphQuery.Append('trackingInfo: {');
if SalesShipmentHeader."Shipping Agent Code" <> '' then begin
GraphQuery.Append('company: \"');
if ShippingAgent.Get(SalesShipmentHeader."Shipping Agent Code") then
if ShippingAgent."Shpfy Tracking Company" = ShippingAgent."Shpfy Tracking Company"::" " then begin
if ShippingAgent.Name = '' then
GraphQuery.Append(ShippingAgent.Code)
else
GraphQuery.Append(ShippingAgent.Name)
end else
GraphQuery.Append(TrackingCompany.Names.Get(TrackingCompany.Ordinals.IndexOf(ShippingAgent."Shpfy Tracking Company".AsInteger())));
GraphQuery.Append('\",');
end;

GraphQuery.Append('number: \"');
GraphQuery.Append(SalesShipmentHeader."Package Tracking No.");
GraphQuery.Append('\",');
ShippingEvents.OnBeforeRetrieveTrackingUrl(SalesShipmentHeader, TrackingUrl, IsHandled);
if not IsHandled then
if ShippingAgent."Internet Address" <> '' then
TrackingUrl := ShippingAgent.GetTrackingInternetAddr(SalesShipmentHeader."Package Tracking No.");

if TrackingUrl <> '' then begin
GraphQuery.Append('url: \"');
GraphQuery.Append(TrackingUrl);
GraphQuery.Append('\"');
end;

GraphQuery.Append('}');
end;
GraphQuery.Append('lineItemsByFulfillmentOrder: [');
GraphQueryStart := GraphQuery.ToText();
EmptyFulfillment := true;
repeat
// Skip fulfillment orders that are assigned and not accepted
Expand All @@ -151,6 +111,21 @@ codeunit 30190 "Shpfy Export Shipments"
if not CanFulfillOrder(TempFulfillmentOrderLine, Shop, UnfulfillableOrders) then
continue;

// When location changes (or first non-skipped record), finalize the current mutation and start a new one
if PrevLocationId <> TempFulfillmentOrderLine."Shopify Location Id" then begin
if not EmptyFulfillment then begin
FinalizeFulfillmentQuery(GraphQuery);
GraphQueries.Add(GraphQuery.ToText());
end;
GraphQuery.Clear();
GraphQueryStart := BuildFulfillmentQueryStart(Shop, SalesShipmentHeader, TempFulfillmentOrderLine."Shopify Location Id");
GraphQuery.Append(GraphQueryStart);
Clear(PrevFulfillmentOrderId);
LineCount := 0;
EmptyFulfillment := true;
end;
PrevLocationId := TempFulfillmentOrderLine."Shopify Location Id";

EmptyFulfillment := false;

if PrevFulfillmentOrderId <> TempFulfillmentOrderLine."Shopify Fulfillment Order Id" then begin
Expand All @@ -175,23 +150,77 @@ codeunit 30190 "Shpfy Export Shipments"
LineCount += 1;
if LineCount = 250 then begin
LineCount := 0;
GraphQuery.Append(']}]})');
GraphQuery.Append('{fulfillment { legacyResourceId name createdAt updatedAt deliveredAt displayStatus estimatedDeliveryAt status totalQuantity location { legacyResourceId } trackingInfo { number url company } service { serviceName type } fulfillmentLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { id quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount }} lineItem { id isGiftCard }}}}, userErrors {field,message}}}"}');
FinalizeFulfillmentQuery(GraphQuery);
GraphQueries.Add(GraphQuery.ToText());
GraphQuery.Clear();
GraphQuery.Append(GraphQueryStart);
Clear(PrevFulfillmentOrderId);
EmptyFulfillment := true;
end;
until TempFulfillmentOrderLine.Next() = 0;
GraphQuery.Append(']}]})');
GraphQuery.Append('{fulfillment { legacyResourceId name createdAt updatedAt deliveredAt displayStatus estimatedDeliveryAt status totalQuantity location { legacyResourceId } trackingInfo { number url company } service { serviceName type } fulfillmentLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { id quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount }} lineItem { id isGiftCard }}}}, userErrors {field,message}}}"}');
if not EmptyFulfillment then
if not EmptyFulfillment then begin
FinalizeFulfillmentQuery(GraphQuery);
GraphQueries.Add(GraphQuery.ToText());
end;
end;
exit(GraphQueries);
end;
end;

local procedure BuildFulfillmentQueryStart(Shop: Record "Shpfy Shop"; SalesShipmentHeader: Record "Sales Shipment Header"; LocationId: BigInteger): Text
var
ShippingAgent: Record "Shipping Agent";
TrackingCompany: Enum "Shpfy Tracking Companies";
IsHandled: Boolean;
TrackingUrl: Text;
GraphQuery: TextBuilder;
begin
GraphQuery.Append('{"query": "mutation {fulfillmentCreate( fulfillment: {');
if GetNotifyCustomer(Shop, SalesShipmentHeader, LocationId) then
GraphQuery.Append('notifyCustomer: true, ')
else
GraphQuery.Append('notifyCustomer: false, ');
if SalesShipmentHeader."Package Tracking No." <> '' then begin
GraphQuery.Append('trackingInfo: {');
if SalesShipmentHeader."Shipping Agent Code" <> '' then begin
GraphQuery.Append('company: \"');
if ShippingAgent.Get(SalesShipmentHeader."Shipping Agent Code") then
if ShippingAgent."Shpfy Tracking Company" = ShippingAgent."Shpfy Tracking Company"::" " then begin
if ShippingAgent.Name = '' then
GraphQuery.Append(ShippingAgent.Code)
else
GraphQuery.Append(ShippingAgent.Name)
end else
GraphQuery.Append(TrackingCompany.Names.Get(TrackingCompany.Ordinals.IndexOf(ShippingAgent."Shpfy Tracking Company".AsInteger())));
GraphQuery.Append('\",');
end;

GraphQuery.Append('number: \"');
GraphQuery.Append(SalesShipmentHeader."Package Tracking No.");
GraphQuery.Append('\",');
ShippingEvents.OnBeforeRetrieveTrackingUrl(SalesShipmentHeader, TrackingUrl, IsHandled);
if not IsHandled then
if ShippingAgent."Internet Address" <> '' then
TrackingUrl := ShippingAgent.GetTrackingInternetAddr(SalesShipmentHeader."Package Tracking No.");

if TrackingUrl <> '' then begin
GraphQuery.Append('url: \"');
GraphQuery.Append(TrackingUrl);
GraphQuery.Append('\"');
end;

GraphQuery.Append('}');
end;
GraphQuery.Append('lineItemsByFulfillmentOrder: [');
exit(GraphQuery.ToText());
end;

local procedure FinalizeFulfillmentQuery(var GraphQuery: TextBuilder)
begin
GraphQuery.Append(']}]})');
GraphQuery.Append('{fulfillment { legacyResourceId name createdAt updatedAt deliveredAt displayStatus estimatedDeliveryAt status totalQuantity location { legacyResourceId } trackingInfo { number url company } service { serviceName type } fulfillmentLineItems(first: 10) { pageInfo { endCursor hasNextPage } nodes { id quantity originalTotalSet { presentmentMoney { amount } shopMoney { amount }} lineItem { id isGiftCard }}}}, userErrors {field,message}}}"}');
end;

local procedure FindFulfillmentOrderLines(SalesShipmentHeader: Record "Sales Shipment Header"; SalesShipmentLine: Record "Sales Shipment Line"; Shop: Record "Shpfy Shop"; var FulfillmentOrderLine: Record "Shpfy FulFillment Order Line"; var TempFulfillmentOrderLine: Record "Shpfy FulFillment Order Line" temporary; var AssignedFulfillmentOrderIds: Dictionary of [BigInteger, Code[20]])
var
RemainingQtyToFulfill: Decimal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,75 @@ codeunit 139559 "Shpfy Shipping Helper"
exit(FulfillmentOrderHeader);
end;

internal procedure CreateOrderLine(ShopifyOrderId: BigInteger; LocationId: BigInteger; DeliveryMethodType: Enum "Shpfy Delivery Method Type"; LineId: BigInteger; VariantId: BigInteger; ProductId: BigInteger; Qty: Integer)
var
OrderLine: Record "Shpfy Order Line";
begin
Clear(OrderLine);
OrderLine."Shopify Order Id" := ShopifyOrderId;
OrderLine."Shopify Product Id" := ProductId;
OrderLine."Shopify Variant Id" := VariantId;
OrderLine."Line Id" := LineId;
OrderLine.Quantity := Qty;
OrderLine."Location Id" := LocationId;
OrderLine."Delivery Method Type" := DeliveryMethodType;
OrderLine.Insert();
end;

internal procedure CreateSalesShipmentLine(DocumentNo: Code[20]; ShpfyOrderLineId: BigInteger; Qty: Decimal; LineNo: Integer)
var
SalesShipmentLine: Record "Sales Shipment Line";
Any: Codeunit Any;
begin
Any.SetDefaultSeed();
Clear(SalesShipmentLine);
SalesShipmentLine."Document No." := DocumentNo;
SalesShipmentLine."Line No." := LineNo;
SalesShipmentLine.Type := SalesShipmentLine.Type::Item;
SalesShipmentLine."No." := CopyStr(Any.AlphanumericText(MaxStrLen(SalesShipmentLine."No.")), 1, MaxStrLen(SalesShipmentLine."No."));
SalesShipmentLine."Shpfy Order Line Id" := ShpfyOrderLineId;
SalesShipmentLine.Quantity := Qty;
SalesShipmentLine.Insert();
end;

internal procedure CreateShopifyFulfillmentOrderForLocation(ShopifyOrderId: BigInteger; LocationId: BigInteger; DeliveryMethodType: Enum "Shpfy Delivery Method Type"): Record "Shpfy FulFillment Order Header"
var
OrderLine: Record "Shpfy Order Line";
FulfillmentOrderHeader: Record "Shpfy FulFillment Order Header";
FulfillmentOrderLine: Record "Shpfy FulFillment Order Line";
Any: Codeunit Any;
OrderLineCount: Integer;
begin
Any.SetDefaultSeed();
Clear(FulfillmentOrderHeader);
FulfillmentOrderHeader."Shopify Fulfillment Order Id" := Any.IntegerInRange(10000, 99999);
FulfillmentOrderHeader."Shopify Order Id" := ShopifyOrderId;
FulfillmentOrderHeader."Shopify Location Id" := LocationId;
FulfillmentOrderHeader."Delivery Method Type" := DeliveryMethodType;
FulfillmentOrderHeader.Insert();

OrderLine.Reset();
OrderLine.SetRange("Shopify Order Id", ShopifyOrderId);
OrderLine.SetRange("Location Id", LocationId);
if OrderLine.FindSet() then
repeat
OrderLineCount += 1;
Clear(FulfillmentOrderLine);
FulfillmentOrderLine."Shopify Fulfillment Order Id" := FulfillmentOrderHeader."Shopify Fulfillment Order Id";
FulfillmentOrderLine."Shopify Fulfillm. Ord. Line Id" := FulfillmentOrderHeader."Shopify Fulfillment Order Id" * 100 + OrderLineCount;
FulfillmentOrderLine."Shopify Order Id" := ShopifyOrderId;
FulfillmentOrderLine."Shopify Product Id" := OrderLine."Shopify Product Id";
FulfillmentOrderLine."Shopify Variant Id" := OrderLine."Shopify Variant Id";
FulfillmentOrderLine."Remaining Quantity" := OrderLine.Quantity;
FulfillmentOrderLine."Shopify Location Id" := LocationId;
FulfillmentOrderLine."Delivery Method Type" := DeliveryMethodType;
FulfillmentOrderLine."Line Item Id" := OrderLine."Line Id";
FulfillmentOrderLine.Insert();
until OrderLine.Next() = 0;

exit(FulfillmentOrderHeader);
end;

internal procedure CreateRandomSalesShipment(var SalesShipmentHeader: Record "Sales Shipment Header"; ShopifyOrderId: BigInteger)
var
SalesShipmentLine: Record "Sales Shipment Line";
Expand Down
73 changes: 73 additions & 0 deletions src/Apps/W1/Shopify/Test/Shipping/ShpfyShippingTest.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,79 @@ codeunit 139606 "Shpfy Shipping Test"
LibraryAssert.AreEqual(0, FulfillmentRequests.Count, 'FulfillmentRequest count check');
end;

[Test]
procedure UnitTestExportShipmentMultipleLocations()
var
SalesShipmentHeader: Record "Sales Shipment Header";
OrderHeader: Record "Shpfy Order Header";
FulfillmentOrderHeaderA: Record "Shpfy FulFillment Order Header";
FulfillmentOrderHeaderB: Record "Shpfy FulFillment Order Header";
ExportShipments: Codeunit "Shpfy Export Shipments";
ShippingHelper: Codeunit "Shpfy Shipping Helper";
DeliveryMethodType: Enum "Shpfy Delivery Method Type";
FulfillmentRequest: Text;
FulfillmentRequests: List of [Text];
AssignedFulfillmentOrderIds: Dictionary of [BigInteger, Code[20]];
ShopifyOrderId: BigInteger;
LocationIdA: BigInteger;
LocationIdB: BigInteger;
LineItemId: BigInteger;
VariantId: BigInteger;
ProductId: BigInteger;
begin
// [SCENARIO] A shipment spanning two Shopify locations must produce separate fulfillment requests per location
// [GIVEN] One item (qty 17) split across two locations: 10 at Location A, 7 at Location B
Initialize();
Any.SetDefaultSeed();
LocationIdA := Any.IntegerInRange(10000, 49999);
LocationIdB := Any.IntegerInRange(50000, 99999);
DeliveryMethodType := DeliveryMethodType::Shipping;
LineItemId := Any.IntegerInRange(10000, 99999);
VariantId := Any.IntegerInRange(10000, 99999);
ProductId := Any.IntegerInRange(10000, 99999);

Clear(OrderHeader);
ShopifyOrderId := Any.IntegerInRange(10000, 99999);
OrderHeader."Shopify Order Id" := ShopifyOrderId;
OrderHeader.Insert();

// Same item split across two locations with different quantities
ShippingHelper.CreateOrderLine(ShopifyOrderId, LocationIdA, DeliveryMethodType, LineItemId, VariantId, ProductId, 10);
ShippingHelper.CreateOrderLine(ShopifyOrderId, LocationIdB, DeliveryMethodType, LineItemId + 1, VariantId, ProductId, 7);

// Create fulfillment orders per location
FulfillmentOrderHeaderA := ShippingHelper.CreateShopifyFulfillmentOrderForLocation(ShopifyOrderId, LocationIdA, DeliveryMethodType);
FulfillmentOrderHeaderB := ShippingHelper.CreateShopifyFulfillmentOrderForLocation(ShopifyOrderId, LocationIdB, DeliveryMethodType);

// [GIVEN] A shipment of qty 9 that spans both locations (needs items from both fulfillment orders)
Clear(SalesShipmentHeader);
SalesShipmentHeader."No." := CopyStr(Any.AlphanumericText(MaxStrLen(SalesShipmentHeader."No.")), 1, MaxStrLen(SalesShipmentHeader."No."));
SalesShipmentHeader."Shpfy Order Id" := ShopifyOrderId;
SalesShipmentHeader."Package Tracking No." := CopyStr(Any.AlphanumericText(MaxStrLen(SalesShipmentHeader."Package Tracking No.")), 1, MaxStrLen(SalesShipmentHeader."Package Tracking No."));
SalesShipmentHeader.Insert();

// Shipment line for item at Location A: ship 5 out of 10
ShippingHelper.CreateSalesShipmentLine(SalesShipmentHeader."No.", LineItemId, 5, 10000);
// Shipment line for item at Location B: ship 4 out of 7
ShippingHelper.CreateSalesShipmentLine(SalesShipmentHeader."No.", LineItemId + 1, 4, 20000);

// [WHEN] Invoke the function CreateFulfillmentOrderRequest()
FulfillmentRequests := ExportShipments.CreateFulfillmentOrderRequest(SalesShipmentHeader, Shop, AssignedFulfillmentOrderIds);

// [THEN] Two separate requests are created, one per location
LibraryAssert.AreEqual(2, FulfillmentRequests.Count, 'Should produce two fulfillment requests, one per location');

// [THEN] First request contains only Location A's fulfillment order
FulfillmentRequests.Get(1, FulfillmentRequest);
LibraryAssert.IsTrue(FulfillmentRequest.Contains(Format(FulfillmentOrderHeaderA."Shopify Fulfillment Order Id")), 'First request should contain Location A fulfillment order');
LibraryAssert.IsFalse(FulfillmentRequest.Contains(Format(FulfillmentOrderHeaderB."Shopify Fulfillment Order Id")), 'First request should not contain Location B fulfillment order');

// [THEN] Second request contains only Location B's fulfillment order
FulfillmentRequests.Get(2, FulfillmentRequest);
LibraryAssert.IsTrue(FulfillmentRequest.Contains(Format(FulfillmentOrderHeaderB."Shopify Fulfillment Order Id")), 'Second request should contain Location B fulfillment order');
LibraryAssert.IsFalse(FulfillmentRequest.Contains(Format(FulfillmentOrderHeaderA."Shopify Fulfillment Order Id")), 'Second request should not contain Location A fulfillment order');
end;

local procedure Initialize()
var
CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
Expand Down
Loading