diff --git a/Apps/W1/Shopify/app/app.json b/Apps/W1/Shopify/app/app.json
index 3ddb14510c..ab77786c2e 100644
--- a/Apps/W1/Shopify/app/app.json
+++ b/Apps/W1/Shopify/app/app.json
@@ -21,7 +21,7 @@
"idRanges": [
{
"from": 30100,
- "to": 30360
+ "to": 30370
}
],
"internalsVisibleTo": [
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 4a9f745590..edc57c53c5 100644
--- a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al
+++ b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al
@@ -245,6 +245,11 @@ page 30101 "Shpfy Shop Card"
ApplicationArea = All;
ToolTip = 'Specifies the status of a product in Shopify via the sync when an item is removed in Shopify or an item is blocked in Business Central.';
}
+ field("Items Mapped to Products"; Rec."Items Mapped to Products")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies if only the items that are mapped to Shopify products/Shopify variants are synchronized from Posted Sales Invoices to Shopify.';
+ }
}
group(PriceSynchronization)
{
@@ -517,6 +522,11 @@ page 30101 "Shpfy Shop Card"
end;
}
#endif
+ field("Posted Invoice Sync"; Rec."Posted Invoice Sync")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies whether the posted sales invoices can be synchronized to Shopify.';
+ }
}
group(ReturnsAndRefunds)
{
@@ -650,6 +660,19 @@ page 30101 "Shpfy Shop Card"
RunPageLink = "Shop Code" = field(Code);
ToolTip = 'Maps the Shopify payment methods to the related payment methods and prioritize them.';
}
+ action(PaymentTerms)
+ {
+ ApplicationArea = All;
+ Caption = 'Payment Terms Mapping';
+ Image = SuggestPayment;
+ Promoted = true;
+ PromotedCategory = Category4;
+ PromotedIsBig = true;
+ PromotedOnly = true;
+ RunObject = page "Shpfy Payment Terms Mapping";
+ RunPageLink = "Shop Code" = field(Code);
+ ToolTip = 'Maps the Shopify payment terms to the related payment terms and prioritize them.';
+ }
action(Orders)
{
ApplicationArea = All;
@@ -986,6 +1009,26 @@ page 30101 "Shpfy Shop Card"
Report.Run(Report::"Shpfy Sync Shipm. to Shopify");
end;
}
+ action(SyncPostedSalesInvoices)
+ {
+ ApplicationArea = All;
+ Ellipsis = true;
+ Caption = 'Sync Posted Sales Invoices';
+ Image = Export;
+ Promoted = true;
+ PromotedCategory = Category5;
+ PromotedIsBig = true;
+ PromotedOnly = true;
+ ToolTip = 'Synchronize posted sales invoices to Shopify. Synchronization will be performed only if the Posted Invoice Sync field is enabled in the Shopify shop.';
+
+ trigger OnAction();
+ var
+ ExportInvoicetoShpfy: Report "Shpfy Sync Invoices to Shpfy";
+ begin
+ ExportInvoicetoShpfy.SetShop(Rec.Code);
+ ExportInvoicetoShpfy.Run();
+ end;
+ }
action(SyncDisputes)
{
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 05bf12e5ad..c90443c731 100644
--- a/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al
+++ b/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al
@@ -787,6 +787,14 @@ table 30102 "Shpfy Shop"
{
DataClassification = SystemMetadata;
}
+ field(201; "Items Mapped to Products"; Boolean)
+ {
+ Caption = 'Items Must be Mapped to Products';
+ }
+ field(202; "Posted Invoice Sync"; Boolean)
+ {
+ Caption = 'Posted Invoice Sync';
+ }
}
keys
diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al
new file mode 100644
index 0000000000..921394524d
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al
@@ -0,0 +1,27 @@
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Codeunit Shpfy GQL DraftOrderComplete (ID 30341) implements Interface Shpfy IGraphQL.
+///
+codeunit 30341 "Shpfy GQL DraftOrderComplete" implements "Shpfy IGraphQL"
+{
+ Access = Internal;
+
+ ///
+ /// GetGraphQL.
+ ///
+ /// Return value of type Text.
+ internal procedure GetGraphQL(): Text
+ begin
+ exit('{"query": "mutation {draftOrderComplete(id: \"gid://shopify/DraftOrder/{{DraftOrderId}}\") { draftOrder { order { legacyResourceId, name }} userErrors { field, message }}}"}');
+ end;
+
+ ///
+ /// GetExpectedCost.
+ ///
+ /// Return value of type Integer.
+ internal procedure GetExpectedCost(): Integer
+ begin
+ exit(11);
+ end;
+}
diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLFulfillOrder.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLFulfillOrder.Codeunit.al
new file mode 100644
index 0000000000..8e85b8b2a6
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLFulfillOrder.Codeunit.al
@@ -0,0 +1,27 @@
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Codeunit Shpfy GQL Fulfill Order (ID 30355) implements Interface Shpfy IGraphQL.
+///
+codeunit 30355 "Shpfy GQL Fulfill Order" implements "Shpfy IGraphQL"
+{
+ Access = Internal;
+
+ ///
+ /// GetGraphQL.
+ ///
+ /// Return value of type Text.
+ internal procedure GetGraphQL(): Text
+ begin
+ exit('{"query": "mutation { fulfillmentCreateV2 ( fulfillment: { lineItemsByFulfillmentOrder: [{ fulfillmentOrderId: \"gid://shopify/FulfillmentOrder/{{FulfillmentOrderId}}\" }] }) { fulfillment {id, status} userErrors {field, message}}}"}');
+ end;
+
+ ///
+ /// GetExpectedCost.
+ ///
+ /// Return value of type Integer.
+ internal procedure GetExpectedCost(): Integer
+ begin
+ exit(10);
+ end;
+}
diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLGetFulfillments.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLGetFulfillments.Codeunit.al
new file mode 100644
index 0000000000..adb8d1562e
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLGetFulfillments.Codeunit.al
@@ -0,0 +1,26 @@
+///
+/// Codeunit Shpfy GQL Get Fulfillments (ID 30356) implements Interface Shpfy IGraphQL.
+///
+codeunit 30356 "Shpfy GQL Get Fulfillments" implements "Shpfy IGraphQL"
+{
+ Access = Internal;
+
+ ///
+ /// GetGraphQL.
+ ///
+ /// Return value of type Text.
+ internal procedure GetGraphQL(): Text
+ begin
+ exit('{"query": "{order (id: \"gid://shopify/Order/{{OrderId}}\") { fulfillmentOrders ( first: {{NumberOfOrders}}) { nodes { id }}}}"}');
+ end;
+
+ ///
+ /// GetExpectedCost.
+ ///
+ /// Return value of type Integer.
+ internal procedure GetExpectedCost(): Integer
+ begin
+ exit(6);
+ end;
+}
+
diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLPaymentTerms.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLPaymentTerms.Codeunit.al
new file mode 100644
index 0000000000..21fc47a25f
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLPaymentTerms.Codeunit.al
@@ -0,0 +1,28 @@
+
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Codeunit Shpfy GQL Payment Terms (ID 30357) implements Interface Shpfy IGraphQL.
+///
+codeunit 30357 "Shpfy GQL Payment Terms" implements "Shpfy IGraphQL"
+{
+ Access = Internal;
+
+ ///
+ /// GetGraphQL.
+ ///
+ /// Return value of type Text.
+ internal procedure GetGraphQL(): Text
+ begin
+ exit('{"query": "{paymentTermsTemplates{id name paymentTermsType dueInDays description translatedName}}"}');
+ end;
+
+ ///
+ /// GetExpectedCost.
+ ///
+ /// Return value of type Integer.
+ internal procedure GetExpectedCost(): Integer
+ begin
+ exit(1);
+ end;
+}
diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLShopLocales.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLShopLocales.Codeunit.al
index 004ba743cb..27a145ce33 100644
--- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLShopLocales.Codeunit.al
+++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLShopLocales.Codeunit.al
@@ -1,6 +1,6 @@
namespace Microsoft.Integration.Shopify;
-codeunit 30168 "Shpfy GQL ShopLocales" implements "Shpfy IGraphQL"
+codeunit 30358 "Shpfy GQL ShopLocales" implements "Shpfy IGraphQL"
{
internal procedure GetGraphQL(): Text
diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLTranslationsRegister.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLTranslationsRegister.Codeunit.al
index 4b571c629a..e7b4167fae 100644
--- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLTranslationsRegister.Codeunit.al
+++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLTranslationsRegister.Codeunit.al
@@ -1,6 +1,6 @@
namespace Microsoft.Integration.Shopify;
-codeunit 30159 "Shpfy GQL TranslationsRegister" implements "Shpfy IGraphQL"
+codeunit 30359 "Shpfy GQL TranslationsRegister" implements "Shpfy IGraphQL"
{
internal procedure GetGraphQL(): Text
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 7659605415..76c143c034 100644
--- a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al
+++ b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al
@@ -405,6 +405,26 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL"
Caption = 'Get Order Transactions';
Implementation = "Shpfy IGraphQL" = "Shpfy GQL OrderTransactions";
}
+ value(80; DraftOrderComplete)
+ {
+ Caption = 'Draft Order Complete';
+ Implementation = "Shpfy IGraphQL" = "Shpfy GQL DraftOrderComplete";
+ }
+ value(81; FulfillOrder)
+ {
+ Caption = 'Fulfill Order';
+ Implementation = "Shpfy IGraphQL" = "Shpfy GQL Fulfill Order";
+ }
+ value(82; GetPaymentTerms)
+ {
+ Caption = 'Get Payment Terms';
+ Implementation = "Shpfy IGraphQL" = "Shpfy GQL Payment Terms";
+ }
+ value(83; GetFulfillmentOrderIds)
+ {
+ Caption = 'Get Fulfillments';
+ Implementation = "Shpfy IGraphQL" = "Shpfy GQL Get Fulfillments";
+ }
value(85; ProductVariantDelete)
{
Caption = 'Product Variant Delete';
diff --git a/Apps/W1/Shopify/app/src/Integration/Codeunits/ShpfyAuthenticationMgt.Codeunit.al b/Apps/W1/Shopify/app/src/Integration/Codeunits/ShpfyAuthenticationMgt.Codeunit.al
index 7a0c1ed557..3770859591 100644
--- a/Apps/W1/Shopify/app/src/Integration/Codeunits/ShpfyAuthenticationMgt.Codeunit.al
+++ b/Apps/W1/Shopify/app/src/Integration/Codeunits/ShpfyAuthenticationMgt.Codeunit.al
@@ -15,7 +15,7 @@ codeunit 30199 "Shpfy Authentication Mgt."
var
// https://shopify.dev/api/usage/access-scopes
- ScopeTxt: Label 'write_orders,read_all_orders,write_assigned_fulfillment_orders,read_checkouts,write_customers,read_discounts,write_files,write_merchant_managed_fulfillment_orders,write_fulfillments,write_inventory,read_locations,read_payment_terms,write_products,write_shipping,read_shopify_payments_disputes,read_shopify_payments_payouts,write_returns,write_translations,write_third_party_fulfillment_orders,write_order_edits,write_companies,write_publications,read_locales', Locked = true;
+ ScopeTxt: Label 'write_orders,read_all_orders,write_assigned_fulfillment_orders,read_checkouts,write_customers,read_discounts,write_files,write_merchant_managed_fulfillment_orders,write_fulfillments,write_inventory,read_locations,write_products,write_shipping,read_shopify_payments_disputes,read_shopify_payments_payouts,write_returns,write_translations,write_third_party_fulfillment_orders,write_order_edits,write_companies,write_publications,read_payment_terms,write_payment_terms,write_draft_orders,read_locales', Locked = true;
ShopifyAPIKeyAKVSecretNameLbl: Label 'ShopifyApiKey', Locked = true;
ShopifyAPISecretAKVSecretNameLbl: Label 'ShopifyApiSecret', Locked = true;
MissingAPIKeyTelemetryTxt: Label 'The api key has not been initialized.', Locked = true;
diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al
new file mode 100644
index 0000000000..0e67fc2d40
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al
@@ -0,0 +1,386 @@
+namespace Microsoft.Integration.Shopify;
+
+using Microsoft.Sales.Comment;
+using Microsoft.Sales.History;
+using Microsoft.Finance.Currency;
+using Microsoft.Inventory.Item.Attribute;
+using Microsoft.Inventory.Item;
+
+///
+/// Codeunit Draft Orders API (ID 30159).
+///
+codeunit 30159 "Shpfy Draft Orders API"
+{
+ Access = Internal;
+
+ var
+ ShpfyShop: Record "Shpfy Shop";
+ ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ ShpfyJsonHelper: Codeunit "Shpfy Json Helper";
+
+ ///
+ /// Creates a draft order in shopify by constructing and sending a graphQL request.
+ ///
+ /// Header information for a shopify order.
+ /// Line items for a shopify order.
+ /// Tax lines for a shopify order.
+ /// Unique id of the created draft order in shopify.
+ internal procedure CreateDraftOrder(
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ var TempShpfyOrderLine: Record "Shpfy Order Line" temporary;
+ var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]
+ ): BigInteger
+ var
+ DraftOrderId: BigInteger;
+ GraphQuery: TextBuilder;
+ begin
+ GraphQuery := CreateDraftOrderGQLRequest(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines);
+ DraftOrderId := SendDraftOrderGraphQLRequest(GraphQuery);
+ exit(DraftOrderId);
+ end;
+
+ ///
+ /// Completes a draft order in shopify by converting it to an order.
+ ///
+ /// Draft order id that needs to be completed.
+ /// Json response of a created order in shopify.
+ internal procedure CompleteDraftOrder(DraftOrderId: BigInteger): JsonToken
+ var
+ GraphQLType: Enum "Shpfy GraphQL Type";
+ Parameters: Dictionary of [Text, Text];
+ JResponse: JsonToken;
+ begin
+ GraphQLType := "Shpfy GraphQL Type"::DraftOrderComplete;
+ Parameters.Add('DraftOrderId', Format(DraftOrderId));
+ JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters);
+ exit(JResponse);
+ end;
+
+ ///
+ /// Sets a global shopify shop to be used for draft orders api functionality.
+ ///
+ /// Shopify shop code to be set.
+ internal procedure SetShop(ShopCode: Code[20])
+ begin
+ Clear(ShpfyShop);
+ ShpfyShop.Get(ShopCode);
+ ShpfyCommunicationMgt.SetShop(ShpfyShop);
+ end;
+
+ local procedure CreateDraftOrderGQLRequest(
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ var TempShpfyOrderLine: Record "Shpfy Order Line" temporary;
+ var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]
+ ): TextBuilder
+ var
+ GraphQuery: TextBuilder;
+ begin
+ GraphQuery.Append('{"query":"mutation {draftOrderCreate(input: {');
+ if TempShpfyOrderHeader.Email <> '' then begin
+ GraphQuery.Append('email: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader.Email));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Phone No." <> '' then begin
+ GraphQuery.Append('phone: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Phone No."));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Currency Code" <> '' then begin
+ GraphQuery.Append('presentmentCurrencyCode: ');
+ GraphQuery.Append(Format(GetISOCode(TempShpfyOrderHeader."Currency Code")));
+ end;
+ if TempShpfyOrderHeader."Discount Amount" <> 0 then
+ AddDiscountAmountToGraphQuery(GraphQuery, TempShpfyOrderHeader."Discount Amount", 'Invoice Discount Amount');
+
+ GraphQuery.Append(', taxExempt: true');
+
+ AddShippingAddressToGraphQuery(GraphQuery, TempShpfyOrderHeader);
+ AddBillingAddressToGraphQuery(GraphQuery, TempShpfyOrderHeader);
+ AddNote(GraphQuery, TempShpfyOrderHeader);
+ if TempShpfyOrderHeader.Unpaid then
+ AddPaymentTerms(GraphQuery, TempShpfyOrderHeader);
+
+ AddLineItemsToGraphQuery(GraphQuery, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines);
+
+ GraphQuery.Append('}) {draftOrder { legacyResourceId } userErrors {field, message}}');
+ GraphQuery.Append('}"}');
+
+ exit(GraphQuery);
+ end;
+
+ local procedure SendDraftOrderGraphQLRequest(GraphQuery: TextBuilder): BigInteger
+ var
+ DraftOrderId: BigInteger;
+ JResponse: JsonToken;
+ begin
+ JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQuery.ToText());
+ DraftOrderId := ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderCreate.draftOrder.legacyResourceId');
+ exit(DraftOrderId);
+ end;
+
+ local procedure AddShippingAddressToGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary)
+ begin
+ GraphQuery.Append(', shippingAddress: {');
+ if TempShpfyOrderHeader."Ship-to Address" <> '' then begin
+ GraphQuery.Append('address1: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Ship-to Address"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Ship-to Address 2" <> '' then begin
+ GraphQuery.Append(', address2: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Ship-to Address 2"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Ship-to City" <> '' then begin
+ GraphQuery.Append(', city: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Ship-to City"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Ship-to Country/Region Code" <> '' then begin
+ GraphQuery.Append(', countryCode: ');
+ GraphQuery.Append(TempShpfyOrderHeader."Ship-to Country/Region Code");
+ end;
+ if TempShpfyOrderHeader."Ship-to Post Code" <> '' then begin
+ GraphQuery.Append(', zip: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Ship-to Post Code"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Ship-to Name" <> '' then begin
+ GraphQuery.Append(', firstName: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Ship-to Name"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Ship-to Name 2" <> '' then begin
+ GraphQuery.Append(', lastName: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Ship-to Name 2"));
+ GraphQuery.Append('\"');
+ end;
+ GraphQuery.Append('}');
+ end;
+
+ local procedure AddBillingAddressToGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary)
+ begin
+ GraphQuery.Append(', billingAddress: {');
+ if TempShpfyOrderHeader."Bill-to Address" <> '' then begin
+ GraphQuery.Append('address1: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Bill-to Address"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Bill-to Address 2" <> '' then begin
+ GraphQuery.Append(', address2: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Bill-to Address 2"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Bill-to City" <> '' then begin
+ GraphQuery.Append(', city: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Bill-to City"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Bill-to Country/Region Code" <> '' then begin
+ GraphQuery.Append(', countryCode: ');
+ GraphQuery.Append(TempShpfyOrderHeader."Bill-to Country/Region Code");
+ end;
+ if TempShpfyOrderHeader."Bill-to Post Code" <> '' then begin
+ GraphQuery.Append(', zip: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Bill-to Post Code"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Bill-to Name" <> '' then begin
+ GraphQuery.Append(', firstName: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Bill-to Name"));
+ GraphQuery.Append('\"');
+ end;
+ if TempShpfyOrderHeader."Bill-to Name 2" <> '' then begin
+ GraphQuery.Append(', lastName: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderHeader."Bill-to Name 2"));
+ GraphQuery.Append('\"');
+ end;
+ GraphQuery.Append('}');
+ end;
+
+ local procedure AddLineItemsToGraphQuery(
+ var GraphQuery: TextBuilder;
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ var TempShpfyOrderLine: Record "Shpfy Order Line" temporary;
+ var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]
+ )
+ var
+ TaxTitle: Text;
+ begin
+ TempShpfyOrderLine.SetRange("Shopify Order Id", TempShpfyOrderHeader."Shopify Order Id");
+ if TempShpfyOrderLine.FindSet(false) then begin
+ GraphQuery.Append(', lineItems: [');
+ repeat
+ GraphQuery.Append('{');
+ GraphQuery.Append('title: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TempShpfyOrderLine.Description));
+ GraphQuery.Append('\"');
+
+ if TempShpfyOrderLine."Shopify Variant Id" <> 0 then begin
+ GraphQuery.Append(', variantId: \"gid://shopify/ProductVariant/');
+ GraphQuery.Append(Format(TempShpfyOrderLine."Shopify Variant Id"));
+ GraphQuery.Append('\"');
+
+ AddItemAttributes(GraphQuery, TempShpfyOrderLine."Item No.");
+ end;
+
+ GraphQuery.Append(', quantity: ');
+ GraphQuery.Append(Format(TempShpfyOrderLine.Quantity, 0, 9));
+
+ GraphQuery.Append(', originalUnitPrice :');
+ GraphQuery.Append(Format(TempShpfyOrderLine."Unit Price", 0, 9));
+
+ GraphQuery.Append('},');
+ until TempShpfyOrderLine.Next() = 0;
+
+ foreach TaxTitle in ShpfyOrderTaxLines.Keys() do begin
+ GraphQuery.Append('{');
+ GraphQuery.Append('title: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(TaxTitle));
+ GraphQuery.Append('\"');
+
+ GraphQuery.Append(', quantity: ');
+ GraphQuery.Append(Format(1, 0, 9));
+
+ GraphQuery.Append(', originalUnitPrice: ');
+ GraphQuery.Append(Format(ShpfyOrderTaxLines.Get(TaxTitle), 0, 9));
+
+ GraphQuery.Append('},');
+ end;
+ GraphQuery.Remove(GraphQuery.Length(), 1);
+ end;
+ GraphQuery.Append(']');
+ end;
+
+ local procedure AddDiscountAmountToGraphQuery(var GraphQuery: TextBuilder; DiscountAmount: Decimal; DiscountTitle: Text)
+ begin
+ GraphQuery.Append(', appliedDiscount: {');
+ GraphQuery.Append('description: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(DiscountTitle));
+ GraphQuery.Append('\"');
+
+ GraphQuery.Append(', value: ');
+ GraphQuery.Append(Format(DiscountAmount, 0, 9));
+
+ GraphQuery.Append(', valueType: ');
+ GraphQuery.Append('FIXED_AMOUNT');
+
+ GraphQuery.Append(', title: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(DiscountTitle));
+ GraphQuery.Append('\"');
+
+ GraphQuery.Append('}');
+ end;
+
+ local procedure AddNote(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary)
+ var
+ SalesCommentLine: Record "Sales Comment Line";
+ NotesTextBuilder: TextBuilder;
+ begin
+ SalesCommentLine.SetRange("Document Type", SalesCommentLine."Document Type"::"Posted Invoice");
+ SalesCommentLine.SetRange("No.", TempShpfyOrderHeader."Sales Invoice No.");
+
+ if SalesCommentLine.FindSet() then begin
+ GraphQuery.Append(', note: \"');
+ repeat
+ NotesTextBuilder.Append(SalesCommentLine.Comment + '\n');
+ until SalesCommentLine.Next() = 0;
+ GraphQuery.Append(NotesTextBuilder.ToText());
+ GraphQuery.Append('\"');
+ end;
+ end;
+
+ local procedure AddPaymentTerms(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary)
+ var
+ SalesInvoiceHeader: Record "Sales Invoice Header";
+ ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ DueAtDateTime: DateTime;
+ IssuedAtDateTime: DateTime;
+ begin
+ if not ShopifyPaymentTermsExists(ShpfyPaymentTerms, TempShpfyOrderHeader, SalesInvoiceHeader) then
+ exit;
+
+ GraphQuery.Append(', paymentTerms: {');
+ GraphQuery.Append('paymentTermsTemplateId: \"gid://shopify/PaymentTermsTemplate/');
+ GraphQuery.Append(Format(ShpfyPaymentTerms.Id));
+ GraphQuery.Append('\"');
+
+ Evaluate(IssuedAtDateTime, Format(SalesInvoiceHeader."Document Date"));
+ Evaluate(DueAtDateTime, Format(SalesInvoiceHeader."Due Date"));
+
+ GraphQuery.Append(', paymentSchedules: {');
+ if ShpfyPaymentTerms.Type = 'FIXED' then begin
+ GraphQuery.Append('dueAt: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(DueAtDateTime, 0, 9)));
+ GraphQuery.Append('\"');
+ end else
+ if ShpfyPaymentTerms.Type = 'NET' then begin
+ GraphQuery.Append(', issuedAt: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(IssuedAtDateTime, 0, 9)));
+ GraphQuery.Append('\"');
+ end;
+
+ GraphQuery.Append('}}');
+ end;
+
+ local procedure ShopifyPaymentTermsExists(
+ var ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ var SalesInvoiceHeader: Record "Sales Invoice Header"
+ ): Boolean
+ begin
+ SalesInvoiceHeader.Get(TempShpfyOrderHeader."Sales Invoice No.");
+ ShpfyPaymentTerms.SetRange("Payment Terms Code", SalesInvoiceHeader."Payment Terms Code");
+ ShpfyPaymentTerms.SetRange("Shop Code", ShpfyShop.Code);
+
+ if not ShpfyPaymentTerms.FindFirst() then begin
+ ShpfyPaymentTerms.SetRange("Payment Terms Code");
+ ShpfyPaymentTerms.SetRange("Is Primary", true);
+
+ if not ShpfyPaymentTerms.FindFirst() then
+ exit(false);
+ end;
+
+ exit(true);
+ end;
+
+ local procedure GetISOCode(CurrencyCode: Code[10]): Code[3]
+ var
+ Currency: Record Currency;
+ begin
+ Currency.Get(CurrencyCode);
+ exit(Currency."ISO Code");
+ end;
+
+ local procedure AddItemAttributes(var GraphQuery: TextBuilder; ItemNo: Code[20])
+ var
+ Item: Record Item;
+ ItemAttribute: Record "Item Attribute";
+ ItemAttributeValue: Record "Item Attribute Value";
+ ItemAttributeValueMapping: Record "Item Attribute Value Mapping";
+ begin
+ Item.Get(ItemNo);
+ ItemAttributeValueMapping.SetRange("Table ID", Database::Item);
+ ItemAttributeValueMapping.SetRange("No.", ItemNo);
+ if ItemAttributeValueMapping.FindSet() then begin
+ GraphQuery.Append(', customAttributes: [');
+ repeat
+ ItemAttribute.Get(ItemAttributeValueMapping."Item Attribute ID");
+ ItemAttributeValue.Get(ItemAttribute.ID, ItemAttributeValueMapping."Item Attribute Value ID");
+
+ GraphQuery.Append('{');
+ GraphQuery.Append('key: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(ItemAttribute.Name)));
+ GraphQuery.Append('\"');
+
+ GraphQuery.Append(', value: \"');
+ GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(ItemAttributeValue.Value)));
+ GraphQuery.Append('\"');
+ GraphQuery.Append('},')
+ until ItemAttributeValueMapping.Next() = 0;
+ GraphQuery.Append(']');
+ end;
+
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al
new file mode 100644
index 0000000000..e63b44d222
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al
@@ -0,0 +1,69 @@
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Codeunit Shpfy Fulfillment API (ID 30361).
+///
+codeunit 30361 "Shpfy Fulfillment API"
+{
+ Access = Internal;
+
+ var
+ ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+
+ ///
+ /// Creates a fulfillment for a provided fulfillment order id.
+ ///
+ /// Fulfillment order id.
+ internal procedure CreateFulfillment(FulfillmentOrderId: BigInteger)
+ var
+ JResponse: JsonToken;
+ GraphQLType: Enum "Shpfy GraphQL Type";
+ Parameters: Dictionary of [Text, Text];
+ begin
+ GraphQLType := "Shpfy GraphQL Type"::FulfillOrder;
+ Parameters.Add('FulfillmentOrderId', Format(FulfillmentOrderId));
+ JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters);
+ end;
+
+ ///
+ /// Gets fulfillment order ids for a provided shopify order id.
+ ///
+ /// Shopify order id to get fulfillments from.
+ /// Number of fulfillment orders to get.
+ /// List of fulfillment order ids.
+ internal procedure GetFulfillmentOrderIds(OrderId: Text; NumberOfLines: Integer) FulfillmentOrderList: List of [BigInteger]
+ var
+ GraphQLType: Enum "Shpfy GraphQL Type";
+ Parameters: Dictionary of [Text, Text];
+ JFulfillments: JsonToken;
+ begin
+ GraphQLType := "Shpfy GraphQL Type"::GetFulfillmentOrderIds;
+ Parameters.Add('OrderId', OrderId);
+ Parameters.Add('NumberOfOrders', Format(NumberOfLines));
+ JFulfillments := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters);
+ FulfillmentOrderList := ParseFulfillmentOrders(JFulfillments);
+
+ exit(FulfillmentOrderList);
+ end;
+
+ ///
+ /// Sets a global shopify shop to be used for fulfillment api functionality.
+ ///
+ /// Shopify shop code to be set.
+ internal procedure SetShop(ShopCode: Code[20])
+ begin
+ ShpfyCommunicationMgt.SetShop(ShopCode);
+ end;
+
+ local procedure ParseFulfillmentOrders(JFulfillments: JsonToken) FulfillmentOrderList: List of [BigInteger]
+ var
+ ShpfyJsonHelper: Codeunit "Shpfy Json Helper";
+ JArray: JsonArray;
+ JToken: JsonToken;
+ begin
+ JArray := ShpfyJsonHelper.GetJsonArray(JFulfillments, 'data.order.fulfillmentOrders.nodes');
+
+ foreach JToken in JArray do
+ FulfillmentOrderList.Add(ShpfyCommunicationMgt.GetIdOfGId(ShpfyJsonHelper.GetValueAsText(JToken, 'id')));
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al
new file mode 100644
index 0000000000..bcd6981e3e
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al
@@ -0,0 +1,459 @@
+namespace Microsoft.Integration.Shopify;
+
+using Microsoft.Sales.History;
+using Microsoft.Finance.GeneralLedger.Setup;
+
+///
+/// Codeunit Shpfy Posted Invoice Export" (ID 30362).
+///
+codeunit 30362 "Shpfy Posted Invoice Export"
+{
+ Access = Internal;
+ TableNo = "Sales Invoice Header";
+ Permissions = tabledata "Sales Invoice Header" = m;
+
+ var
+ ShpfyShop: Record "Shpfy Shop";
+ ShpfyDraftOrdersAPI: Codeunit "Shpfy Draft Orders API";
+ ShpfyFulfillmentAPI: Codeunit "Shpfy Fulfillment API";
+ ShpfyJsonHelper: Codeunit "Shpfy Json Helper";
+
+ trigger OnRun()
+ begin
+ ExportPostedSalesInvoiceToShopify(Rec);
+ end;
+
+ ///
+ /// Sets a global shopify shop to be used for posted invoice export.
+ ///
+ /// Shopify shop code to be set.
+ internal procedure SetShop(NewShopCode: Code[20])
+ begin
+ ShpfyShop.Get(NewShopCode);
+ ShpfyDraftOrdersAPI.SetShop(ShpfyShop.Code);
+ ShpfyFulfillmentAPI.SetShop(ShpfyShop.Code);
+ end;
+
+ ///
+ /// Exports provided posted sales invoice to shopify.
+ ///
+ ///
+ /// If the posted sales invoice isn't exportable, the shopify order id is set to -2.
+ /// If shopify order creation fails, the id is set to -1.
+ ///
+ /// Posted sales invoice to be exported.
+ internal procedure ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader: Record "Sales Invoice Header")
+ var
+ TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ TempShpfyOrderLine: Record "Shpfy Order Line" temporary;
+ DraftOrderId: BigInteger;
+ ShpfyOrderTaxLines: Dictionary of [Text, Decimal];
+ FulfillmentOrderIds: List of [BigInteger];
+ JResponse: JsonToken;
+ OrderId: BigInteger;
+ OrderNo: Text;
+ begin
+ if not IsInvoiceExportable(SalesInvoiceHeader) then begin
+ SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -2, '');
+ exit;
+ end;
+
+ MapPostedSalesInvoiceData(SalesInvoiceHeader, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines);
+
+ DraftOrderId := ShpfyDraftOrdersAPI.CreateDraftOrder(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines);
+ JResponse := ShpfyDraftOrdersAPI.CompleteDraftOrder(DraftOrderId);
+
+ if IsSuccess(JResponse) then begin
+ OrderId := ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId');
+ OrderNo := ShpfyJsonHelper.GetValueAsText(JResponse, 'data.draftOrderComplete.draftOrder.order.name');
+
+ FulfillmentOrderIds := ShpfyFulfillmentAPI.GetFulfillmentOrderIds(Format(OrderId), GetNumberOfLines(TempShpfyOrderLine, ShpfyOrderTaxLines));
+ CreateFulfillmentsForShopifyOrder(FulfillmentOrderIds);
+ CreateShpfyInvoiceHeader(OrderId);
+ SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, OrderId, Format(OrderNo));
+ AddDocumentLinkToBCDocument(SalesInvoiceHeader);
+ end else
+ SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -1, '');
+ end;
+
+ local procedure CreateFulfillmentsForShopifyOrder(FulfillmentOrderIds: List of [BigInteger])
+ var
+ FulfillmentOrderId: BigInteger;
+ begin
+ foreach FulfillmentOrderId in FulfillmentOrderIds do
+ ShpfyFulfillmentAPI.CreateFulfillment(FulfillmentOrderId);
+ end;
+
+ local procedure IsInvoiceExportable(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
+ var
+ ShpfyCompany: Record "Shpfy Company";
+ ShpfyCustomer: Record "Shpfy Customer";
+ begin
+ ShpfyCompany.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No.");
+ if ShpfyCompany.IsEmpty() then begin
+ ShpfyCustomer.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No.");
+ if ShpfyCustomer.IsEmpty() then
+ exit(false);
+ end;
+
+ if not CurrencyCodeMatch(SalesInvoiceHeader) then
+ exit(false);
+
+ if not ShopifyPaymentTermsExists(SalesInvoiceHeader."Payment Terms Code") then
+ exit(false);
+
+ if ShpfyShop."Default Customer No." = SalesInvoiceHeader."Bill-to Customer No." then
+ exit(false);
+
+ if CheckCustomerTemplates(SalesInvoiceHeader."Bill-to Customer No.") then
+ exit(false);
+
+ if not CheckSalesInvoiceHeaderLines(SalesInvoiceHeader) then
+ exit(false);
+
+ exit(true);
+ end;
+
+ local procedure CurrencyCodeMatch(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
+ var
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ ShopifyLocalCurrencyCode: Code[10];
+ begin
+ GeneralLedgerSetup.Get();
+
+ if ShpfyShop."Currency Code" = '' then
+ ShopifyLocalCurrencyCode := GeneralLedgerSetup."LCY Code"
+ else
+ ShopifyLocalCurrencyCode := ShpfyShop."Currency Code";
+
+ if SalesInvoiceHeader."Currency Code" = '' then
+ exit(ShopifyLocalCurrencyCode = GeneralLedgerSetup."LCY Code")
+ else
+ exit(ShopifyLocalCurrencyCode = SalesInvoiceHeader."Currency Code");
+ end;
+
+ local procedure ShopifyPaymentTermsExists(PaymentTermsCode: Code[10]): Boolean
+ var
+ ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ begin
+ ShpfyPaymentTerms.SetRange("Payment Terms Code", PaymentTermsCode);
+ ShpfyPaymentTerms.SetRange("Shop Code", ShpfyShop.Code);
+
+ if not ShpfyPaymentTerms.FindFirst() then begin
+ ShpfyPaymentTerms.SetRange("Payment Terms Code");
+ ShpfyPaymentTerms.SetRange("Is Primary", true);
+
+ if not ShpfyPaymentTerms.FindFirst() then
+ exit(false);
+ end;
+
+ exit(true);
+ end;
+
+ local procedure CheckCustomerTemplates(CustomerNo: Code[20]): Boolean
+ var
+ ShpfyCustomerTemplate: Record "Shpfy Customer Template";
+ begin
+ ShpfyCustomerTemplate.SetRange("Default Customer No.", CustomerNo);
+ ShpfyCustomerTemplate.SetRange("Shop Code", ShpfyShop.Code);
+ exit(not ShpfyCustomerTemplate.IsEmpty());
+ end;
+
+ local procedure CheckSalesInvoiceHeaderLines(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
+ var
+ SalesInvoiceLine: Record "Sales Invoice Line";
+ begin
+ SalesInvoiceLine.SetFilter(Type, '<>%1', SalesInvoiceLine.Type::" ");
+ if SalesInvoiceLine.IsEmpty() then
+ exit(false);
+
+ SalesInvoiceLine.Reset();
+
+ SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
+ SalesInvoiceLine.SetRange(Type, SalesInvoiceLine.Type::Item);
+ if SalesInvoiceLine.FindSet() then
+ repeat
+ if (SalesInvoiceLine.Quantity <> 0) and (SalesInvoiceLine.Quantity <> Round(SalesInvoiceLine.Quantity, 1)) then
+ exit(false);
+
+ if ShpfyShop."Items Mapped to Products" then
+ if not ItemIsMappedToShopifyProduct(SalesInvoiceLine) then
+ exit(false);
+
+ if (SalesInvoiceLine.Type <> SalesInvoiceLine.Type::" ") and (SalesInvoiceLine."No." = '') then
+ exit(false);
+ until SalesInvoiceLine.Next() = 0;
+
+ exit(true);
+ end;
+
+ local procedure SetSalesInvoiceShopifyOrderInformation(var SalesInvoiceHeader: Record "Sales Invoice Header"; OrderId: BigInteger; OrderNo: Code[50])
+ begin
+ SalesInvoiceHeader.Validate("Shpfy Order Id", OrderId);
+ SalesInvoiceHeader.Validate("Shpfy Order No.", OrderNo);
+ SalesInvoiceHeader.Modify(true);
+ end;
+
+ local procedure ItemIsMappedToShopifyProduct(SalesInvoiceLine: Record "Sales Invoice Line"): Boolean
+ var
+ ShpfyProduct: Record "Shpfy Product";
+ ShpfyVariant: Record "Shpfy Variant";
+ begin
+ ShpfyProduct.SetRange("Item No.", SalesInvoiceLine."No.");
+ if ShpfyProduct.IsEmpty() then
+ exit(false);
+
+ if ShpfyShop."UoM as Variant" then begin
+ if not ProductVariantExists(SalesInvoiceLine."Unit of Measure Code", SalesInvoiceLine) then
+ exit(false);
+ end else begin
+ ShpfyVariant.SetRange("Item No.", SalesInvoiceLine."No.");
+ ShpfyVariant.SetRange("Variant Code", SalesInvoiceLine."Variant Code");
+ ShpfyVariant.SetRange("Shop Code", ShpfyShop.Code);
+ if ShpfyVariant.IsEmpty() then
+ exit(false);
+ end;
+
+ exit(true);
+ end;
+
+ local procedure ProductVariantExists(UnitOfMeasure: Code[10]; SalesInvoiceLine: Record "Sales Invoice Line"): Boolean
+ var
+ ShpfyVariant: Record "Shpfy Variant";
+ begin
+ ShpfyVariant.SetRange("Item No.", SalesInvoiceLine."No.");
+ ShpfyVariant.SetRange("Shop Code", ShpfyShop.Code);
+ ShpfyVariant.SetRange("Variant Code", SalesInvoiceLine."Variant Code");
+ if ShpfyVariant.FindSet() then
+ repeat
+ case ShpfyVariant."UoM Option Id" of
+ 1:
+ if ShpfyVariant."Option 1 Value" = UnitOfMeasure then
+ exit(true);
+ 2:
+ if ShpfyVariant."Option 2 Value" = UnitOfMeasure then
+ exit(true);
+ 3:
+ if ShpfyVariant."Option 3 Value" = UnitOfMeasure then
+ exit(true);
+ end;
+ until ShpfyVariant.Next() = 0;
+ end;
+
+ local procedure MapPostedSalesInvoiceData(
+ SalesInvoiceHeader: Record "Sales Invoice Header";
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ var TempShpfyOrderLine: Record "Shpfy Order Line" temporary;
+ var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]
+ )
+ var
+ InvoiceLine: Record "Sales Invoice Line";
+ begin
+ MapSalesInvoiceHeader(SalesInvoiceHeader, TempShpfyOrderHeader);
+
+ InvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
+ if InvoiceLine.FindSet() then
+ repeat
+ MapSalesInvoiceLine(InvoiceLine, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines);
+ until InvoiceLine.Next() = 0;
+ end;
+
+ local procedure MapSalesInvoiceHeader(
+ SalesInvoiceHeader: Record "Sales Invoice Header";
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary
+ )
+ begin
+ TempShpfyOrderHeader.Init();
+ TempShpfyOrderHeader."Sales Invoice No." := SalesInvoiceHeader."No.";
+ TempShpfyOrderHeader."Sales Order No." := SalesInvoiceHeader."Order No.";
+ TempShpfyOrderHeader."Created At" := SalesInvoiceHeader.SystemCreatedAt;
+ TempShpfyOrderHeader.Confirmed := true;
+ TempShpfyOrderHeader."Updated At" := SalesInvoiceHeader.SystemModifiedAt;
+ TempShpfyOrderHeader."Currency Code" := MapCurrencyCode(SalesInvoiceHeader);
+ TempShpfyOrderHeader."Document Date" := SalesInvoiceHeader."Document Date";
+ SalesInvoiceHeader.CalcFields(Amount, "Amount Including VAT", "Invoice Discount Amount");
+ TempShpfyOrderHeader."VAT Amount" := SalesInvoiceHeader."Amount Including VAT" - SalesInvoiceHeader.Amount;
+ TempShpfyOrderHeader."Discount Amount" := SalesInvoiceHeader."Invoice Discount Amount";
+ TempShpfyOrderHeader."Fulfillment Status" := Enum::"Shpfy Order Fulfill. Status"::Fulfilled;
+ TempShpfyOrderHeader."Shop Code" := ShpfyShop.Code;
+ TempShpfyOrderHeader.Unpaid := IsInvoiceUnpaid(SalesInvoiceHeader);
+
+ MapBillToInformation(TempShpfyOrderHeader, SalesInvoiceHeader);
+ MapShipToInformation(TempShpfyOrderHeader, SalesInvoiceHeader);
+
+ TempShpfyOrderHeader.Insert(false);
+ end;
+
+ local procedure MapCurrencyCode(SalesInvoiceHeader: Record "Sales Invoice Header"): Code[10]
+ var
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ begin
+ if SalesInvoiceHeader."Currency Code" <> '' then
+ exit(SalesInvoiceHeader."Currency Code");
+
+ GeneralLedgerSetup.Get();
+ exit(GeneralLedgerSetup."LCY Code");
+ end;
+
+ local procedure MapBillToInformation(
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ SalesInvoiceHeader: Record "Sales Invoice Header"
+ )
+ var
+ ShpfyCustomer: Record "Shpfy Customer";
+ begin
+ TempShpfyOrderHeader."Bill-to Name" := CopyStr(SalesInvoiceHeader."Bill-to Name", 1, MaxStrLen(TempShpfyOrderHeader."Bill-to Name"));
+ TempShpfyOrderHeader."Bill-to Name 2" := SalesInvoiceHeader."Bill-to Name 2";
+ TempShpfyOrderHeader."Bill-to Address" := SalesInvoiceHeader."Bill-to Address";
+ TempShpfyOrderHeader."Bill-to Address 2" := SalesInvoiceHeader."Bill-to Address 2";
+ TempShpfyOrderHeader."Bill-to Post Code" := SalesInvoiceHeader."Bill-to Post Code";
+ TempShpfyOrderHeader."Bill-to City" := SalesInvoiceHeader."Bill-to City";
+ TempShpfyOrderHeader."Bill-to County" := SalesInvoiceHeader."Bill-to County";
+ TempShpfyOrderHeader."Bill-to Country/Region Code" := SalesInvoiceHeader."Bill-to Country/Region Code";
+ TempShpfyOrderHeader."Bill-to Customer No." := SalesInvoiceHeader."Bill-to Customer No.";
+
+ ShpfyCustomer.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No.");
+ if ShpfyCustomer.FindFirst() then begin
+ TempShpfyOrderHeader.Email := CopyStr(ShpfyCustomer.Email, 1, MaxStrLen(TempShpfyOrderHeader.Email));
+ TempShpfyOrderHeader."Phone No." := ShpfyCustomer."Phone No.";
+ end;
+ end;
+
+ local procedure MapShipToInformation(
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ SalesInvoiceHeader: Record "Sales Invoice Header"
+ )
+ begin
+ TempShpfyOrderHeader."Ship-to Name" := CopyStr(SalesInvoiceHeader."Ship-to Name", 1, MaxStrLen(TempShpfyOrderHeader."Ship-to Name"));
+ TempShpfyOrderHeader."Ship-to Name 2" := SalesInvoiceHeader."Ship-to Name 2";
+ TempShpfyOrderHeader."Ship-to Address" := SalesInvoiceHeader."Ship-to Address";
+ TempShpfyOrderHeader."Ship-to Address 2" := SalesInvoiceHeader."Ship-to Address 2";
+ TempShpfyOrderHeader."Ship-to Post Code" := SalesInvoiceHeader."Ship-to Post Code";
+ TempShpfyOrderHeader."Ship-to City" := SalesInvoiceHeader."Ship-to City";
+ TempShpfyOrderHeader."Ship-to County" := SalesInvoiceHeader."Ship-to County";
+ TempShpfyOrderHeader."Ship-to Country/Region Code" := SalesInvoiceHeader."Ship-to Country/Region Code";
+ end;
+
+ local procedure IsInvoiceUnpaid(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
+ begin
+ SalesInvoiceHeader.CalcFields("Remaining Amount");
+ exit(SalesInvoiceHeader."Remaining Amount" <> 0);
+ end;
+
+ local procedure MapSalesInvoiceLine(
+ SalesInvoiceLine: Record "Sales Invoice Line";
+ var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary;
+ var TempShpfyOrderLine: Record "Shpfy Order Line" temporary;
+ var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]
+): BigInteger
+ var
+ ShpfyVariant: Record "Shpfy Variant";
+ begin
+ TempShpfyOrderLine.Init();
+ TempShpfyOrderLine."Line Id" := SalesInvoiceLine."Line No.";
+ TempShpfyOrderLine.Description := SalesInvoiceLine.Description;
+ TempShpfyOrderLine.Quantity := SalesInvoiceLine.Quantity;
+ TempShpfyOrderLine."Item No." := SalesInvoiceLine."No.";
+ TempShpfyOrderLine."Variant Code" := SalesInvoiceLine."Variant Code";
+ TempShpfyOrderLine."Gift Card" := false;
+ TempShpfyOrderLine.Taxable := false;
+ TempShpfyOrderLine."Unit Price" := SalesInvoiceLine."Unit Price";
+ TempShpfyOrderHeader."Discount Amount" += SalesInvoiceLine."Line Discount Amount";
+ TempShpfyOrderHeader.Modify(false);
+
+ if ShpfyShop."UoM as Variant" then
+ MapUOMProductVariants(SalesInvoiceLine, TempShpfyOrderLine)
+ else begin
+ ShpfyVariant.SetRange("Shop Code", ShpfyShop.Code);
+ ShpfyVariant.SetRange("Item No.", SalesInvoiceLine."No.");
+ ShpfyVariant.SetRange("Variant Code", SalesInvoiceLine."Variant Code");
+ if ShpfyVariant.FindFirst() then begin
+ TempShpfyOrderLine."Shopify Product Id" := ShpfyVariant."Product Id";
+ TempShpfyOrderLine."Shopify Variant Id" := ShpfyVariant.Id;
+ end;
+ end;
+
+ MapTaxLine(SalesInvoiceLine, ShpfyOrderTaxLines);
+
+ TempShpfyOrderLine.Insert(false);
+ end;
+
+ local procedure MapUOMProductVariants(SalesInvoiceLine: Record "Sales Invoice Line"; var TempShpfyOrderLine: Record "Shpfy Order Line" temporary)
+ var
+ ShpfyVariant: Record "Shpfy Variant";
+ begin
+ ShpfyVariant.SetRange("Shop Code", ShpfyShop.Code);
+ ShpfyVariant.SetRange("Item No.", SalesInvoiceLine."No.");
+ ShpfyVariant.SetRange("Variant Code", SalesInvoiceLine."Variant Code");
+ if ShpfyVariant.FindSet() then
+ repeat
+ case ShpfyVariant."UoM Option Id" of
+ 1:
+ if ShpfyVariant."Option 1 Value" = SalesInvoiceLine."Unit of Measure Code" then begin
+ TempShpfyOrderLine."Shopify Product Id" := ShpfyVariant."Product Id";
+ TempShpfyOrderLine."Shopify Variant Id" := ShpfyVariant.Id;
+ exit;
+ end;
+ 2:
+ if ShpfyVariant."Option 2 Value" = SalesInvoiceLine."Unit of Measure Code" then begin
+ TempShpfyOrderLine."Shopify Product Id" := ShpfyVariant."Product Id";
+ TempShpfyOrderLine."Shopify Variant Id" := ShpfyVariant.Id;
+ exit;
+ end;
+ 3:
+ if ShpfyVariant."Option 3 Value" = SalesInvoiceLine."Unit of Measure Code" then begin
+ TempShpfyOrderLine."Shopify Product Id" := ShpfyVariant."Product Id";
+ TempShpfyOrderLine."Shopify Variant Id" := ShpfyVariant.Id;
+ exit;
+ end;
+ end;
+ until ShpfyVariant.Next() = 0;
+ end;
+
+ local procedure MapTaxLine(var SalesInvoiceLine: Record "Sales Invoice Line" temporary; var ShpfyOrderTaxLines: Dictionary of [Text, Decimal])
+ var
+ VATAmount: Decimal;
+ TaxLineTok: Label '%1 - %2%', Comment = '%1 = VAT Calculation Type, %2 = VAT %', Locked = true;
+ TaxTitle: Text;
+ begin
+ VATAmount := SalesInvoiceLine."Amount Including VAT" - SalesInvoiceLine."VAT Base Amount";
+
+ TaxTitle := StrSubstNo(TaxLineTok, Format(SalesInvoiceLine."VAT Calculation Type"), Format(SalesInvoiceLine."VAT %"));
+ if ShpfyOrderTaxLines.ContainsKey(TaxTitle) then
+ ShpfyOrderTaxLines.Set(TaxTitle, ShpfyOrderTaxLines.Get(TaxTitle) + VATAmount)
+ else
+ ShpfyOrderTaxLines.Add(TaxTitle, VATAmount);
+ end;
+
+ local procedure IsSuccess(JsonTokenResponse: JsonToken): Boolean
+ begin
+ exit(ShpfyJsonHelper.GetJsonArray(JsonTokenResponse, 'data.draftOrderComplete.userErrors').Count() = 0);
+ end;
+
+ local procedure CreateShpfyInvoiceHeader(OrderId: BigInteger)
+ var
+ ShpfyInvoiceHeader: Record "Shpfy Invoice Header";
+ begin
+ ShpfyInvoiceHeader.Init();
+ ShpfyInvoiceHeader.Validate("Shopify Order Id", OrderId);
+ ShpfyInvoiceHeader.Insert(true);
+ end;
+
+ local procedure AddDocumentLinkToBCDocument(SalesInvoiceHeader: Record "Sales Invoice Header")
+ var
+ DocLinkToBCDoc: Record "Shpfy Doc. Link To Doc.";
+ BCDocumentTypeConvert: Codeunit "Shpfy BC Document Type Convert";
+ begin
+ DocLinkToBCDoc.Init();
+ DocLinkToBCDoc."Shopify Document Type" := "Shpfy Shop Document Type"::"Shopify Shop Order";
+ DocLinkToBCDoc."Shopify Document Id" := SalesInvoiceHeader."Shpfy Order Id";
+ DocLinkToBCDoc."Document Type" := BCDocumentTypeConvert.Convert(SalesInvoiceHeader);
+ DocLinkToBCDoc."Document No." := SalesInvoiceHeader."No.";
+ DocLinkToBCDoc.Insert(true);
+ end;
+
+ local procedure GetNumberOfLines(var TempShpfyOrderLine: Record "Shpfy Order Line" temporary; var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]): Integer
+ begin
+ exit(ShpfyOrderTaxLines.Count() + TempShpfyOrderLine.Count());
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al
new file mode 100644
index 0000000000..cf03c116ea
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al
@@ -0,0 +1,22 @@
+namespace Microsoft.Integration.Shopify;
+
+using Microsoft.Sales.History;
+
+codeunit 30364 "Shpfy Update Sales Invoice"
+{
+ Access = Internal;
+
+ [EventSubscriber(ObjectType::Page, Page::"Posted Sales Inv. - Update", 'OnAfterRecordChanged', '', false, false)]
+ local procedure CheckShopifyOrderIdOnAfterRecordChanged(var SalesInvoiceHeader: Record "Sales Invoice Header"; xSalesInvoiceHeader: Record "Sales Invoice Header"; var IsChanged: Boolean)
+ begin
+ if IsChanged then
+ exit;
+ IsChanged := SalesInvoiceHeader."Shpfy Order Id" <> xSalesInvoiceHeader."Shpfy Order Id";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales Inv. Header - Edit", 'OnOnRunOnBeforeTestFieldNo', '', false, false)]
+ local procedure SetShopifyOrderIdOnBeforeSalesShptHeaderModify(var SalesInvoiceHeader: Record "Sales Invoice Header"; SalesInvoiceHeaderRec: Record "Sales Invoice Header")
+ begin
+ SalesInvoiceHeader."Shpfy Order Id" := SalesInvoiceHeaderRec."Shpfy Order Id";
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al
new file mode 100644
index 0000000000..d9b090faa4
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al
@@ -0,0 +1,44 @@
+namespace Microsoft.Integration.Shopify;
+
+using Microsoft.Sales.History;
+
+///
+/// PageExtension Shpfy Sales Invoice Update (ID 30125) extends Record Posted Sales Inv. - Update.
+///
+pageextension 30125 "Shpfy Sales Invoice Update" extends "Posted Sales Inv. - Update"
+{
+ layout
+ {
+ addlast(content)
+ {
+ group(Shopify)
+ {
+ Caption = 'Shopify';
+ Visible = ShopifyTabVisible;
+
+ field("Shpfy Order Id"; Rec."Shpfy Order Id")
+ {
+ ApplicationArea = Basic, Suite;
+ Caption = 'Shopify Order Id';
+ Editable = (Rec."Shpfy Order Id" = 0) or (Rec."Shpfy Order Id" = -1) or (Rec."Shpfy Order Id" = -2);
+ ToolTip = 'Specifies the Shopify Order ID. Helps track the status of invoices within Shopify, with 0 indicating readiness to synchronize, -1 indicating an error, and -2 indicating that the shipment is skipped.';
+
+ trigger OnValidate()
+ begin
+ if not (Rec."Shpfy Order Id" in [0, -1, -2]) then
+ Error(ValueNotAllowedErr);
+ end;
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ ShopifyTabVisible := Rec."Shpfy Order Id" <> 0;
+ end;
+
+ var
+ ShopifyTabVisible: Boolean;
+ ValueNotAllowedErr: Label 'Allowed values are 0, -1 or -2. 0 indicates readiness to synchronize, -1 indicates an error, and -2 indicates that the invoice is skipped.';
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al
new file mode 100644
index 0000000000..38e086d885
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al
@@ -0,0 +1,105 @@
+namespace Microsoft.Integration.Shopify;
+
+using Microsoft.Sales.History;
+
+///
+/// Report Shpfy Sync Invoices to Shpfy (ID 30119).
+///
+report 30119 "Shpfy Sync Invoices to Shpfy"
+{
+ ApplicationArea = All;
+ Caption = 'Sync Invoices to Shopify';
+ ProcessingOnly = true;
+ UsageCategory = Tasks;
+
+ dataset
+ {
+ dataitem(SalesInvoiceHeader; "Sales Invoice Header")
+ {
+ RequestFilterFields = "No.", "Posting Date";
+ trigger OnPreDataItem()
+ var
+ ShopCodeNotSetErr: Label 'Shopify Shop Code is empty.';
+ PostedInvoiceSyncNotSetErr: Label 'Posted Invoice Sync is not enabled for this shop.';
+ begin
+ if ShopCode = '' then
+ Error(ShopCodeNotSetErr);
+
+ ShpfyShop.Get(ShopCode);
+
+ if not ShpfyShop."Posted Invoice Sync" then
+ Error(PostedInvoiceSyncNotSetErr);
+
+ ShpfyPostedInvoiceExport.SetShop(ShopCode);
+ SetRange("Shpfy Order Id", 0);
+
+ if GuiAllowed then begin
+ CurrSalesInvoiceHeaderNo := SalesInvoiceHeader."No.";
+ ProcessDialog.Open(ProcessMsg, CurrSalesInvoiceHeaderNo);
+ ProcessDialog.Update();
+ end;
+ end;
+
+ trigger OnAfterGetRecord()
+ begin
+ if GuiAllowed then begin
+ CurrSalesInvoiceHeaderNo := SalesInvoiceHeader."No.";
+ ProcessDialog.Update();
+ end;
+
+ ShpfyPostedInvoiceExport.Run(SalesInvoiceHeader);
+ end;
+
+ trigger OnPostDataItem()
+ var
+ ShpfyBackgroundSyncs: Codeunit "Shpfy Background Syncs";
+ begin
+ if GuiAllowed then
+ ProcessDialog.Close();
+
+ ShpfyBackgroundSyncs.InventorySync(ShopCode);
+ end;
+ }
+ }
+ requestpage
+ {
+ SaveValues = true;
+ layout
+ {
+ area(Content)
+ {
+ group(ShopFilter)
+ {
+ Caption = 'Options';
+ field(Shop; ShopCode)
+ {
+ ApplicationArea = All;
+ Caption = 'Shop Code';
+ Lookup = true;
+ LookupPageId = "Shpfy Shops";
+ TableRelation = "Shpfy Shop";
+ ToolTip = 'Specifies the Shopify Shop to which the invoice will be exported.';
+ ShowMandatory = true;
+ }
+ }
+ }
+ }
+ }
+
+ var
+ ShpfyShop: Record "Shpfy Shop";
+ ShpfyPostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export";
+ ShopCode: Code[20];
+ CurrSalesInvoiceHeaderNo: Code[20];
+ ProcessDialog: Dialog;
+ ProcessMsg: Label 'Synchronizing Posted Sales Invoice #1####################', Comment = '#1 = Posted Sales Invoice No.';
+
+ ///
+ /// Sets a global shopify shop code to be used.
+ ///
+ /// Shopify shop code to be set.
+ internal procedure SetShop(NewShopCode: Code[20])
+ begin
+ ShopCode := NewShopCode;
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al
new file mode 100644
index 0000000000..001b9f99e0
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al
@@ -0,0 +1,27 @@
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Table Shpfy Invoice Header (ID 30161).
+///
+table 30161 "Shpfy Invoice Header"
+{
+ Caption = 'Shopify Invoice Header';
+ DataClassification = CustomerContent;
+ Access = Internal;
+
+ fields
+ {
+ field(1; "Shopify Order Id"; BigInteger)
+ {
+ Caption = 'Shopify Order Id';
+ }
+ }
+
+ keys
+ {
+ key(PK; "Shopify Order Id")
+ {
+ Clustered = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyImportOrder.Codeunit.al b/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyImportOrder.Codeunit.al
index 69a398c2fd..c4e67f7c00 100644
--- a/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyImportOrder.Codeunit.al
+++ b/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyImportOrder.Codeunit.al
@@ -116,6 +116,22 @@ codeunit 30161 "Shpfy Import Order"
if CheckToCloseOrder(OrderHeader) then
CloseOrder(OrderHeader);
+
+ if ShopifyInvoiceExists(OrderHeader) then
+ MarkAsProcessed(OrderHeader);
+ end;
+
+ local procedure ShopifyInvoiceExists(OrderHeader: Record "Shpfy Order Header"): Boolean
+ var
+ ShpfyInvoiceHeader: Record "Shpfy Invoice Header";
+ begin
+ exit(ShpfyInvoiceHeader.Get(OrderHeader."Shopify Order Id"));
+ end;
+
+ local procedure MarkAsProcessed(OrderHeader: Record "Shpfy Order Header")
+ begin
+ OrderHeader.Validate(Processed, true);
+ OrderHeader.Modify()
end;
local procedure InsertOrderLinesAndRelatedRecords(var TempOrderLine: Record "Shpfy Order Line" temporary; var DataCaptureDict: Dictionary of [BigInteger, JsonToken]; var Redundancy: Integer)
@@ -502,6 +518,8 @@ codeunit 30161 "Shpfy Import Order"
JsonHelper.GetValueIntoField(JOrder, 'currentTotalPriceSet.shopMoney.amount', OrderHeaderRecordRef, OrderHeader.FieldNo("Current Total Amount"));
JsonHelper.GetValueIntoField(JOrder, 'currentSubtotalLineItemsQuantity', OrderHeaderRecordRef, OrderHeader.FieldNo("Current Total Items Quantity"));
JsonHelper.GetValueIntoField(Jorder, 'poNumber', OrderHeaderRecordRef, OrderHeader.FieldNo("PO Number"));
+ JsonHelper.GetValueIntoField(JOrder, 'paymentTerms.paymentTermsType', OrderHeaderRecordRef, OrderHeader.FieldNo("Payment Terms Type"));
+ JsonHelper.GetValueIntoField(JOrder, 'paymentTerms.paymentTermsName', OrderHeaderRecordRef, OrderHeader.FieldNo("Payment Terms Name"));
OrderHeaderRecordRef.SetTable(OrderHeader);
if JsonHelper.GetJsonObject(JOrder, JObject, 'purchasingEntity') then
if JsonHelper.GetJsonObject(JOrder, JObject, 'purchasingEntity.company') then
@@ -620,7 +638,7 @@ codeunit 30161 "Shpfy Import Order"
OrderAttribute.Value := CopyStr(JsonHelper.GetValueAsText(JToken, 'value', MaxStrLen(OrderAttribute.Value)), 1, MaxStrLen(OrderAttribute.Value))
else
#endif
- OrderAttribute."Attribute Value" := CopyStr(JsonHelper.GetValueAsText(JToken, 'value', MaxStrLen(OrderAttribute."Attribute Value")), 1, MaxStrLen(OrderAttribute."Attribute Value"));
+ OrderAttribute."Attribute Value" := CopyStr(JsonHelper.GetValueAsText(JToken, 'value', MaxStrLen(OrderAttribute."Attribute Value")), 1, MaxStrLen(OrderAttribute."Attribute Value"));
OrderAttribute.Insert();
end;
end;
@@ -632,7 +650,7 @@ codeunit 30161 "Shpfy Import Order"
begin
OrderLineAttribute.SetRange("Order Id", ShopifyOrderId);
OrderLineAttribute.SetRange("Order Line Id", OrderLineId);
- if not OrderLineAttribute.IsEmpty then
+ if not OrderLineAttribute.IsEmpty() then
OrderLineAttribute.DeleteAll();
foreach JToken in JCustomAttributtes do begin
Clear(OrderLineAttribute);
@@ -723,7 +741,7 @@ codeunit 30161 "Shpfy Import Order"
JToken: JsonToken;
begin
OrderTaxLine.SetRange("Parent Id", ParentId);
- if not OrderTaxLine.IsEmpty then
+ if not OrderTaxLine.IsEmpty() then
OrderTaxLine.DeleteAll();
foreach JToken in JTaxLines do begin
RecordRef.Open(Database::"Shpfy Order Tax Line");
diff --git a/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyProcessOrder.Codeunit.al b/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyProcessOrder.Codeunit.al
index c2dadeb483..2d027d83bb 100644
--- a/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyProcessOrder.Codeunit.al
+++ b/Apps/W1/Shopify/app/src/Order handling/Codeunits/ShpfyProcessOrder.Codeunit.al
@@ -125,6 +125,8 @@ codeunit 30166 "Shpfy Process Order"
end;
if ShopifyOrderHeader."Payment Method Code" <> '' then
SalesHeader.Validate("Payment Method Code", ShopifyOrderHeader."Payment Method Code");
+ if ShopifyOrderHeader."Payment Terms Type" <> '' then
+ UpdatePaymentTerms(SalesHeader, ShopifyOrderHeader."Payment Terms Type", ShopifyOrderHeader."Payment Terms Name");
SalesHeader.Modify(true);
@@ -148,6 +150,17 @@ codeunit 30166 "Shpfy Process Order"
OrderEvents.OnAfterCreateSalesHeader(ShopifyOrderHeader, SalesHeader);
end;
+ local procedure UpdatePaymentTerms(var SalesHeader: Record "Sales Header"; PaymentTermsType: Code[20]; PaymentTermsName: Text[50])
+ var
+ ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ begin
+ ShpfyPaymentTerms.SetRange(Type, PaymentTermsType);
+ ShpfyPaymentTerms.SetRange("Shop Code", ShopifyShop.Code);
+ ShpfyPaymentTerms.SetRange(Name, PaymentTermsName);
+ if ShpfyPaymentTerms.FindFirst() then
+ SalesHeader.Validate("Payment Terms Code", ShpfyPaymentTerms."Payment Terms Code");
+ end;
+
local procedure ApplyGlobalDiscounts(OrderHeader: Record "Shpfy Order Header"; var SalesHeader: Record "Sales Header")
var
OrderLine: Record "Shpfy Order Line";
diff --git a/Apps/W1/Shopify/app/src/Order handling/Tables/ShpfyOrderHeader.Table.al b/Apps/W1/Shopify/app/src/Order handling/Tables/ShpfyOrderHeader.Table.al
index e31860f410..3e54e0be84 100644
--- a/Apps/W1/Shopify/app/src/Order handling/Tables/ShpfyOrderHeader.Table.al
+++ b/Apps/W1/Shopify/app/src/Order handling/Tables/ShpfyOrderHeader.Table.al
@@ -751,6 +751,16 @@ table 30118 "Shpfy Order Header"
Caption = 'Shipping Agent Service Code';
TableRelation = "Shipping Agent Services".Code where("Shipping Agent Code" = field("Shipping Agent Code"));
}
+ field(1030; "Payment Terms Type"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Payment Terms Type';
+ }
+ field(1040; "Payment Terms Name"; Text[50])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Payment Terms Name';
+ }
}
keys
{
diff --git a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al
new file mode 100644
index 0000000000..143c9b5e5d
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al
@@ -0,0 +1,85 @@
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Codeunit Shpfy Payment Terms API (ID 30360).
+///
+codeunit 30360 "Shpfy Payment Terms API"
+{
+ Access = Internal;
+
+ var
+ CommunicationMgt: Codeunit "Shpfy Communication Mgt.";
+ JsonHelper: Codeunit "Shpfy Json Helper";
+ ShopCode: Code[20];
+
+ ///
+ /// Synchronizes payment terms from shopify, ensuring that the payment terms are up-to-date with those defined in the shopify store.
+ ///
+ /// Shopify shop code to be used.
+ internal procedure PullPaymentTermsCodes()
+ var
+ GraphQLType: Enum "Shpfy GraphQL Type";
+ JTemplates: JsonArray;
+ JTemplate: JsonToken;
+ JResponse: JsonToken;
+ begin
+ GraphQLType := GraphQLType::GetPaymentTerms;
+ JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType);
+
+ JsonHelper.GetJsonArray(JResponse, JTemplates, 'data.paymentTermsTemplates');
+ foreach JTemplate in JTemplates do
+ UpdatePaymentTerms(JTemplate);
+ end;
+
+ ///
+ /// Sets a global shopify shop to be used form payment terms api functionality.
+ ///
+ /// Shopify shop code to be set.
+ internal procedure SetShop(NewShopCode: Code[20])
+ begin
+ ShopCode := NewShopCode;
+ CommunicationMgt.SetShop(NewShopCode);
+ end;
+
+ local procedure UpdatePaymentTerms(JTemplate: JsonToken)
+ var
+ ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ PaymentTermRecordRef: RecordRef;
+ Id: BigInteger;
+ IsNew: Boolean;
+ begin
+ Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JTemplate, 'id'));
+ IsNew := not ShpfyPaymentTerms.Get(ShopCode, Id);
+
+ if IsNew then begin
+ Clear(ShpfyPaymentTerms);
+ ShpfyPaymentTerms.Id := Id;
+ ShpfyPaymentTerms."Shop Code" := ShopCode;
+ end;
+
+ PaymentTermRecordRef.GetTable(ShpfyPaymentTerms);
+ JsonHelper.GetValueIntoField(JTemplate, 'name', PaymentTermRecordRef, ShpfyPaymentTerms.FieldNo(Name));
+ JsonHelper.GetValueIntoField(JTemplate, 'paymentTermsType', PaymentTermRecordRef, ShpfyPaymentTerms.FieldNo(Type));
+ JsonHelper.GetValueIntoField(JTemplate, 'dueInDays', PaymentTermRecordRef, ShpfyPaymentTerms.FieldNo("Due In Days"));
+ JsonHelper.GetValueIntoField(JTemplate, 'description', PaymentTermRecordRef, ShpfyPaymentTerms.FieldNo(Description));
+ PaymentTermRecordRef.SetTable(ShpfyPaymentTerms);
+
+ if ShpfyPaymentTerms.Type = 'FIXED' then
+ if ShouldBeMarkedAsPrimary() then
+ ShpfyPaymentTerms.Validate("Is Primary", true);
+
+ if IsNew then
+ ShpfyPaymentTerms.Insert(true)
+ else
+ ShpfyPaymentTerms.Modify(true);
+ end;
+
+ local procedure ShouldBeMarkedAsPrimary(): Boolean
+ var
+ ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ begin
+ ShpfyPaymentTerms.SetRange("Shop Code", ShopCode);
+ ShpfyPaymentTerms.SetRange("Is Primary", true);
+ exit(ShpfyPaymentTerms.IsEmpty());
+ end;
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al b/Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al
new file mode 100644
index 0000000000..bcf888a04d
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al
@@ -0,0 +1,75 @@
+namespace Microsoft.Integration.Shopify;
+
+///
+/// Page Shpfy Payment Terms Mapping (ID 30162).
+///
+page 30162 "Shpfy Payment Terms Mapping"
+{
+ PageType = List;
+ ApplicationArea = All;
+ UsageCategory = Lists;
+ SourceTable = "Shpfy Payment Terms";
+ Caption = 'Shopify Payment Terms Mapping';
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(General)
+ {
+ field(Type; Rec.Type)
+ {
+ ToolTip = 'Specifies the value of the Type field.';
+ }
+ field(Name; Rec.Name)
+ {
+ ToolTip = 'Specifies the value of the Name field.';
+ }
+ field(Description; Rec.Description)
+ {
+ ToolTip = 'Specifies the value of the Description field.';
+ }
+ field("Payment Terms Code"; Rec."Payment Terms Code")
+ {
+ ToolTip = 'Specifies the value of the Payment Terms Code field.';
+ }
+ field("Is Primary"; Rec."Is Primary")
+ {
+ ToolTip = 'Specifies the value of the Is Primary field.';
+ }
+ }
+ }
+ }
+
+ actions
+ {
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ Caption = 'Process';
+ ShowAs = Standard;
+
+ actionref(PromotedRefresh; Refresh) { }
+ }
+ }
+ area(Processing)
+ {
+ action(Refresh)
+ {
+ ApplicationArea = All;
+ Caption = 'Refresh';
+ Image = Refresh;
+ ToolTip = 'Refreshes the list of Shopify Payment Terms.';
+
+ trigger OnAction()
+ var
+ ShpfyPaymentTermAPI: Codeunit "Shpfy Payment Terms API";
+ begin
+ ShpfyPaymentTermAPI.SetShop(CopyStr(Rec.GetFilter("Shop Code"), 1, 20));
+ ShpfyPaymentTermAPI.PullPaymentTermsCodes();
+ end;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al
new file mode 100644
index 0000000000..fffe926394
--- /dev/null
+++ b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al
@@ -0,0 +1,78 @@
+namespace Microsoft.Integration.Shopify;
+
+using Microsoft.Foundation.PaymentTerms;
+
+///
+/// Table Shpfy Payment Terms (ID 30158).
+///
+table 30158 "Shpfy Payment Terms"
+{
+ Caption = 'Payment Terms';
+ DataClassification = CustomerContent;
+ Access = Internal;
+
+ fields
+ {
+ field(1; "Shop Code"; Code[20])
+ {
+ Caption = 'Shop Code';
+ TableRelation = "Shpfy Shop";
+ Editable = false;
+ }
+ field(2; Id; BigInteger)
+ {
+ Caption = 'ID';
+ Editable = false;
+ }
+ field(20; Name; Text[50])
+ {
+ Caption = 'Name';
+ Editable = false;
+ }
+ field(30; "Due In Days"; Integer)
+ {
+ Caption = 'Due In Days';
+ Editable = false;
+ }
+ field(40; Description; Text[50])
+ {
+ Caption = 'Description';
+ Editable = false;
+ }
+ field(50; Type; Code[20])
+ {
+ Caption = 'Type';
+ Editable = false;
+ }
+ field(60; "Is Primary"; Boolean)
+ {
+ Caption = 'Is Primary';
+
+ trigger OnValidate()
+ var
+ ShpfyPaymentTerms: Record "Shpfy Payment Terms";
+ PrimaryPaymentTermsExistsErr: Label 'Primary payment terms already exist for this shop.';
+ begin
+ ShpfyPaymentTerms.SetRange("Shop Code", Rec."Shop Code");
+ ShpfyPaymentTerms.SetRange("Is Primary", true);
+ ShpfyPaymentTerms.SetFilter(Id, '<>%1', Rec.Id);
+
+ if not ShpfyPaymentTerms.IsEmpty() then
+ Error(PrimaryPaymentTermsExistsErr);
+ end;
+ }
+ field(70; "Payment Terms Code"; Code[10])
+ {
+ TableRelation = "Payment Terms";
+ Caption = 'Payment Terms Code';
+ }
+ }
+
+ keys
+ {
+ key(PK; "Shop Code", Id)
+ {
+ Clustered = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al
index 19913bb1aa..d8296da1f9 100644
--- a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al
+++ b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al
@@ -32,6 +32,7 @@ permissionset 30102 "Shpfy - Edit"
tabledata "Shpfy Gift Card" = IMD,
tabledata "Shpfy Initial Import Line" = imd,
tabledata "Shpfy Inventory Item" = IMD,
+ tabledata "Shpfy Invoice Header" = IMD,
tabledata "Shpfy Language" = IMD,
tabledata "Shpfy Log Entry" = IMD,
tabledata "Shpfy Metafield" = IMD,
@@ -52,6 +53,7 @@ permissionset 30102 "Shpfy - Edit"
tabledata "Shpfy Order Tax Line" = IMD,
tabledata "Shpfy Order Transaction" = IMD,
tabledata "Shpfy Payment Method Mapping" = IMD,
+ tabledata "Shpfy Payment Terms" = IMD,
tabledata "Shpfy Payment Transaction" = IMD,
tabledata "Shpfy Payout" = IMD,
tabledata "Shpfy Product" = IMD,
diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al
index 80680e13c7..2fab53a2b8 100644
--- a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al
+++ b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al
@@ -32,6 +32,7 @@ permissionset 30100 "Shpfy - Read"
tabledata "Shpfy Gift Card" = R,
tabledata "Shpfy Initial Import Line" = r,
tabledata "Shpfy Inventory Item" = R,
+ tabledata "Shpfy Invoice Header" = R,
tabledata "Shpfy Language" = R,
tabledata "Shpfy Log Entry" = R,
tabledata "Shpfy Metafield" = R,
@@ -48,6 +49,7 @@ permissionset 30100 "Shpfy - Read"
tabledata "Shpfy Order Tax Line" = R,
tabledata "Shpfy Order Transaction" = R,
tabledata "Shpfy Payment Method Mapping" = R,
+ tabledata "Shpfy Payment Terms" = R,
tabledata "Shpfy Payment Transaction" = R,
tabledata "Shpfy Payout" = R,
tabledata "Shpfy Product" = R,