From c038bf6e6bd756df1449a5029a3ddbc9e3c5bc0f Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Thu, 20 Jun 2024 15:58:16 +0300 Subject: [PATCH 1/8] Export posted sales invoices to Shopify as Orders --- .../app/src/Base/Pages/ShpfyShopCard.Page.al | 36 ++ .../app/src/Base/Tables/ShpfyShop.Table.al | 8 +- .../ShpfyGQLDraftOrderComplete.Codeunit.al | 27 ++ .../ShpfyGQLFulfillOrder.Codeunit.al | 27 ++ .../ShpfyGQLPaymentTerms.Codeunit.al | 28 ++ .../GraphQL/Enums/ShpfyGraphQLType.Enum.al | 15 + .../ShpfyAuthenticationMgt.Codeunit.al | 2 +- .../Codeunits/ShpfyDraftOrdersAPI.Codeunit.al | 381 ++++++++++++++++++ .../ShpfyPostedInvoiceExport.Codeunit.al | 370 +++++++++++++++++ .../ShpfyUpdateSalesInvoice.Codeunit.al | 20 + .../ShpfySalesInvoiceUpdate.PageExt.al | 42 ++ .../src/Invoicing/Pages/ShpfyInvoices.Page.al | 32 ++ .../ShpfyExportInvoiceToShpfy.Report.al | 89 ++++ .../Tables/ShpfyInvoiceHeader.Table.al | 31 ++ .../Codeunits/ShpfyImportOrder.Codeunit.al | 22 +- .../Codeunits/ShpfyProcessOrder.Codeunit.al | 12 + .../Tables/ShpfyOrderHeader.Table.al | 10 + .../Codeunits/ShpfyPaymentTermAPI.Codeunit.al | 54 +++ .../Pages/ShpfyPaymentTermsMapping.Page.al | 70 ++++ .../Payments/Tables/ShpfyPaymentTerm.Table.al | 64 +++ .../PermissionSets/ShpfyEdit.PermissionSet.al | 4 +- .../PermissionSets/ShpfyRead.PermissionSet.al | 4 +- 22 files changed, 1342 insertions(+), 6 deletions(-) create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLFulfillOrder.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLPaymentTerms.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al create mode 100644 Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al create mode 100644 Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al 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 2db501be67..53ca7be460 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,10 @@ 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") + { + ToolTip = 'Specifies if only the items that are mapped to Shopify products/Shopify variants are synchronized from Posted Sales Invoices to Shopify.'; + } } group(PriceSynchronization) { @@ -651,6 +655,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; @@ -974,6 +991,25 @@ page 30101 "Shpfy Shop Card" Report.Run(Report::"Shpfy Sync Shipm. to Shopify"); end; } + action(SyncPostedSalesInvoices) + { + ApplicationArea = All; + Caption = 'Sync Posted Sales Invoices'; + Image = Export; + Promoted = true; + PromotedCategory = Category5; + PromotedIsBig = true; + PromotedOnly = true; + ToolTip = 'Synchronize invoices to Shopify.'; + + trigger OnAction(); + var + ExportInvoicetoShpfy: Report "Shpfy Export Invoice 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 0cbe7684bf..e09f381742 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) @@ -788,6 +788,10 @@ table 30102 "Shpfy Shop" { DataClassification = SystemMetadata; } + field(210; "Items Mapped to Products"; Boolean) + { + Caption = 'Items Must be Mapped to Products'; + } } 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..5743a06e08 --- /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 30312) implements Interface Shpfy IGraphQL. +/// +codeunit 30312 "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 { id, name, fulfillmentOrders(first: {{NumberOfOrders}}) {nodes{ id, status}} } 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/ShpfyGQLFulfillOrder.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLFulfillOrder.Codeunit.al new file mode 100644 index 0000000000..a890562fcf --- /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 30313) implements Interface Shpfy IGraphQL. +/// +codeunit 30313 "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/ShpfyGQLPaymentTerms.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLPaymentTerms.Codeunit.al new file mode 100644 index 0000000000..e38d2aeda2 --- /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 30213) implements Interface Shpfy IGraphQL. +/// +codeunit 30213 "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/Enums/ShpfyGraphQLType.Enum.al b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al index 4315a4f5e6..9860579f34 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al @@ -400,4 +400,19 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL" Caption = 'Get Next Catalog Products'; Implementation = "Shpfy IGraphQL" = "Shpfy GQL NextCatalogProducts"; } + 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"; + } } 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 e3ac43a697..d290137b75 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', Locked = true; + ScopeTxt: Label 'write_payment_terms,write_draft_orders,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', 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..0b21fc5d4b --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al @@ -0,0 +1,381 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Sales.Comment; +using Microsoft.Sales.History; + +/// +/// 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"; + + internal procedure ExportOrderToShopify( + var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; + var TempShpfyOrderLine: Record "Shpfy Order Line" temporary; + var ShpfyOrderTaxLines: Dictionary of [Text, Decimal] + ) ResponseJsonToken: JsonToken + var + ShpfyBackgroundSyncs: Codeunit "Shpfy Background Syncs"; + DraftOrderId: BigInteger; + NumberOfLines: Integer; + GraphQuery: TextBuilder; + begin + CreateDraftOrderGraphQL(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines, GraphQuery, NumberOfLines); + DraftOrderId := SendDraftOrderGraphQLRequest(GraphQuery); + ResponseJsonToken := CompleteDraftOrder(DraftOrderId, NumberOfLines); + FulfillShopifyOrder(ResponseJsonToken); + ShpfyBackgroundSyncs.InventorySync(ShpfyShop.Code); + end; + + local procedure CreateDraftOrderGraphQL( + var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; + var TempShpfyOrderLine: Record "Shpfy Order Line" temporary; + var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; + var GraphQuery: TextBuilder; + var NumberOfLines: Integer + ) + 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."Discount Amount" <> 0 then + AddDiscountAmountToGraphQuery(GraphQuery, TempShpfyOrderHeader."Discount Amount", 'Invoice Discount Amount'); + + GraphQuery.Append(', taxExempt: true'); + + AddShippingAddressTOGraphQuery(GraphQuery, TempShpfyOrderHeader); + AddBillingAddressTOGraphQuery(GraphQuery, TempShpfyOrderHeader); + AddLineItemsToGraphQuery(GraphQuery, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines, NumberOfLines); + AddNote(GraphQuery, TempShpfyOrderHeader); + if TempShpfyOrderHeader.Unpaid then + AddPaymentTerms(GraphQuery, TempShpfyOrderHeader); + + GraphQuery.Append('}) {draftOrder {id } userErrors {field, message}}'); + GraphQuery.Append('}"}'); + end; + + local procedure SendDraftOrderGraphQLRequest(GraphQuery: TextBuilder): BigInteger + var + ShpfyPaymentTermAPI: Codeunit "Shpfy Payment Terms API"; + ResponseJsonToken: JsonToken; + begin + ResponseJsonToken := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQuery.ToText()); + exit(ParseShopifyResponse(ResponseJsonToken, 'data.draftOrderCreate.draftOrder.id')); + end; + + local procedure CompleteDraftOrder(DraftOrderId: BigInteger; NumberOfLines: Integer): JsonToken + var + GraphQLType: Enum "Shpfy GraphQL Type"; + Parameters: Dictionary of [Text, Text]; + begin + GraphQLType := "Shpfy GraphQL Type"::DraftOrderComplete; + Parameters.Add('DraftOrderId', Format(DraftOrderId)); + Parameters.Add('NumberOfOrders', Format(NumberOfLines)); + exit(ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters)); + end; + + local procedure FulfillShopifyOrder(ResponseJsonToken: JsonToken) + var + FulfillmentOrderList: List of [Text]; + FulfillmentOrderId: Text; + ResponseToken: JsonToken; + GraphQLType: Enum "Shpfy GraphQL Type"; + Parameters: Dictionary of [Text, Text]; + begin + FulfillmentOrderList := ParseFulfillmentOrders(ResponseJsonToken); + + GraphQLType := "Shpfy GraphQL Type"::FulfillOrder; + + foreach FulfillmentOrderId in FulfillmentOrderList do begin + Parameters.Add('FulfillmentOrderId', FulfillmentOrderId); + ResponseToken := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); + Clear(Parameters); + end; + end; + + local procedure ParseFulfillmentOrders(ResponseJsonToken: JsonToken) FulfillmentOrderList: List of [Text] + var + + Counter: Integer; + FulfillmentOrderArray: JsonArray; + FulfillmentObject: JsonObject; + FulfillmentOrderToken: JsonToken; + JToken: JsonToken; + begin + FulfillmentObject := ResponseJsonToken.AsObject(); + FulfillmentObject.SelectToken('data.draftOrderComplete.draftOrder.order.fulfillmentOrders.nodes', JToken); + FulfillmentOrderArray := ShpfyJsonHelper.GetJsonArray(JToken, ''); + + for Counter := 0 to FulfillmentOrderArray.Count() - 1 do begin + FulfillmentOrderArray.Get(Counter, FulfillmentOrderToken); + FulfillmentOrderList.Add(Format(ParseShopifyResponse(FulfillmentOrderToken, 'id'))); + end; + end; + + internal procedure ParseShopifyResponse(JsonTokenResponse: JsonToken; TokenPath: Text): BigInteger + var + ShopifyParsedResponse: BigInteger; + ShopifyParsedText: Text; + begin + ShopifyParsedText := ShpfyJsonHelper.GetValueAsText(JsonTokenResponse, TokenPath); + ShopifyParsedText := CopyStr(ShopifyParsedText, ShopifyParsedText.LastIndexOf('/') + 1, StrLen(ShopifyParsedText)); + Evaluate(ShopifyParsedResponse, ShopifyParsedText); + exit(ShopifyParsedResponse); + end; + + local procedure AddShippingAddressTOGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) + var + myInt: Integer; + 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 NumberOfLines: Integer) + 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('\"'); + end; + + GraphQuery.Append(', quantity: '); + GraphQuery.Append(Format(TempShpfyOrderLine.Quantity, 0, 9)); + + GraphQuery.Append(', originalUnitPrice :'); + GraphQuery.Append(Format(TempShpfyOrderLine."Unit Price", 0, 9)); + + // if TempShpfyOrderLine."Discount Amount" <> 0 then + // AddDiscountAmountToGraphQuery(GraphQuery, TempShpfyOrderLine."Discount Amount", 'Line Discount Amount'); + + GraphQuery.Append('},'); + + NumberOfLines += 1; + 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))); //TODO: If the invoice discount amount is filled, not sure how to add that if the vat calculation types are different + GraphQuery.Append(Format(TempShpfyOrderHeader."VAT Amount")); + + GraphQuery.Append('},'); + NumberOfLines += 1; + end; + GraphQuery.Remove(GraphQuery.Length(), 1); + end; + GraphQuery.Append(']'); + end; + + local procedure AddDiscountAmountToGraphQuery(var GraphQuery: TextBuilder; DiscountAmount: Decimal; DiscountTitle: Text) + var + myInt: Integer; + begin + GraphQuery.Append(', appliedDiscount: {'); + GraphQuery.Append('description: \"'); + GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(DiscountTitle)); + GraphQuery.Append('\"'); + + GraphQuery.Append(', value: '); + GraphQuery.Append(Format(DiscountAmount)); + + 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"; + ShpfyPaymentTerm: Record "Shpfy Payment Terms"; + DueAtDateTime: DateTime; + IssuedAtDateTime: DateTime; + begin + if not ShopifyPaymentTermsExists(ShpfyPaymentTerm, TempShpfyOrderHeader, SalesInvoiceHeader) then + exit; + + GraphQuery.Append(', paymentTerms: {'); + GraphQuery.Append('paymentTermsTemplateId: \"gid://shopify/PaymentTermsTemplate/'); + GraphQuery.Append(Format(ShpfyPaymentTerm."Id")); + GraphQuery.Append('\"'); + + Evaluate(IssuedAtDateTime, Format(SalesInvoiceHeader."Document Date")); + Evaluate(DueAtDateTime, Format(SalesInvoiceHeader."Due Date")); + + GraphQuery.Append(', paymentSchedules: {'); + if ShpfyPaymentTerm.Type = 'FIXED' then begin //TODO: Maybe an Enum? + GraphQuery.Append('dueAt: \"'); + GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(DueAtDateTime, 0, 9))); + GraphQuery.Append('\"'); + end else + if ShpfyPaymentTerm.Type = 'NET' then begin //TODO: Maybe an Enum? + GraphQuery.Append(', issuedAt: \"'); + GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(IssuedAtDateTime, 0, 9))); + GraphQuery.Append('\"'); + end; + + GraphQuery.Append('}}'); + end; + + local procedure ShopifyPaymentTermsExists( + var ShpfyPaymentTerm: 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."); + ShpfyPaymentTerm.SetRange("Payment Terms Code", SalesInvoiceHeader."Payment Terms Code"); + ShpfyPaymentTerm.SetRange("Shop Code", ShpfyShop.Code); + + if not ShpfyPaymentTerm.FindFirst() then begin + ShpfyPaymentTerm.SetRange("Payment Terms Code"); + ShpfyPaymentTerm.SetRange("Is Primary", true); + + if not ShpfyPaymentTerm.FindFirst() then + exit(false); + end; + + exit(true); + end; + + internal procedure SetShop(ShopCode: Code[20]) + begin + Clear(ShpfyShop); + ShpfyShop.Get(ShopCode); + ShpfyCommunicationMgt.SetShop(ShpfyShop); + 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..2b3f924772 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -0,0 +1,370 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Sales.History; +using Microsoft.Utilities; +using Microsoft.Sales.Receivables; + +/// +/// Codeunit Shpfy Export Invoice (ID 30105). +/// +codeunit 30105 "Shpfy Posted Invoice Export" +{ + Access = Internal; + TableNo = "Sales Invoice Header"; + + var + ShpfyShop: Record "Shpfy Shop"; + ShpfyDraftOrdersAPI: Codeunit "Shpfy Draft Orders API"; + ShpfyJsonHelper: Codeunit "Shpfy Json Helper"; + + trigger OnRun() + begin + ExportPostedSalesInvoiceToShopify(Rec); + end; + + /// + /// Set Shop. + /// + /// Parameter of type Code[20]. + internal procedure SetShop(NewShopCode: Code[20]) + begin + ShpfyShop.Get(NewShopCode); + end; + + local procedure ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader: Record "Sales Invoice Header") + var + TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; + TempShpfyOrderLine: Record "Shpfy Order Line" temporary; + ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; + JsonTokenResponse: JsonToken; + begin + if not IsInvoiceExportable(SalesInvoiceHeader) then begin + SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -2, ''); + exit; + end; + + MapPostedSalesInvoiceData(SalesInvoiceHeader, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); + + ShpfyDraftOrdersAPI.SetShop(ShpfyShop.Code); + JsonTokenResponse := ShpfyDraftOrdersAPI.ExportOrderToShopify(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); + + if IsSuccess(JsonTokenResponse) then begin + CreateShpfyInvoiceHeader(JsonTokenResponse, SalesInvoiceHeader."No."); + SetSalesInvoiceShopifyOrderInformation( + SalesInvoiceHeader, + ShpfyDraftOrdersAPI.ParseShopifyResponse(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.id'), + Format(ShpfyJsonHelper.GetValueAsText(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.name')) + ); + AddDocumentLinkToBCDocument(SalesInvoiceHeader); + end else + SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -1, ''); + 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 ShpfyShop."Default Customer No." = SalesInvoiceHeader."Bill-to Customer No." then + exit(false); + + if not CheckSalesInvoiceHeaderLines(SalesInvoiceHeader) then + exit(false); + + //TODO: Fractions check? + + exit(true); + 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."); + if SalesInvoiceLine.FindSet() then + repeat + 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"); + 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("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 +) + var + DocumentTotals: Codeunit "Document Totals"; + 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" := SalesInvoiceHeader."Currency Code"; + TempShpfyOrderHeader."Document Date" := SalesInvoiceHeader."Document Date"; + SalesInvoiceHeader.CalcFields(Amount, "Amount Including VAT", "Invoice Discount Amount"); + TempShpfyOrderHeader."VAT Amount" := SalesInvoiceHeader."Amount Including VAT" - SalesInvoiceHeader.Amount; + + MapBillInformation(TempShpfyOrderHeader, SalesInvoiceHeader); + MapShipToInformation(TempShpfyOrderHeader, SalesInvoiceHeader); + MapCustomerLedgerEntryInformation(SalesInvoiceHeader, TempShpfyOrderHeader); + + TempShpfyOrderHeader."Fulfillment Status" := Enum::"Shpfy Order Fulfill. Status"::Fulfilled; + SalesInvoiceHeader.CalcFields("Invoice Discount Amount"); + TempShpfyOrderHeader."Discount Amount" := SalesInvoiceHeader."Invoice Discount Amount"; + TempShpfyOrderHeader."Shop Code" := ShpfyShop.Code; + TempShpfyOrderHeader.Insert(); + end; + + local procedure MapBillInformation( + var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; + SalesInvoiceHeader: Record "Sales Invoice Header" + ) + var + ShpfyCustomer: Record "Shpfy Customer"; + begin + TempShpfyOrderHeader."Bill-to Name" := SalesInvoiceHeader."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 := ShpfyCustomer.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" := SalesInvoiceHeader."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 MapCustomerLedgerEntryInformation( + SalesInvoiceHeader: Record "Sales Invoice Header"; + var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) + var + CustLedgerEntry: Record "Cust. Ledger Entry"; + begin + CustLedgerEntry.Get(SalesInvoiceHeader."Cust. Ledger Entry No."); + CustLedgerEntry.CalcFields("Remaining Amount"); + + if CustLedgerEntry."Remaining Amount" = 0 then + TempShpfyOrderHeader.Unpaid := false + else + TempShpfyOrderHeader.Unpaid := true; + 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."Gift Card" := false; + TempShpfyOrderLine.Taxable := false; + TempShpfyOrderLine."Unit Price" := SalesInvoiceLine."Unit Price"; + TempShpfyOrderHeader."Discount Amount" += SalesInvoiceLine."Line Discount Amount"; + TempShpfyOrderHeader.Modify(); + + if ShpfyShop."UoM as Variant" then + MapUOMProductVariants(SalesInvoiceLine, TempShpfyOrderLine) + else begin + 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(); + end; + + local procedure MapUOMProductVariants(SalesInvoiceLine: Record "Sales Invoice Line"; var TempShpfyOrderLine: Record "Shpfy Order Line" temporary) + var + ShpfyVariant: Record "Shpfy Variant"; + begin + 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 + TaxTitle: Text; + begin + TaxTitle := Format(SalesInvoiceLine."VAT Calculation Type"); //TODO: Comment that if language is different, then this will also be translated since this is a caption value + if ShpfyOrderTaxLines.ContainsKey(TaxTitle) then + ShpfyOrderTaxLines.Set(TaxTitle, ShpfyOrderTaxLines.Get(TaxTitle) + SalesInvoiceLine.GetLineAmountInclVAT() - SalesInvoiceLine.GetLineAmountExclVAT()) + else + ShpfyOrderTaxLines.Add(TaxTitle, SalesInvoiceLine.GetLineAmountInclVAT() - SalesInvoiceLine.GetLineAmountExclVAT()); + end; + + local procedure IsSuccess(JsonTokenResponse: JsonToken): Boolean + begin + exit(ShpfyJsonHelper.GetJsonArray(JsonTokenResponse, 'data.draftOrderComplete.userErrors').Count() = 0); + end; + + local procedure CreateShpfyInvoiceHeader(JsonTokenResponse: JsonToken; InvoiceNo: Code[20]) + var + ShpfyInvoiceHeader: Record "Shpfy Invoice Header"; + begin + ShpfyInvoiceHeader.Init(); + ShpfyInvoiceHeader.Validate("Shopify Order Id", ShpfyDraftOrdersAPI.ParseShopifyResponse(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.id')); + ShpfyInvoiceHeader.Validate("Shopify Order No.", Format(ShpfyJsonHelper.GetValueAsText(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.name'))); + 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(); + 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..450780f55e --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al @@ -0,0 +1,20 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Sales.History; + +codeunit 30314 "Shpfy Update Sales Invoice" +{ + [EventSubscriber(ObjectType::Page, Page::"Posted Sales Inv. - Update", 'OnAfterRecordChanged', '', false, false)] + local procedure OnAfterRecordChanged(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 OnBeforeSalesShptHeaderModify(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..ed50c9f8e8 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al @@ -0,0 +1,42 @@ +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) + { + 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 Fulfillment ID. Helps track the status of shipments 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/Pages/ShpfyInvoices.Page.al b/Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al new file mode 100644 index 0000000000..e43b6f6494 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al @@ -0,0 +1,32 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Page Shpfy Invoices (ID 30138). +/// +page 30138 "Shpfy Invoices" //TODO: Maybe this page is not needed? +{ + PageType = List; + Caption = 'Shopify Invoices'; + ApplicationArea = All; + UsageCategory = Lists; + SourceTable = "Shpfy Invoice Header"; + Editable = false; + + layout + { + area(Content) + { + repeater(GroupName) + { + field("Shopify Order Id"; Rec."Shopify Order Id") + { + ToolTip = 'Specifies the value of the Shopify Order Id field.'; + } + field("Shopify Order No."; Rec."Shopify Order No.") + { + ToolTip = 'Specifies the value of the Shopify Order No. field.'; + } + } + } + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al new file mode 100644 index 0000000000..b1633b4eb1 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al @@ -0,0 +1,89 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Sales.History; + +/// +/// Report Shpfy Export Invoice to Shpfy (ID 30117). +/// +report 30117 "Shpfy Export Invoice to Shpfy" +{ + ApplicationArea = All; + Caption = 'Export Invoice to Shopify'; + ProcessingOnly = true; + UsageCategory = Administration; + + dataset + { + dataitem(SalesInvoiceHeader; "Sales Invoice Header") + { + RequestFilterFields = "No.", "Posting Date"; + trigger OnPreDataItem() + begin + 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.SetShop(ShopCode); + ShpfyPostedInvoiceExport.Run(SalesInvoiceHeader); + end; + + trigger OnPostDataItem() + begin + if GuiAllowed then + ProcessDialog.Close(); + 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.'; + ShowMandatory = true; + } + } + } + } + } + + var + 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.'; + + /// + /// Set Shop. + /// + /// Parameter of type Code[20]. + 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..5f1234122b --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al @@ -0,0 +1,31 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Table Shpfy Invoice Header (ID 30156). +/// +table 30156 "Shpfy Invoice Header" +{ + Caption = 'Shopify Invoice Header'; + DataClassification = CustomerContent; + LookupPageId = "Shpfy Invoices"; + + fields + { + field(1; "Shopify Order Id"; BigInteger) + { + Caption = 'Shopify Order Id'; + } + field(20; "Shopify Order No."; Code[50]) + { + Caption = 'Shopify Order Name'; + } + } + + 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 a92bb50c4e..bb9773200e 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 @@ -117,6 +117,24 @@ 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") + var + myInt: Integer; + 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) @@ -500,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 @@ -618,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; 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 afc8ec9ef4..bb766b47a9 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 @@ -123,6 +123,8 @@ codeunit 30166 "Shpfy Process Order" SalesHeader.Validate("Shipment Method Code", ShopifyOrderHeader."Shipping Method Code"); 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); @@ -150,6 +152,16 @@ 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(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 16fabfc966..5ea22f39c3 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 @@ -727,6 +727,16 @@ table 30118 "Shpfy Order Header" Caption = 'Has Order State Error'; DataClassification = SystemMetadata; } + 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/ShpfyPaymentTermAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al new file mode 100644 index 0000000000..8904a6ff4b --- /dev/null +++ b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al @@ -0,0 +1,54 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy Payment Terms API (ID 30168). +/// +codeunit 30168 "Shpfy Payment Terms API" +{ + internal procedure PullPaymentTermsCodes(ShopCode: Code[20]) + var + Shop: Record "Shpfy Shop"; + PaymentTerms: Record "Shpfy Payment Terms"; + PaymentTermRecordRef: RecordRef; + CommunicationMgt: Codeunit "Shpfy Communication Mgt."; + ShpfyDraftOrdersAPI: Codeunit "Shpfy Draft Orders API"; + JsonHelper: Codeunit "Shpfy Json Helper"; + GraphQLType: Enum "Shpfy GraphQL Type"; + JArray: JsonArray; + JToken: JsonToken; + JResponse: JsonToken; + IsNew: Boolean; + Id: BigInteger; + begin + Shop.Get(ShopCode); + + CommunicationMgt.SetShop(Shop.Code); + + GraphQLType := GraphQLType::GetPaymentTerms; + JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType); + + JsonHelper.GetJsonArray(JResponse, JArray, 'data.paymentTermsTemplates'); + foreach JToken in JArray do begin + Id := ShpfyDraftOrdersAPI.ParseShopifyResponse(JToken, 'id'); + IsNew := not PaymentTerms.Get(ShopCode, Id); + + if IsNew then begin + Clear(PaymentTerms); + PaymentTerms."Id" := Id; + PaymentTerms."Shop Code" := ShopCode; + end; + + PaymentTermRecordRef.GetTable(PaymentTerms); + JsonHelper.GetValueIntoField(JToken, 'name', PaymentTermRecordRef, PaymentTerms.FieldNo(Name)); + JsonHelper.GetValueIntoField(JToken, 'paymentTermsType', PaymentTermRecordRef, PaymentTerms.FieldNo(Type)); + JsonHelper.GetValueIntoField(JToken, 'dueInDays', PaymentTermRecordRef, PaymentTerms.FieldNo("Due In Days")); + JsonHelper.GetValueIntoField(JToken, 'description', PaymentTermRecordRef, PaymentTerms.FieldNo(Description)); + PaymentTermRecordRef.SetTable(PaymentTerms); + + if IsNew then + PaymentTerms.Insert() + else + PaymentTerms.Modify(); + end; + 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..98b848df51 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al @@ -0,0 +1,70 @@ +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(GroupName) + { + 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(Processing) + { + action(Refresh) + { + ApplicationArea = All; + Caption = 'Refresh'; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + Image = Refresh; + ToolTip = 'Refreshes the list of Shopify Payment Terms.'; + + trigger OnAction() + var + ShpfyPaymentTermAPI: Codeunit "Shpfy Payment Terms API"; + begin + ShpfyPaymentTermAPI.PullPaymentTermsCodes(CopyStr(Rec.GetFilter("Shop Code"), 1, 20)); + end; + } + } + } +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al new file mode 100644 index 0000000000..97aa1e6b94 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al @@ -0,0 +1,64 @@ +namespace Microsoft.Integration.Shopify; + +using Microsoft.Foundation.PaymentTerms; + +/// +/// Table Shpfy Payment Terms (ID 30157). +/// +table 30157 "Shpfy Payment Terms" +{ + Caption = 'Payment Terms'; + DataClassification = CustomerContent; + + 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'; + } + 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 20ab6069b9..b8493d9608 100644 --- a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al +++ b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al @@ -64,5 +64,7 @@ permissionset 30102 "Shpfy - Edit" tabledata "Shpfy Tag" = IMD, tabledata "Shpfy Tax Area" = IMD, tabledata "Shpfy Transaction Gateway" = IMD, - tabledata "Shpfy Variant" = IMD; + tabledata "Shpfy Variant" = IMD, + tabledata "Shpfy Payment Terms" = IMD, + tabledata "Shpfy Invoice Header" = IMD; } \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al index e93a734675..2fc5a8bc9b 100644 --- a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al +++ b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al @@ -64,6 +64,8 @@ permissionset 30100 "Shpfy - Read" tabledata "Shpfy Tag" = R, tabledata "Shpfy Tax Area" = R, tabledata "Shpfy Transaction Gateway" = R, - tabledata "Shpfy Variant" = R; + tabledata "Shpfy Variant" = R, + tabledata "Shpfy Payment Terms" = R, + tabledata "Shpfy Invoice Header" = R; } #pragma warning restore AS0090, AS0049 \ No newline at end of file From e7bbf90d5dc270995a56c7a4b8365f80e458c953 Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Tue, 25 Jun 2024 09:40:53 +0300 Subject: [PATCH 2/8] Fixed PR comments --- .../app/src/Base/Pages/ShpfyShopCard.Page.al | 9 +- .../app/src/Base/Tables/ShpfyShop.Table.al | 4 + .../ShpfyGQLDraftOrderComplete.Codeunit.al | 4 +- .../ShpfyAuthenticationMgt.Codeunit.al | 2 +- .../Codeunits/ShpfyDraftOrdersAPI.Codeunit.al | 176 ++++++++---------- .../Codeunits/ShpfyFulfillmentAPI.Codeunit.al | 53 ++++++ .../ShpfyPostedInvoiceExport.Codeunit.al | 79 +++++--- .../ShpfySalesInvoiceUpdate.PageExt.al | 2 +- .../src/Invoicing/Pages/ShpfyInvoices.Page.al | 32 ---- .../ShpfyExportInvoiceToShpfy.Report.al | 15 +- .../Tables/ShpfyInvoiceHeader.Table.al | 5 - .../Codeunits/ShpfyPaymentTermAPI.Codeunit.al | 27 +-- .../PermissionSets/ShpfyEdit.PermissionSet.al | 6 +- .../PermissionSets/ShpfyRead.PermissionSet.al | 6 +- 14 files changed, 234 insertions(+), 186 deletions(-) create mode 100644 Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al delete mode 100644 Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al 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 53ca7be460..3440548da2 100644 --- a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al +++ b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al @@ -139,6 +139,12 @@ page 30101 "Shpfy Shop Card" ToolTip = 'Specifies the date on which Business Central will no longer support Shopify Admin API version. To continue to use your integration, upgrade to the latest version of Business Central before this date.'; Editable = false; } + field("Posted Invoice Sync"; Rec."Posted Invoice Sync") + { + ApplicationArea = All; + Importance = Additional; + ToolTip = 'Specifies whether the posted sales invoices can be synchronized to Shopify.'; + } } group(ItemSync) { @@ -996,11 +1002,12 @@ page 30101 "Shpfy Shop Card" ApplicationArea = All; Caption = 'Sync Posted Sales Invoices'; Image = Export; + Enabled = Rec."Posted Invoice Sync"; Promoted = true; PromotedCategory = Category5; PromotedIsBig = true; PromotedOnly = true; - ToolTip = 'Synchronize invoices to Shopify.'; + ToolTip = 'Synchronize posted sales invoices to Shopify. The action can be used only if the Posted Invoice Sync field is enabled in the Shopify shop.'; trigger OnAction(); var 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 e09f381742..dd020af6e8 100644 --- a/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al +++ b/Apps/W1/Shopify/app/src/Base/Tables/ShpfyShop.Table.al @@ -792,6 +792,10 @@ table 30102 "Shpfy Shop" { Caption = 'Items Must be Mapped to Products'; } + field(220; "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 index 5743a06e08..d3f55cff0c 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al @@ -13,7 +13,7 @@ codeunit 30312 "Shpfy GQL DraftOrderComplete" implements "Shpfy IGraphQL" /// Return value of type Text. internal procedure GetGraphQL(): Text begin - exit('{"query": "mutation {draftOrderComplete(id: \"gid://shopify/DraftOrder/{{DraftOrderId}}\") { draftOrder { order { id, name, fulfillmentOrders(first: {{NumberOfOrders}}) {nodes{ id, status}} } id, status} userErrors { field, message }}}"}'); + exit('{"query": "mutation {draftOrderComplete(id: \"gid://shopify/DraftOrder/{{DraftOrderId}}\") { draftOrder { order { legacyResourceId, name, fulfillmentOrders(first: {{NumberOfOrders}}) {nodes{ id, status}} } id, status} userErrors { field, message }}}"}'); end; /// @@ -22,6 +22,6 @@ codeunit 30312 "Shpfy GQL DraftOrderComplete" implements "Shpfy IGraphQL" /// Return value of type Integer. internal procedure GetExpectedCost(): Integer begin - exit(10); + exit(15); end; } 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 d290137b75..f914ac6af8 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_payment_terms,write_draft_orders,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', 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,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,write_payment_terms,write_draft_orders', 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 index 0b21fc5d4b..aef57d379b 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al @@ -2,6 +2,7 @@ namespace Microsoft.Integration.Shopify; using Microsoft.Sales.Comment; using Microsoft.Sales.History; +using Microsoft.Finance.Currency; /// /// Codeunit Draft Orders API (ID 30159). @@ -15,31 +16,64 @@ codeunit 30159 "Shpfy Draft Orders API" ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; ShpfyJsonHelper: Codeunit "Shpfy Json Helper"; - internal procedure ExportOrderToShopify( + /// + /// 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] - ) ResponseJsonToken: JsonToken + ): BigInteger var - ShpfyBackgroundSyncs: Codeunit "Shpfy Background Syncs"; - DraftOrderId: BigInteger; - NumberOfLines: Integer; GraphQuery: TextBuilder; + DraftOrderId: BigInteger; begin - CreateDraftOrderGraphQL(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines, GraphQuery, NumberOfLines); + GraphQuery := CreateDraftOrderGQLRequest(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); DraftOrderId := SendDraftOrderGraphQLRequest(GraphQuery); - ResponseJsonToken := CompleteDraftOrder(DraftOrderId, NumberOfLines); - FulfillShopifyOrder(ResponseJsonToken); - ShpfyBackgroundSyncs.InventorySync(ShpfyShop.Code); + exit(DraftOrderId); + end; + + /// + /// Completes a draft order in shopify by converting it to an order. + /// + /// Draft order id that needs to be completed. + /// Maximum amount of possible fulfillment orders. + /// Json response of a created order in shopify. + internal procedure CompleteDraftOrder(DraftOrderId: BigInteger; NumberOfLines: Integer): 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)); + Parameters.Add('NumberOfOrders', Format(NumberOfLines)); + JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); + exit(JResponse); + end; + + /// + /// Sets a global shopify shop variable to be used. + /// + /// Shopify shop code to be set. + internal procedure SetShop(ShopCode: Code[20]) + begin + Clear(ShpfyShop); + ShpfyShop.Get(ShopCode); + ShpfyCommunicationMgt.SetShop(ShpfyShop); end; - local procedure CreateDraftOrderGraphQL( + local procedure CreateDraftOrderGQLRequest( var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; var TempShpfyOrderLine: Record "Shpfy Order Line" temporary; - var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; - var GraphQuery: TextBuilder; - var NumberOfLines: Integer - ) + var ShpfyOrderTaxLines: Dictionary of [Text, Decimal] + ): TextBuilder + var + GraphQuery: TextBuilder; begin GraphQuery.Append('{"query":"mutation {draftOrderCreate(input: {'); if TempShpfyOrderHeader.Email <> '' then begin @@ -52,92 +86,40 @@ codeunit 30159 "Shpfy Draft Orders API" 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); - AddLineItemsToGraphQuery(GraphQuery, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines, NumberOfLines); + AddShippingAddressToGraphQuery(GraphQuery, TempShpfyOrderHeader); + AddBillingAddressToGraphQuery(GraphQuery, TempShpfyOrderHeader); + AddLineItemsToGraphQuery(GraphQuery, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); AddNote(GraphQuery, TempShpfyOrderHeader); if TempShpfyOrderHeader.Unpaid then AddPaymentTerms(GraphQuery, TempShpfyOrderHeader); - GraphQuery.Append('}) {draftOrder {id } userErrors {field, message}}'); + GraphQuery.Append('}) {draftOrder { legacyResourceId } userErrors {field, message}}'); GraphQuery.Append('}"}'); + + exit(GraphQuery); end; local procedure SendDraftOrderGraphQLRequest(GraphQuery: TextBuilder): BigInteger var ShpfyPaymentTermAPI: Codeunit "Shpfy Payment Terms API"; - ResponseJsonToken: JsonToken; - begin - ResponseJsonToken := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQuery.ToText()); - exit(ParseShopifyResponse(ResponseJsonToken, 'data.draftOrderCreate.draftOrder.id')); - end; - - local procedure CompleteDraftOrder(DraftOrderId: BigInteger; NumberOfLines: Integer): JsonToken - var - GraphQLType: Enum "Shpfy GraphQL Type"; - Parameters: Dictionary of [Text, Text]; - begin - GraphQLType := "Shpfy GraphQL Type"::DraftOrderComplete; - Parameters.Add('DraftOrderId', Format(DraftOrderId)); - Parameters.Add('NumberOfOrders', Format(NumberOfLines)); - exit(ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters)); - end; - - local procedure FulfillShopifyOrder(ResponseJsonToken: JsonToken) - var - FulfillmentOrderList: List of [Text]; - FulfillmentOrderId: Text; - ResponseToken: JsonToken; - GraphQLType: Enum "Shpfy GraphQL Type"; - Parameters: Dictionary of [Text, Text]; - begin - FulfillmentOrderList := ParseFulfillmentOrders(ResponseJsonToken); - - GraphQLType := "Shpfy GraphQL Type"::FulfillOrder; - - foreach FulfillmentOrderId in FulfillmentOrderList do begin - Parameters.Add('FulfillmentOrderId', FulfillmentOrderId); - ResponseToken := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); - Clear(Parameters); - end; - end; - - local procedure ParseFulfillmentOrders(ResponseJsonToken: JsonToken) FulfillmentOrderList: List of [Text] - var - - Counter: Integer; - FulfillmentOrderArray: JsonArray; - FulfillmentObject: JsonObject; - FulfillmentOrderToken: JsonToken; - JToken: JsonToken; - begin - FulfillmentObject := ResponseJsonToken.AsObject(); - FulfillmentObject.SelectToken('data.draftOrderComplete.draftOrder.order.fulfillmentOrders.nodes', JToken); - FulfillmentOrderArray := ShpfyJsonHelper.GetJsonArray(JToken, ''); - - for Counter := 0 to FulfillmentOrderArray.Count() - 1 do begin - FulfillmentOrderArray.Get(Counter, FulfillmentOrderToken); - FulfillmentOrderList.Add(Format(ParseShopifyResponse(FulfillmentOrderToken, 'id'))); - end; - end; - - internal procedure ParseShopifyResponse(JsonTokenResponse: JsonToken; TokenPath: Text): BigInteger - var - ShopifyParsedResponse: BigInteger; - ShopifyParsedText: Text; + JResponse: JsonToken; + DraftOrderId: BigInteger; begin - ShopifyParsedText := ShpfyJsonHelper.GetValueAsText(JsonTokenResponse, TokenPath); - ShopifyParsedText := CopyStr(ShopifyParsedText, ShopifyParsedText.LastIndexOf('/') + 1, StrLen(ShopifyParsedText)); - Evaluate(ShopifyParsedResponse, ShopifyParsedText); - exit(ShopifyParsedResponse); + 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) + local procedure AddShippingAddressToGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) var myInt: Integer; begin @@ -179,7 +161,7 @@ codeunit 30159 "Shpfy Draft Orders API" GraphQuery.Append('}'); end; - local procedure AddBillingAddressTOGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) + local procedure AddBillingAddressToGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) begin GraphQuery.Append(', billingAddress: {'); if TempShpfyOrderHeader."Bill-to Address" <> '' then begin @@ -223,8 +205,8 @@ codeunit 30159 "Shpfy Draft Orders API" var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; var TempShpfyOrderLine: Record "Shpfy Order Line" temporary; - var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; - var NumberOfLines: Integer) + var ShpfyOrderTaxLines: Dictionary of [Text, Decimal] + ) var TaxTitle: Text; begin @@ -249,12 +231,7 @@ codeunit 30159 "Shpfy Draft Orders API" GraphQuery.Append(', originalUnitPrice :'); GraphQuery.Append(Format(TempShpfyOrderLine."Unit Price", 0, 9)); - // if TempShpfyOrderLine."Discount Amount" <> 0 then - // AddDiscountAmountToGraphQuery(GraphQuery, TempShpfyOrderLine."Discount Amount", 'Line Discount Amount'); - GraphQuery.Append('},'); - - NumberOfLines += 1; until TempShpfyOrderLine.Next() = 0; foreach TaxTitle in ShpfyOrderTaxLines.Keys() do begin @@ -267,11 +244,9 @@ codeunit 30159 "Shpfy Draft Orders API" GraphQuery.Append(Format(1, 0, 9)); GraphQuery.Append(', originalUnitPrice: '); - //GraphQuery.Append(Format(ShpfyOrderTaxLines.Get(TaxTitle))); //TODO: If the invoice discount amount is filled, not sure how to add that if the vat calculation types are different - GraphQuery.Append(Format(TempShpfyOrderHeader."VAT Amount")); + GraphQuery.Append(Format(ShpfyOrderTaxLines.Get(TaxTitle), 0, 9)); GraphQuery.Append('},'); - NumberOfLines += 1; end; GraphQuery.Remove(GraphQuery.Length(), 1); end; @@ -288,7 +263,7 @@ codeunit 30159 "Shpfy Draft Orders API" GraphQuery.Append('\"'); GraphQuery.Append(', value: '); - GraphQuery.Append(Format(DiscountAmount)); + GraphQuery.Append(Format(DiscountAmount, 0, 9)); GraphQuery.Append(', valueType: '); GraphQuery.Append('FIXED_AMOUNT'); @@ -337,12 +312,12 @@ codeunit 30159 "Shpfy Draft Orders API" Evaluate(DueAtDateTime, Format(SalesInvoiceHeader."Due Date")); GraphQuery.Append(', paymentSchedules: {'); - if ShpfyPaymentTerm.Type = 'FIXED' then begin //TODO: Maybe an Enum? + if ShpfyPaymentTerm.Type = 'FIXED' then begin GraphQuery.Append('dueAt: \"'); GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(DueAtDateTime, 0, 9))); GraphQuery.Append('\"'); end else - if ShpfyPaymentTerm.Type = 'NET' then begin //TODO: Maybe an Enum? + if ShpfyPaymentTerm.Type = 'NET' then begin GraphQuery.Append(', issuedAt: \"'); GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(IssuedAtDateTime, 0, 9))); GraphQuery.Append('\"'); @@ -372,10 +347,11 @@ codeunit 30159 "Shpfy Draft Orders API" exit(true); end; - internal procedure SetShop(ShopCode: Code[20]) + local procedure GetISOCode(CurrencyCode: Code[10]): Code[3] + var + Currency: Record Currency; begin - Clear(ShpfyShop); - ShpfyShop.Get(ShopCode); - ShpfyCommunicationMgt.SetShop(ShpfyShop); + Currency.Get(CurrencyCode); + exit(Currency."ISO Code"); 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..721acf1a1b --- /dev/null +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al @@ -0,0 +1,53 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy Fulfillment API (ID 30315). +/// +codeunit 30315 "Shpfy Fulfillment API" +{ + var + ShpfyJsonHelper: Codeunit "Shpfy Json Helper"; + ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; + + /// + /// Fulfills shopify orders for each fulfillment id parsed from a completed draft order. + /// + /// Json response from a completed draft order + /// Shopify shop code to be used. + internal procedure FulfillShopifyOrder(JResponse: JsonToken; ShopCode: Code[20]) + var + FulfillmentOrderList: List of [Text]; + FulfillmentOrderId: Text; + ResponseJsonToken: JsonToken; + GraphQLType: Enum "Shpfy GraphQL Type"; + Parameters: Dictionary of [Text, Text]; + begin + ShpfyCommunicationMgt.SetShop(ShopCode); + FulfillmentOrderList := ParseFulfillmentOrders(JResponse); + GraphQLType := "Shpfy GraphQL Type"::FulfillOrder; + + foreach FulfillmentOrderId in FulfillmentOrderList do begin + Parameters.Add('FulfillmentOrderId', FulfillmentOrderId); + ResponseJsonToken := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); + Clear(Parameters); + end; + end; + + local procedure ParseFulfillmentOrders(ResponseJsonToken: JsonToken) FulfillmentOrderList: List of [Text] + var + Counter: Integer; + FulfillmentOrderArray: JsonArray; + FulfillmentObject: JsonObject; + FulfillmentOrderToken: JsonToken; + JToken: JsonToken; + begin + FulfillmentObject := ResponseJsonToken.AsObject(); + FulfillmentObject.SelectToken('data.draftOrderComplete.draftOrder.order.fulfillmentOrders.nodes', JToken); + FulfillmentOrderArray := ShpfyJsonHelper.GetJsonArray(JToken, ''); + + for Counter := 0 to FulfillmentOrderArray.Count() - 1 do begin + FulfillmentOrderArray.Get(Counter, FulfillmentOrderToken); + FulfillmentOrderList.Add(Format(ShpfyCommunicationMgt.GetIdOfGId(ShpfyJsonHelper.GetValueAsText(FulfillmentOrderToken, 'id')))); + end; + 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 index 2b3f924772..0b7def96b4 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -1,13 +1,14 @@ namespace Microsoft.Integration.Shopify; using Microsoft.Sales.History; +using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Utilities; using Microsoft.Sales.Receivables; /// -/// Codeunit Shpfy Export Invoice (ID 30105). +/// Codeunit Shpfy Posted Invoice Export" (ID 30316). /// -codeunit 30105 "Shpfy Posted Invoice Export" +codeunit 30316 "Shpfy Posted Invoice Export" { Access = Internal; TableNo = "Sales Invoice Header"; @@ -23,20 +24,30 @@ codeunit 30105 "Shpfy Posted Invoice Export" end; /// - /// Set Shop. + /// Sets a global shopify shop variable to be used. /// - /// Parameter of type Code[20]. + /// Shopify shop code to be set. internal procedure SetShop(NewShopCode: Code[20]) begin ShpfyShop.Get(NewShopCode); end; - local procedure ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader: Record "Sales Invoice Header") + /// + /// 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; + ShpfyFulfillmentAPI: Codeunit "Shpfy Fulfillment API"; ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; - JsonTokenResponse: JsonToken; + JResponse: JsonToken; + DraftOrderId: BigInteger; begin if not IsInvoiceExportable(SalesInvoiceHeader) then begin SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -2, ''); @@ -46,14 +57,16 @@ codeunit 30105 "Shpfy Posted Invoice Export" MapPostedSalesInvoiceData(SalesInvoiceHeader, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); ShpfyDraftOrdersAPI.SetShop(ShpfyShop.Code); - JsonTokenResponse := ShpfyDraftOrdersAPI.ExportOrderToShopify(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); + DraftOrderId := ShpfyDraftOrdersAPI.CreateDraftOrder(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); + JResponse := ShpfyDraftOrdersAPI.CompleteDraftOrder(DraftOrderId, GetNumberOfLines(TempShpfyOrderLine, ShpfyOrderTaxLines)); + ShpfyFulfillmentAPI.FulfillShopifyOrder(JResponse, ShpfyShop.Code); - if IsSuccess(JsonTokenResponse) then begin - CreateShpfyInvoiceHeader(JsonTokenResponse, SalesInvoiceHeader."No."); + if IsSuccess(JResponse) then begin + CreateShpfyInvoiceHeader(JResponse, SalesInvoiceHeader."No."); SetSalesInvoiceShopifyOrderInformation( SalesInvoiceHeader, - ShpfyDraftOrdersAPI.ParseShopifyResponse(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.id'), - Format(ShpfyJsonHelper.GetValueAsText(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.name')) + ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId'), + Format(ShpfyJsonHelper.GetValueAsText(JResponse, 'data.draftOrderComplete.draftOrder.order.name')) ); AddDocumentLinkToBCDocument(SalesInvoiceHeader); end else @@ -78,8 +91,6 @@ codeunit 30105 "Shpfy Posted Invoice Export" if not CheckSalesInvoiceHeaderLines(SalesInvoiceHeader) then exit(false); - //TODO: Fractions check? - exit(true); end; @@ -94,8 +105,12 @@ codeunit 30105 "Shpfy Posted Invoice Export" 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); @@ -189,7 +204,7 @@ codeunit 30105 "Shpfy Posted Invoice Export" TempShpfyOrderHeader."Created At" := SalesInvoiceHeader.SystemCreatedAt; TempShpfyOrderHeader.Confirmed := true; TempShpfyOrderHeader."Updated At" := SalesInvoiceHeader.SystemModifiedAt; - TempShpfyOrderHeader."Currency Code" := SalesInvoiceHeader."Currency Code"; + 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; @@ -205,6 +220,17 @@ codeunit 30105 "Shpfy Posted Invoice Export" TempShpfyOrderHeader.Insert(); 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 MapBillInformation( var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; SalesInvoiceHeader: Record "Sales Invoice Header" @@ -326,18 +352,19 @@ codeunit 30105 "Shpfy Posted Invoice Export" until ShpfyVariant.Next() = 0; end; - local procedure MapTaxLine( - var SalesInvoiceLine: Record "Sales Invoice Line" temporary; - var ShpfyOrderTaxLines: Dictionary of [Text, Decimal] - ) + local procedure MapTaxLine(var SalesInvoiceLine: Record "Sales Invoice Line" temporary; var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]) var TaxTitle: Text; + VATAmount: Decimal; + TaxLineTok: Label '%1 - %2%', Comment = '%1 = VAT Calculation Type, %2 = VAT %', Locked = true; begin - TaxTitle := Format(SalesInvoiceLine."VAT Calculation Type"); //TODO: Comment that if language is different, then this will also be translated since this is a caption value + 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) + SalesInvoiceLine.GetLineAmountInclVAT() - SalesInvoiceLine.GetLineAmountExclVAT()) + ShpfyOrderTaxLines.Set(TaxTitle, ShpfyOrderTaxLines.Get(TaxTitle) + VATAmount) else - ShpfyOrderTaxLines.Add(TaxTitle, SalesInvoiceLine.GetLineAmountInclVAT() - SalesInvoiceLine.GetLineAmountExclVAT()); + ShpfyOrderTaxLines.Add(TaxTitle, VATAmount); end; local procedure IsSuccess(JsonTokenResponse: JsonToken): Boolean @@ -345,13 +372,12 @@ codeunit 30105 "Shpfy Posted Invoice Export" exit(ShpfyJsonHelper.GetJsonArray(JsonTokenResponse, 'data.draftOrderComplete.userErrors').Count() = 0); end; - local procedure CreateShpfyInvoiceHeader(JsonTokenResponse: JsonToken; InvoiceNo: Code[20]) + local procedure CreateShpfyInvoiceHeader(JResponse: JsonToken; InvoiceNo: Code[20]) var ShpfyInvoiceHeader: Record "Shpfy Invoice Header"; begin ShpfyInvoiceHeader.Init(); - ShpfyInvoiceHeader.Validate("Shopify Order Id", ShpfyDraftOrdersAPI.ParseShopifyResponse(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.id')); - ShpfyInvoiceHeader.Validate("Shopify Order No.", Format(ShpfyJsonHelper.GetValueAsText(JsonTokenResponse, 'data.draftOrderComplete.draftOrder.order.name'))); + ShpfyInvoiceHeader.Validate("Shopify Order Id", ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId')); ShpfyInvoiceHeader.Insert(true); end; @@ -367,4 +393,9 @@ codeunit 30105 "Shpfy Posted Invoice Export" DocLinkToBCDoc."Document No." := SalesInvoiceHeader."No."; DocLinkToBCDoc.Insert(); 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/PageExt/ShpfySalesInvoiceUpdate.PageExt.al b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al index ed50c9f8e8..3abe18ceea 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al +++ b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al @@ -19,7 +19,7 @@ pageextension 30125 "Shpfy Sales Invoice Update" extends "Posted Sales Inv. - Up 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 Fulfillment ID. Helps track the status of shipments within Shopify, with 0 indicating readiness to synchronize, -1 indicating an error, and -2 indicating that the shipment is skipped.'; + 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 diff --git a/Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al b/Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al deleted file mode 100644 index e43b6f6494..0000000000 --- a/Apps/W1/Shopify/app/src/Invoicing/Pages/ShpfyInvoices.Page.al +++ /dev/null @@ -1,32 +0,0 @@ -namespace Microsoft.Integration.Shopify; - -/// -/// Page Shpfy Invoices (ID 30138). -/// -page 30138 "Shpfy Invoices" //TODO: Maybe this page is not needed? -{ - PageType = List; - Caption = 'Shopify Invoices'; - ApplicationArea = All; - UsageCategory = Lists; - SourceTable = "Shpfy Invoice Header"; - Editable = false; - - layout - { - area(Content) - { - repeater(GroupName) - { - field("Shopify Order Id"; Rec."Shopify Order Id") - { - ToolTip = 'Specifies the value of the Shopify Order Id field.'; - } - field("Shopify Order No."; Rec."Shopify Order No.") - { - ToolTip = 'Specifies the value of the Shopify Order No. field.'; - } - } - } - } -} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al index b1633b4eb1..c167804922 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al @@ -18,7 +18,13 @@ report 30117 "Shpfy Export Invoice to Shpfy" { RequestFilterFields = "No.", "Posting Date"; trigger OnPreDataItem() + var + ShopCodeNotSetErr: Label 'Shopify Shop Code is empty.'; begin + if ShopCode = '' then + Error(ShopCodeNotSetErr); + + ShpfyPostedInvoiceExport.SetShop(ShopCode); SetRange("Shpfy Order Id", 0); if GuiAllowed then begin @@ -35,14 +41,17 @@ report 30117 "Shpfy Export Invoice to Shpfy" ProcessDialog.Update(); end; - ShpfyPostedInvoiceExport.SetShop(ShopCode); ShpfyPostedInvoiceExport.Run(SalesInvoiceHeader); end; trigger OnPostDataItem() + var + ShpfyBackgroundSyncs: Codeunit "Shpfy Background Syncs"; begin if GuiAllowed then ProcessDialog.Close(); + + ShpfyBackgroundSyncs.InventorySync(ShopCode); end; } } @@ -79,9 +88,9 @@ report 30117 "Shpfy Export Invoice to Shpfy" ProcessMsg: Label 'Synchronizing Posted Sales Invoice #1####################', Comment = '#1 = Posted Sales Invoice No.'; /// - /// Set Shop. + /// Sets a global shopify shop code to be used. /// - /// Parameter of type Code[20]. + /// Shopify shop code to be set. internal procedure SetShop(NewShopCode: Code[20]) begin ShopCode := NewShopCode; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al index 5f1234122b..3b67bddc4e 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al @@ -7,7 +7,6 @@ table 30156 "Shpfy Invoice Header" { Caption = 'Shopify Invoice Header'; DataClassification = CustomerContent; - LookupPageId = "Shpfy Invoices"; fields { @@ -15,10 +14,6 @@ table 30156 "Shpfy Invoice Header" { Caption = 'Shopify Order Id'; } - field(20; "Shopify Order No."; Code[50]) - { - Caption = 'Shopify Order Name'; - } } keys diff --git a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al index 8904a6ff4b..28828ddeb1 100644 --- a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al @@ -5,17 +5,22 @@ namespace Microsoft.Integration.Shopify; /// codeunit 30168 "Shpfy Payment Terms API" { + var + JsonHelper: Codeunit "Shpfy Json Helper"; + + /// + /// 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(ShopCode: Code[20]) var Shop: Record "Shpfy Shop"; PaymentTerms: Record "Shpfy Payment Terms"; PaymentTermRecordRef: RecordRef; CommunicationMgt: Codeunit "Shpfy Communication Mgt."; - ShpfyDraftOrdersAPI: Codeunit "Shpfy Draft Orders API"; - JsonHelper: Codeunit "Shpfy Json Helper"; GraphQLType: Enum "Shpfy GraphQL Type"; - JArray: JsonArray; - JToken: JsonToken; + JTemplates: JsonArray; + JTemplate: JsonToken; JResponse: JsonToken; IsNew: Boolean; Id: BigInteger; @@ -27,9 +32,9 @@ codeunit 30168 "Shpfy Payment Terms API" GraphQLType := GraphQLType::GetPaymentTerms; JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType); - JsonHelper.GetJsonArray(JResponse, JArray, 'data.paymentTermsTemplates'); - foreach JToken in JArray do begin - Id := ShpfyDraftOrdersAPI.ParseShopifyResponse(JToken, 'id'); + JsonHelper.GetJsonArray(JResponse, JTemplates, 'data.paymentTermsTemplates'); + foreach JTemplate in JTemplates do begin + Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JTemplate, 'id')); IsNew := not PaymentTerms.Get(ShopCode, Id); if IsNew then begin @@ -39,10 +44,10 @@ codeunit 30168 "Shpfy Payment Terms API" end; PaymentTermRecordRef.GetTable(PaymentTerms); - JsonHelper.GetValueIntoField(JToken, 'name', PaymentTermRecordRef, PaymentTerms.FieldNo(Name)); - JsonHelper.GetValueIntoField(JToken, 'paymentTermsType', PaymentTermRecordRef, PaymentTerms.FieldNo(Type)); - JsonHelper.GetValueIntoField(JToken, 'dueInDays', PaymentTermRecordRef, PaymentTerms.FieldNo("Due In Days")); - JsonHelper.GetValueIntoField(JToken, 'description', PaymentTermRecordRef, PaymentTerms.FieldNo(Description)); + JsonHelper.GetValueIntoField(JTemplate, 'name', PaymentTermRecordRef, PaymentTerms.FieldNo(Name)); + JsonHelper.GetValueIntoField(JTemplate, 'paymentTermsType', PaymentTermRecordRef, PaymentTerms.FieldNo(Type)); + JsonHelper.GetValueIntoField(JTemplate, 'dueInDays', PaymentTermRecordRef, PaymentTerms.FieldNo("Due In Days")); + JsonHelper.GetValueIntoField(JTemplate, 'description', PaymentTermRecordRef, PaymentTerms.FieldNo(Description)); PaymentTermRecordRef.SetTable(PaymentTerms); if IsNew then diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al index b8493d9608..fa910ecaa5 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 Log Entry" = IMD, tabledata "Shpfy Metafield" = IMD, tabledata "Shpfy Refund Header" = IMD, @@ -51,6 +52,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, @@ -64,7 +66,5 @@ permissionset 30102 "Shpfy - Edit" tabledata "Shpfy Tag" = IMD, tabledata "Shpfy Tax Area" = IMD, tabledata "Shpfy Transaction Gateway" = IMD, - tabledata "Shpfy Variant" = IMD, - tabledata "Shpfy Payment Terms" = IMD, - tabledata "Shpfy Invoice Header" = IMD; + tabledata "Shpfy Variant" = IMD; } \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al index 2fc5a8bc9b..3a19d323bb 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 Log Entry" = R, tabledata "Shpfy Metafield" = R, tabledata "Shpfy Order Attribute" = R, @@ -47,6 +48,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, @@ -64,8 +66,6 @@ permissionset 30100 "Shpfy - Read" tabledata "Shpfy Tag" = R, tabledata "Shpfy Tax Area" = R, tabledata "Shpfy Transaction Gateway" = R, - tabledata "Shpfy Variant" = R, - tabledata "Shpfy Payment Terms" = R, - tabledata "Shpfy Invoice Header" = R; + tabledata "Shpfy Variant" = R; } #pragma warning restore AS0090, AS0049 \ No newline at end of file From 34871ca14088a67bcd0575f9116268256eefde0d Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Wed, 26 Jun 2024 12:03:02 +0300 Subject: [PATCH 3/8] Fixed PR comments --- .../ShpfyGQLDraftOrderComplete.Codeunit.al | 4 +- .../ShpfyGQLGetFulfillments.Codeunit.al | 26 +++++++ .../GraphQL/Enums/ShpfyGraphQLType.Enum.al | 5 ++ .../ShpfyAuthenticationMgt.Codeunit.al | 2 +- .../Codeunits/ShpfyDraftOrdersAPI.Codeunit.al | 71 +++++++++++++----- .../Codeunits/ShpfyFulfillmentAPI.Codeunit.al | 57 +++++++------- .../ShpfyPostedInvoiceExport.Codeunit.al | 52 ++++++++++--- .../ShpfyExportInvoiceToShpfy.Report.al | 2 +- .../Codeunits/ShpfyPaymentTermAPI.Codeunit.al | 75 +++++++++++-------- .../Pages/ShpfyPaymentTermsMapping.Page.al | 3 +- 10 files changed, 202 insertions(+), 95 deletions(-) create mode 100644 Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLGetFulfillments.Codeunit.al diff --git a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al index d3f55cff0c..34b199b280 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLDraftOrderComplete.Codeunit.al @@ -13,7 +13,7 @@ codeunit 30312 "Shpfy GQL DraftOrderComplete" implements "Shpfy IGraphQL" /// Return value of type Text. internal procedure GetGraphQL(): Text begin - exit('{"query": "mutation {draftOrderComplete(id: \"gid://shopify/DraftOrder/{{DraftOrderId}}\") { draftOrder { order { legacyResourceId, name, fulfillmentOrders(first: {{NumberOfOrders}}) {nodes{ id, status}} } id, status} userErrors { field, message }}}"}'); + exit('{"query": "mutation {draftOrderComplete(id: \"gid://shopify/DraftOrder/{{DraftOrderId}}\") { draftOrder { order { legacyResourceId, name }} userErrors { field, message }}}"}'); end; /// @@ -22,6 +22,6 @@ codeunit 30312 "Shpfy GQL DraftOrderComplete" implements "Shpfy IGraphQL" /// Return value of type Integer. internal procedure GetExpectedCost(): Integer begin - exit(15); + exit(11); 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..fa1e51f956 --- /dev/null +++ b/Apps/W1/Shopify/app/src/GraphQL/Codeunits/ShpfyGQLGetFulfillments.Codeunit.al @@ -0,0 +1,26 @@ +/// +/// Codeunit Shpfy GQL Get Fulfillments (ID 30317) implements Interface Shpfy IGraphQL. +/// +codeunit 30317 "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/Enums/ShpfyGraphQLType.Enum.al b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al index 9860579f34..26088240e2 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al @@ -415,4 +415,9 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL" Caption = 'Get Payment Terms'; Implementation = "Shpfy IGraphQL" = "Shpfy GQL Payment Terms"; } + value(83; GetFulfillments) + { + Caption = 'Get Fulfillments'; + Implementation = "Shpfy IGraphQL" = "Shpfy GQL Get Fulfillments"; + } } 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 f914ac6af8..7d9ec92647 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,write_payment_terms,write_draft_orders', 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,write_payment_terms,write_draft_orders', 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 index aef57d379b..2aa13d654e 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al @@ -3,6 +3,8 @@ 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). @@ -29,8 +31,8 @@ codeunit 30159 "Shpfy Draft Orders API" var ShpfyOrderTaxLines: Dictionary of [Text, Decimal] ): BigInteger var - GraphQuery: TextBuilder; DraftOrderId: BigInteger; + GraphQuery: TextBuilder; begin GraphQuery := CreateDraftOrderGQLRequest(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); DraftOrderId := SendDraftOrderGraphQLRequest(GraphQuery); @@ -41,9 +43,8 @@ codeunit 30159 "Shpfy Draft Orders API" /// Completes a draft order in shopify by converting it to an order. /// /// Draft order id that needs to be completed. - /// Maximum amount of possible fulfillment orders. /// Json response of a created order in shopify. - internal procedure CompleteDraftOrder(DraftOrderId: BigInteger; NumberOfLines: Integer): JsonToken + internal procedure CompleteDraftOrder(DraftOrderId: BigInteger): JsonToken var GraphQLType: Enum "Shpfy GraphQL Type"; Parameters: Dictionary of [Text, Text]; @@ -51,13 +52,12 @@ codeunit 30159 "Shpfy Draft Orders API" begin GraphQLType := "Shpfy GraphQL Type"::DraftOrderComplete; Parameters.Add('DraftOrderId', Format(DraftOrderId)); - Parameters.Add('NumberOfOrders', Format(NumberOfLines)); JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); exit(JResponse); end; /// - /// Sets a global shopify shop variable to be used. + /// 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]) @@ -110,9 +110,8 @@ codeunit 30159 "Shpfy Draft Orders API" local procedure SendDraftOrderGraphQLRequest(GraphQuery: TextBuilder): BigInteger var - ShpfyPaymentTermAPI: Codeunit "Shpfy Payment Terms API"; - JResponse: JsonToken; DraftOrderId: BigInteger; + JResponse: JsonToken; begin JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQuery.ToText()); DraftOrderId := ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderCreate.draftOrder.legacyResourceId'); @@ -120,8 +119,6 @@ codeunit 30159 "Shpfy Draft Orders API" end; local procedure AddShippingAddressToGraphQuery(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) - var - myInt: Integer; begin GraphQuery.Append(', shippingAddress: {'); if TempShpfyOrderHeader."Ship-to Address" <> '' then begin @@ -223,6 +220,8 @@ codeunit 30159 "Shpfy Draft Orders API" GraphQuery.Append(', variantId: \"gid://shopify/ProductVariant/'); GraphQuery.Append(Format(TempShpfyOrderLine."Shopify Variant Id")); GraphQuery.Append('\"'); + + AddItemAttributes(GraphQuery, TempShpfyOrderLine."Item No."); end; GraphQuery.Append(', quantity: '); @@ -254,8 +253,6 @@ codeunit 30159 "Shpfy Draft Orders API" end; local procedure AddDiscountAmountToGraphQuery(var GraphQuery: TextBuilder; DiscountAmount: Decimal; DiscountTitle: Text) - var - myInt: Integer; begin GraphQuery.Append(', appliedDiscount: {'); GraphQuery.Append('description: \"'); @@ -327,20 +324,20 @@ codeunit 30159 "Shpfy Draft Orders API" end; local procedure ShopifyPaymentTermsExists( - var ShpfyPaymentTerm: Record "Shpfy Payment Terms"; + 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."); - ShpfyPaymentTerm.SetRange("Payment Terms Code", SalesInvoiceHeader."Payment Terms Code"); - ShpfyPaymentTerm.SetRange("Shop Code", ShpfyShop.Code); + ShpfyPaymentTerms.SetRange("Payment Terms Code", SalesInvoiceHeader."Payment Terms Code"); + ShpfyPaymentTerms.SetRange("Shop Code", ShpfyShop.Code); - if not ShpfyPaymentTerm.FindFirst() then begin - ShpfyPaymentTerm.SetRange("Payment Terms Code"); - ShpfyPaymentTerm.SetRange("Is Primary", true); + if not ShpfyPaymentTerms.FindFirst() then begin + ShpfyPaymentTerms.SetRange("Payment Terms Code"); + ShpfyPaymentTerms.SetRange("Is Primary", true); - if not ShpfyPaymentTerm.FindFirst() then + if not ShpfyPaymentTerms.FindFirst() then exit(false); end; @@ -354,4 +351,42 @@ codeunit 30159 "Shpfy Draft Orders API" Currency.Get(CurrencyCode); exit(Currency."ISO Code"); end; + + local procedure IsItem(ItemNo: Code[20]): Boolean + var + Item: Record Item; + begin + exit(Item.Get(ItemNo)); + 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 index 721acf1a1b..703ce5cb1b 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al @@ -6,48 +6,47 @@ namespace Microsoft.Integration.Shopify; codeunit 30315 "Shpfy Fulfillment API" { var - ShpfyJsonHelper: Codeunit "Shpfy Json Helper"; ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; /// - /// Fulfills shopify orders for each fulfillment id parsed from a completed draft order. + /// Creates a fulfillment for a provided fulfillment order id. /// - /// Json response from a completed draft order - /// Shopify shop code to be used. - internal procedure FulfillShopifyOrder(JResponse: JsonToken; ShopCode: Code[20]) + /// Fulfillment order id. + internal procedure CreateFulfillment(FulfillmentOrderId: Text) var - FulfillmentOrderList: List of [Text]; - FulfillmentOrderId: Text; - ResponseJsonToken: JsonToken; + JResponse: JsonToken; GraphQLType: Enum "Shpfy GraphQL Type"; Parameters: Dictionary of [Text, Text]; begin - ShpfyCommunicationMgt.SetShop(ShopCode); - FulfillmentOrderList := ParseFulfillmentOrders(JResponse); GraphQLType := "Shpfy GraphQL Type"::FulfillOrder; - - foreach FulfillmentOrderId in FulfillmentOrderList do begin - Parameters.Add('FulfillmentOrderId', FulfillmentOrderId); - ResponseJsonToken := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); - Clear(Parameters); - end; + Parameters.Add('FulfillmentOrderId', FulfillmentOrderId); + JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); end; - local procedure ParseFulfillmentOrders(ResponseJsonToken: JsonToken) FulfillmentOrderList: List of [Text] + /// + /// Gets fulfillment orders for a provided shopify order id. + /// + /// Shopify order id to get fulfillments from. + /// Number of fulfillment orders to get. + /// Fulfillment orders. + internal procedure GetFulfillmentOrders(OrderId: Text; NumberOfLines: Integer) JFulfillments: JsonToken var - Counter: Integer; - FulfillmentOrderArray: JsonArray; - FulfillmentObject: JsonObject; - FulfillmentOrderToken: JsonToken; - JToken: JsonToken; + GraphQLType: Enum "Shpfy GraphQL Type"; + Parameters: Dictionary of [Text, Text]; begin - FulfillmentObject := ResponseJsonToken.AsObject(); - FulfillmentObject.SelectToken('data.draftOrderComplete.draftOrder.order.fulfillmentOrders.nodes', JToken); - FulfillmentOrderArray := ShpfyJsonHelper.GetJsonArray(JToken, ''); + GraphQLType := "Shpfy GraphQL Type"::GetFulfillments; + Parameters.Add('OrderId', OrderId); + Parameters.Add('NumberOfOrders', Format(NumberOfLines)); + JFulfillments := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); + exit(JFulfillments); + end; - for Counter := 0 to FulfillmentOrderArray.Count() - 1 do begin - FulfillmentOrderArray.Get(Counter, FulfillmentOrderToken); - FulfillmentOrderList.Add(Format(ShpfyCommunicationMgt.GetIdOfGId(ShpfyJsonHelper.GetValueAsText(FulfillmentOrderToken, 'id')))); - 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; } \ 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 index 0b7def96b4..08ce2023bb 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -16,6 +16,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" var ShpfyShop: Record "Shpfy Shop"; ShpfyDraftOrdersAPI: Codeunit "Shpfy Draft Orders API"; + ShpfyFulfillmentAPI: Codeunit "Shpfy Fulfillment API"; ShpfyJsonHelper: Codeunit "Shpfy Json Helper"; trigger OnRun() @@ -24,12 +25,14 @@ codeunit 30316 "Shpfy Posted Invoice Export" end; /// - /// Sets a global shopify shop variable to be used. + /// 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; /// @@ -45,9 +48,10 @@ codeunit 30316 "Shpfy Posted Invoice Export" TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; TempShpfyOrderLine: Record "Shpfy Order Line" temporary; ShpfyFulfillmentAPI: Codeunit "Shpfy Fulfillment API"; + DraftOrderId: BigInteger; ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; + JFulfillments: JsonToken; JResponse: JsonToken; - DraftOrderId: BigInteger; begin if not IsInvoiceExportable(SalesInvoiceHeader) then begin SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -2, ''); @@ -56,10 +60,13 @@ codeunit 30316 "Shpfy Posted Invoice Export" MapPostedSalesInvoiceData(SalesInvoiceHeader, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); - ShpfyDraftOrdersAPI.SetShop(ShpfyShop.Code); DraftOrderId := ShpfyDraftOrdersAPI.CreateDraftOrder(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); - JResponse := ShpfyDraftOrdersAPI.CompleteDraftOrder(DraftOrderId, GetNumberOfLines(TempShpfyOrderLine, ShpfyOrderTaxLines)); - ShpfyFulfillmentAPI.FulfillShopifyOrder(JResponse, ShpfyShop.Code); + JResponse := ShpfyDraftOrdersAPI.CompleteDraftOrder(DraftOrderId); + JFulfillments := ShpfyFulfillmentAPI.GetFulfillmentOrders( + Format(ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId')), + GetNumberOfLines(TempShpfyOrderLine, ShpfyOrderTaxLines) + ); + CreateFulfillmentsForShopifyOrder(JFulfillments); if IsSuccess(JResponse) then begin CreateShpfyInvoiceHeader(JResponse, SalesInvoiceHeader."No."); @@ -73,6 +80,30 @@ codeunit 30316 "Shpfy Posted Invoice Export" SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -1, ''); end; + local procedure CreateFulfillmentsForShopifyOrder(JFulfillments: JsonToken) + var + FulfillmentOrderList: List of [Text]; + FulfillmentOrderId: Text; + begin + FulfillmentOrderList := ParseFulfillmentOrders(JFulfillments); + + foreach FulfillmentOrderId in FulfillmentOrderList do begin + ShpfyFulfillmentAPI.CreateFulfillment(FulfillmentOrderId); + end; + end; + + local procedure ParseFulfillmentOrders(JFulfillments: JsonToken) FulfillmentOrderList: List of [Text] + var + ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; + JArray: JsonArray; + JToken: JsonToken; + begin + JArray := ShpfyJsonHelper.GetJsonArray(JFulfillments, 'data.order.fulfillmentOrders.nodes'); + + foreach JToken in JArray do + FulfillmentOrderList.Add(Format(ShpfyCommunicationMgt.GetIdOfGId(ShpfyJsonHelper.GetValueAsText(JToken, 'id')))); + end; + local procedure IsInvoiceExportable(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean var ShpfyCompany: Record "Shpfy Company"; @@ -192,11 +223,9 @@ codeunit 30316 "Shpfy Posted Invoice Export" end; local procedure MapSalesInvoiceHeader( - SalesInvoiceHeader: Record "Sales Invoice Header"; - var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary -) - var - DocumentTotals: Codeunit "Document Totals"; + SalesInvoiceHeader: Record "Sales Invoice Header"; + var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary + ) begin TempShpfyOrderHeader.Init(); TempShpfyOrderHeader."Sales Invoice No." := SalesInvoiceHeader."No."; @@ -299,6 +328,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" 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"; @@ -354,9 +384,9 @@ codeunit 30316 "Shpfy Posted Invoice Export" local procedure MapTaxLine(var SalesInvoiceLine: Record "Sales Invoice Line" temporary; var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]) var - TaxTitle: Text; 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"; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al index c167804922..ca12ef5bba 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al @@ -72,7 +72,7 @@ report 30117 "Shpfy Export Invoice to Shpfy" Lookup = true; LookupPageId = "Shpfy Shops"; TableRelation = "Shpfy Shop"; - ToolTip = 'Specifies the Shopify Shop.'; + ToolTip = 'Specifies the Shopify Shop to which the invoice will be exported.'; ShowMandatory = true; } } diff --git a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al index 28828ddeb1..be2c23e973 100644 --- a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al @@ -6,54 +6,65 @@ namespace Microsoft.Integration.Shopify; codeunit 30168 "Shpfy Payment Terms API" { 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(ShopCode: Code[20]) + internal procedure PullPaymentTermsCodes() var - Shop: Record "Shpfy Shop"; - PaymentTerms: Record "Shpfy Payment Terms"; - PaymentTermRecordRef: RecordRef; - CommunicationMgt: Codeunit "Shpfy Communication Mgt."; GraphQLType: Enum "Shpfy GraphQL Type"; JTemplates: JsonArray; JTemplate: JsonToken; JResponse: JsonToken; - IsNew: Boolean; - Id: BigInteger; begin - Shop.Get(ShopCode); - - CommunicationMgt.SetShop(Shop.Code); - GraphQLType := GraphQLType::GetPaymentTerms; JResponse := CommunicationMgt.ExecuteGraphQL(GraphQLType); JsonHelper.GetJsonArray(JResponse, JTemplates, 'data.paymentTermsTemplates'); - foreach JTemplate in JTemplates do begin - Id := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JTemplate, 'id')); - IsNew := not PaymentTerms.Get(ShopCode, Id); - - if IsNew then begin - Clear(PaymentTerms); - PaymentTerms."Id" := Id; - PaymentTerms."Shop Code" := ShopCode; - end; - - PaymentTermRecordRef.GetTable(PaymentTerms); - JsonHelper.GetValueIntoField(JTemplate, 'name', PaymentTermRecordRef, PaymentTerms.FieldNo(Name)); - JsonHelper.GetValueIntoField(JTemplate, 'paymentTermsType', PaymentTermRecordRef, PaymentTerms.FieldNo(Type)); - JsonHelper.GetValueIntoField(JTemplate, 'dueInDays', PaymentTermRecordRef, PaymentTerms.FieldNo("Due In Days")); - JsonHelper.GetValueIntoField(JTemplate, 'description', PaymentTermRecordRef, PaymentTerms.FieldNo(Description)); - PaymentTermRecordRef.SetTable(PaymentTerms); - - if IsNew then - PaymentTerms.Insert() - else - PaymentTerms.Modify(); + 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 IsNew then + ShpfyPaymentTerms.Insert() + else + ShpfyPaymentTerms.Modify(); 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 index 98b848df51..5fd01430ea 100644 --- a/Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al +++ b/Apps/W1/Shopify/app/src/Payments/Pages/ShpfyPaymentTermsMapping.Page.al @@ -62,7 +62,8 @@ page 30162 "Shpfy Payment Terms Mapping" var ShpfyPaymentTermAPI: Codeunit "Shpfy Payment Terms API"; begin - ShpfyPaymentTermAPI.PullPaymentTermsCodes(CopyStr(Rec.GetFilter("Shop Code"), 1, 20)); + ShpfyPaymentTermAPI.SetShop(CopyStr(Rec.GetFilter("Shop Code"), 1, 20)); + ShpfyPaymentTermAPI.PullPaymentTermsCodes(); end; } } From 187b077cff24d445c4e39923b74587c3de054f56 Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Wed, 26 Jun 2024 14:54:02 +0300 Subject: [PATCH 4/8] Fixed PR comments --- .../app/src/Base/Pages/ShpfyShopCard.Page.al | 2 +- .../GraphQL/Enums/ShpfyGraphQLType.Enum.al | 2 +- .../Codeunits/ShpfyDraftOrdersAPI.Codeunit.al | 7 -- .../Codeunits/ShpfyFulfillmentAPI.Codeunit.al | 31 +++++++-- .../ShpfyPostedInvoiceExport.Codeunit.al | 64 +++++++------------ .../ShpfyUpdateSalesInvoice.Codeunit.al | 2 + .../ShpfySalesInvoiceUpdate.PageExt.al | 2 + ....al => ShpfySyncInvoicesToShpfy.Report.al} | 8 +-- .../Tables/ShpfyInvoiceHeader.Table.al | 1 + ...it.al => ShpfyPaymentTermsAPI.Codeunit.al} | 6 +- ...rm.Table.al => ShpfyPaymentTerms.Table.al} | 1 + 11 files changed, 62 insertions(+), 64 deletions(-) rename Apps/W1/Shopify/app/src/Invoicing/Reports/{ShpfyExportInvoiceToShpfy.Report.al => ShpfySyncInvoicesToShpfy.Report.al} (94%) rename Apps/W1/Shopify/app/src/Payments/Codeunits/{ShpfyPaymentTermAPI.Codeunit.al => ShpfyPaymentTermsAPI.Codeunit.al} (95%) rename Apps/W1/Shopify/app/src/Payments/Tables/{ShpfyPaymentTerm.Table.al => ShpfyPaymentTerms.Table.al} (98%) 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 3440548da2..e790e59939 100644 --- a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al +++ b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al @@ -1011,7 +1011,7 @@ page 30101 "Shpfy Shop Card" trigger OnAction(); var - ExportInvoicetoShpfy: Report "Shpfy Export Invoice to Shpfy"; + ExportInvoicetoShpfy: Report "Shpfy Sync Invoices to Shpfy"; begin ExportInvoicetoShpfy.SetShop(Rec.Code); ExportInvoicetoShpfy.Run(); 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 26088240e2..38bcabe7e3 100644 --- a/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al +++ b/Apps/W1/Shopify/app/src/GraphQL/Enums/ShpfyGraphQLType.Enum.al @@ -415,7 +415,7 @@ enum 30111 "Shpfy GraphQL Type" implements "Shpfy IGraphQL" Caption = 'Get Payment Terms'; Implementation = "Shpfy IGraphQL" = "Shpfy GQL Payment Terms"; } - value(83; GetFulfillments) + value(83; GetFulfillmentOrderIds) { Caption = 'Get Fulfillments'; Implementation = "Shpfy IGraphQL" = "Shpfy GQL Get Fulfillments"; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al index 2aa13d654e..4fbf4aea99 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al @@ -352,13 +352,6 @@ codeunit 30159 "Shpfy Draft Orders API" exit(Currency."ISO Code"); end; - local procedure IsItem(ItemNo: Code[20]): Boolean - var - Item: Record Item; - begin - exit(Item.Get(ItemNo)); - end; - local procedure AddItemAttributes(var GraphQuery: TextBuilder; ItemNo: Code[20]) var Item: Record Item; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al index 703ce5cb1b..99cf8f968d 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyFulfillmentAPI.Codeunit.al @@ -5,6 +5,8 @@ namespace Microsoft.Integration.Shopify; /// codeunit 30315 "Shpfy Fulfillment API" { + Access = Internal; + var ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; @@ -12,33 +14,36 @@ codeunit 30315 "Shpfy Fulfillment API" /// Creates a fulfillment for a provided fulfillment order id. /// /// Fulfillment order id. - internal procedure CreateFulfillment(FulfillmentOrderId: Text) + 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', FulfillmentOrderId); + Parameters.Add('FulfillmentOrderId', Format(FulfillmentOrderId)); JResponse := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); end; /// - /// Gets fulfillment orders for a provided shopify order id. + /// Gets fulfillment order ids for a provided shopify order id. /// /// Shopify order id to get fulfillments from. /// Number of fulfillment orders to get. - /// Fulfillment orders. - internal procedure GetFulfillmentOrders(OrderId: Text; NumberOfLines: Integer) JFulfillments: JsonToken + /// 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"::GetFulfillments; + GraphQLType := "Shpfy GraphQL Type"::GetFulfillmentOrderIds; Parameters.Add('OrderId', OrderId); Parameters.Add('NumberOfOrders', Format(NumberOfLines)); JFulfillments := ShpfyCommunicationMgt.ExecuteGraphQL(GraphQLType, Parameters); - exit(JFulfillments); + FulfillmentOrderList := ParseFulfillmentOrders(JFulfillments); + + exit(FulfillmentOrderList); end; /// @@ -49,4 +54,16 @@ codeunit 30315 "Shpfy Fulfillment API" 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 index 08ce2023bb..915becf969 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -2,7 +2,6 @@ namespace Microsoft.Integration.Shopify; using Microsoft.Sales.History; using Microsoft.Finance.GeneralLedger.Setup; -using Microsoft.Utilities; using Microsoft.Sales.Receivables; /// @@ -47,11 +46,12 @@ codeunit 30316 "Shpfy Posted Invoice Export" var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; TempShpfyOrderLine: Record "Shpfy Order Line" temporary; - ShpfyFulfillmentAPI: Codeunit "Shpfy Fulfillment API"; DraftOrderId: BigInteger; ShpfyOrderTaxLines: Dictionary of [Text, Decimal]; - JFulfillments: JsonToken; + FulfillmentOrderIds: List of [BigInteger]; JResponse: JsonToken; + OrderId: BigInteger; + OrderNo: Text; begin if not IsInvoiceExportable(SalesInvoiceHeader) then begin SetSalesInvoiceShopifyOrderInformation(SalesInvoiceHeader, -2, ''); @@ -62,46 +62,26 @@ codeunit 30316 "Shpfy Posted Invoice Export" DraftOrderId := ShpfyDraftOrdersAPI.CreateDraftOrder(TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); JResponse := ShpfyDraftOrdersAPI.CompleteDraftOrder(DraftOrderId); - JFulfillments := ShpfyFulfillmentAPI.GetFulfillmentOrders( - Format(ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId')), - GetNumberOfLines(TempShpfyOrderLine, ShpfyOrderTaxLines) - ); - CreateFulfillmentsForShopifyOrder(JFulfillments); if IsSuccess(JResponse) then begin - CreateShpfyInvoiceHeader(JResponse, SalesInvoiceHeader."No."); - SetSalesInvoiceShopifyOrderInformation( - SalesInvoiceHeader, - ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId'), - Format(ShpfyJsonHelper.GetValueAsText(JResponse, 'data.draftOrderComplete.draftOrder.order.name')) - ); + 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(JFulfillments: JsonToken) + local procedure CreateFulfillmentsForShopifyOrder(FulfillmentOrderIds: List of [BigInteger]) var - FulfillmentOrderList: List of [Text]; - FulfillmentOrderId: Text; + FulfillmentOrderId: BigInteger; begin - FulfillmentOrderList := ParseFulfillmentOrders(JFulfillments); - - foreach FulfillmentOrderId in FulfillmentOrderList do begin + foreach FulfillmentOrderId in FulfillmentOrderIds do ShpfyFulfillmentAPI.CreateFulfillment(FulfillmentOrderId); - end; - end; - - local procedure ParseFulfillmentOrders(JFulfillments: JsonToken) FulfillmentOrderList: List of [Text] - var - ShpfyCommunicationMgt: Codeunit "Shpfy Communication Mgt."; - JArray: JsonArray; - JToken: JsonToken; - begin - JArray := ShpfyJsonHelper.GetJsonArray(JFulfillments, 'data.order.fulfillmentOrders.nodes'); - - foreach JToken in JArray do - FulfillmentOrderList.Add(Format(ShpfyCommunicationMgt.GetIdOfGId(ShpfyJsonHelper.GetValueAsText(JToken, 'id')))); end; local procedure IsInvoiceExportable(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean @@ -246,7 +226,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" SalesInvoiceHeader.CalcFields("Invoice Discount Amount"); TempShpfyOrderHeader."Discount Amount" := SalesInvoiceHeader."Invoice Discount Amount"; TempShpfyOrderHeader."Shop Code" := ShpfyShop.Code; - TempShpfyOrderHeader.Insert(); + TempShpfyOrderHeader.Insert(false); end; local procedure MapCurrencyCode(SalesInvoiceHeader: Record "Sales Invoice Header"): Code[10] @@ -267,7 +247,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" var ShpfyCustomer: Record "Shpfy Customer"; begin - TempShpfyOrderHeader."Bill-to Name" := SalesInvoiceHeader."Bill-to Name"; + TempShpfyOrderHeader."Bill-to Name" := Format(SalesInvoiceHeader."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"; @@ -279,7 +259,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" ShpfyCustomer.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No."); if ShpfyCustomer.FindFirst() then begin - TempShpfyOrderHeader.Email := ShpfyCustomer.Email; + TempShpfyOrderHeader.Email := Format(ShpfyCustomer.Email); TempShpfyOrderHeader."Phone No." := ShpfyCustomer."Phone No."; end; end; @@ -289,7 +269,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" SalesInvoiceHeader: Record "Sales Invoice Header" ) begin - TempShpfyOrderHeader."Ship-to Name" := SalesInvoiceHeader."Ship-to Name"; + TempShpfyOrderHeader."Ship-to Name" := Format(SalesInvoiceHeader."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"; @@ -333,7 +313,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" TempShpfyOrderLine.Taxable := false; TempShpfyOrderLine."Unit Price" := SalesInvoiceLine."Unit Price"; TempShpfyOrderHeader."Discount Amount" += SalesInvoiceLine."Line Discount Amount"; - TempShpfyOrderHeader.Modify(); + TempShpfyOrderHeader.Modify(false); if ShpfyShop."UoM as Variant" then MapUOMProductVariants(SalesInvoiceLine, TempShpfyOrderLine) @@ -348,7 +328,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" MapTaxLine(SalesInvoiceLine, ShpfyOrderTaxLines); - TempShpfyOrderLine.Insert(); + TempShpfyOrderLine.Insert(false); end; local procedure MapUOMProductVariants(SalesInvoiceLine: Record "Sales Invoice Line"; var TempShpfyOrderLine: Record "Shpfy Order Line" temporary) @@ -402,12 +382,12 @@ codeunit 30316 "Shpfy Posted Invoice Export" exit(ShpfyJsonHelper.GetJsonArray(JsonTokenResponse, 'data.draftOrderComplete.userErrors').Count() = 0); end; - local procedure CreateShpfyInvoiceHeader(JResponse: JsonToken; InvoiceNo: Code[20]) + local procedure CreateShpfyInvoiceHeader(OrderId: BigInteger) var ShpfyInvoiceHeader: Record "Shpfy Invoice Header"; begin ShpfyInvoiceHeader.Init(); - ShpfyInvoiceHeader.Validate("Shopify Order Id", ShpfyJsonHelper.GetValueAsBigInteger(JResponse, 'data.draftOrderComplete.draftOrder.order.legacyResourceId')); + ShpfyInvoiceHeader.Validate("Shopify Order Id", OrderId); ShpfyInvoiceHeader.Insert(true); end; @@ -421,7 +401,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" DocLinkToBCDoc."Shopify Document Id" := SalesInvoiceHeader."Shpfy Order Id"; DocLinkToBCDoc."Document Type" := BCDocumentTypeConvert.Convert(SalesInvoiceHeader); DocLinkToBCDoc."Document No." := SalesInvoiceHeader."No."; - DocLinkToBCDoc.Insert(); + DocLinkToBCDoc.Insert(true); end; local procedure GetNumberOfLines(var TempShpfyOrderLine: Record "Shpfy Order Line" temporary; var ShpfyOrderTaxLines: Dictionary of [Text, Decimal]): Integer diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al index 450780f55e..fbd4aa6ed1 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyUpdateSalesInvoice.Codeunit.al @@ -4,6 +4,8 @@ using Microsoft.Sales.History; codeunit 30314 "Shpfy Update Sales Invoice" { + Access = Internal; + [EventSubscriber(ObjectType::Page, Page::"Posted Sales Inv. - Update", 'OnAfterRecordChanged', '', false, false)] local procedure OnAfterRecordChanged(var SalesInvoiceHeader: Record "Sales Invoice Header"; xSalesInvoiceHeader: Record "Sales Invoice Header"; var IsChanged: Boolean) begin diff --git a/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al index 3abe18ceea..d9b090faa4 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al +++ b/Apps/W1/Shopify/app/src/Invoicing/PageExt/ShpfySalesInvoiceUpdate.PageExt.al @@ -13,7 +13,9 @@ pageextension 30125 "Shpfy Sales Invoice Update" extends "Posted Sales Inv. - Up { group(Shopify) { + Caption = 'Shopify'; Visible = ShopifyTabVisible; + field("Shpfy Order Id"; Rec."Shpfy Order Id") { ApplicationArea = Basic, Suite; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al similarity index 94% rename from Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al rename to Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al index ca12ef5bba..8b9fb3641d 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfyExportInvoiceToShpfy.Report.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al @@ -3,14 +3,14 @@ namespace Microsoft.Integration.Shopify; using Microsoft.Sales.History; /// -/// Report Shpfy Export Invoice to Shpfy (ID 30117). +/// Report Shpfy Sync Invoices to Shpfy (ID 30117). /// -report 30117 "Shpfy Export Invoice to Shpfy" +report 30117 "Shpfy Sync Invoices to Shpfy" { ApplicationArea = All; - Caption = 'Export Invoice to Shopify'; + Caption = 'Sync Invoices to Shopify'; ProcessingOnly = true; - UsageCategory = Administration; + UsageCategory = Tasks; dataset { diff --git a/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al index 3b67bddc4e..a0626241d5 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Tables/ShpfyInvoiceHeader.Table.al @@ -7,6 +7,7 @@ table 30156 "Shpfy Invoice Header" { Caption = 'Shopify Invoice Header'; DataClassification = CustomerContent; + Access = Internal; fields { diff --git a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al similarity index 95% rename from Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al rename to Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al index be2c23e973..43371f35ea 100644 --- a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al @@ -5,6 +5,8 @@ namespace Microsoft.Integration.Shopify; /// codeunit 30168 "Shpfy Payment Terms API" { + Access = Internal; + var CommunicationMgt: Codeunit "Shpfy Communication Mgt."; JsonHelper: Codeunit "Shpfy Json Helper"; @@ -63,8 +65,8 @@ codeunit 30168 "Shpfy Payment Terms API" PaymentTermRecordRef.SetTable(ShpfyPaymentTerms); if IsNew then - ShpfyPaymentTerms.Insert() + ShpfyPaymentTerms.Insert(true) else - ShpfyPaymentTerms.Modify(); + ShpfyPaymentTerms.Modify(true); end; } \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al similarity index 98% rename from Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al rename to Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al index 97aa1e6b94..f663e5f834 100644 --- a/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerm.Table.al +++ b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al @@ -9,6 +9,7 @@ table 30157 "Shpfy Payment Terms" { Caption = 'Payment Terms'; DataClassification = CustomerContent; + Access = Internal; fields { From ab6d5edb5de026fceb0a302fe18b1fd238255bfc Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Wed, 26 Jun 2024 16:09:31 +0300 Subject: [PATCH 5/8] Fixed PR comments --- .../app/src/Base/Pages/ShpfyShopCard.Page.al | 14 +++++++------- .../ShpfyPostedInvoiceExport.Codeunit.al | 18 +++++++++++++++--- .../Reports/ShpfySyncInvoicesToShpfy.Report.al | 6 ++++++ 3 files changed, 28 insertions(+), 10 deletions(-) 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 e790e59939..ec1a95df19 100644 --- a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al +++ b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al @@ -139,12 +139,6 @@ page 30101 "Shpfy Shop Card" ToolTip = 'Specifies the date on which Business Central will no longer support Shopify Admin API version. To continue to use your integration, upgrade to the latest version of Business Central before this date.'; Editable = false; } - field("Posted Invoice Sync"; Rec."Posted Invoice Sync") - { - ApplicationArea = All; - Importance = Additional; - ToolTip = 'Specifies whether the posted sales invoices can be synchronized to Shopify.'; - } } group(ItemSync) { @@ -253,6 +247,7 @@ page 30101 "Shpfy Shop Card" } 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.'; } } @@ -534,6 +529,12 @@ page 30101 "Shpfy Shop Card" ToolTip = 'Specifies whether to keep zero quantity lines in the Shopify order. If you do not select this option, zero quantity lines are removed from the Shopify order.'; Visible = false; } + field("Posted Invoice Sync"; Rec."Posted Invoice Sync") + { + ApplicationArea = All; + Importance = Additional; + ToolTip = 'Specifies whether the posted sales invoices can be synchronized to Shopify.'; + } } group(ReturnsAndRefunds) { @@ -1002,7 +1003,6 @@ page 30101 "Shpfy Shop Card" ApplicationArea = All; Caption = 'Sync Posted Sales Invoices'; Image = Export; - Enabled = Rec."Posted Invoice Sync"; Promoted = true; PromotedCategory = Category5; PromotedIsBig = true; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al index 915becf969..0f1d5cd528 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -99,12 +99,24 @@ codeunit 30316 "Shpfy Posted Invoice Export" 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 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"; @@ -247,7 +259,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" var ShpfyCustomer: Record "Shpfy Customer"; begin - TempShpfyOrderHeader."Bill-to Name" := Format(SalesInvoiceHeader."Bill-to Name"); + 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"; @@ -259,7 +271,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" ShpfyCustomer.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No."); if ShpfyCustomer.FindFirst() then begin - TempShpfyOrderHeader.Email := Format(ShpfyCustomer.Email); + TempShpfyOrderHeader.Email := CopyStr(ShpfyCustomer.Email, 1, MaxStrLen(TempShpfyOrderHeader.Email)); TempShpfyOrderHeader."Phone No." := ShpfyCustomer."Phone No."; end; end; @@ -269,7 +281,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" SalesInvoiceHeader: Record "Sales Invoice Header" ) begin - TempShpfyOrderHeader."Ship-to Name" := Format(SalesInvoiceHeader."Ship-to Name"); + 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"; diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al index 8b9fb3641d..bc5fca3acb 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al @@ -20,10 +20,14 @@ report 30117 "Shpfy Sync Invoices to Shpfy" 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); + if not ShpfyShop."Posted Invoice Sync" then + Error(PostedInvoiceSyncNotSetErr); + ShpfyPostedInvoiceExport.SetShop(ShopCode); SetRange("Shpfy Order Id", 0); @@ -81,6 +85,7 @@ report 30117 "Shpfy Sync Invoices to Shpfy" } var + ShpfyShop: Record "Shpfy Shop"; ShpfyPostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; ShopCode: Code[20]; CurrSalesInvoiceHeaderNo: Code[20]; @@ -94,5 +99,6 @@ report 30117 "Shpfy Sync Invoices to Shpfy" internal procedure SetShop(NewShopCode: Code[20]) begin ShopCode := NewShopCode; + ShpfyShop.Get(ShopCode); end; } \ No newline at end of file From 9e1ba4ced1487fb2402a8d1e9b681e4757088140 Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Thu, 27 Jun 2024 11:32:13 +0300 Subject: [PATCH 6/8] Fixed PR commets --- .../app/src/Base/Pages/ShpfyShopCard.Page.al | 3 +- .../Codeunits/ShpfyDraftOrdersAPI.Codeunit.al | 13 ++--- .../ShpfyPostedInvoiceExport.Codeunit.al | 49 ++++++++++++------- .../ShpfySyncInvoicesToShpfy.Report.al | 9 +++- .../Tables/ShpfyPaymentTerms.Table.al | 13 +++++ 5 files changed, 59 insertions(+), 28 deletions(-) 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 ec1a95df19..31fc79980f 100644 --- a/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al +++ b/Apps/W1/Shopify/app/src/Base/Pages/ShpfyShopCard.Page.al @@ -532,7 +532,6 @@ page 30101 "Shpfy Shop Card" field("Posted Invoice Sync"; Rec."Posted Invoice Sync") { ApplicationArea = All; - Importance = Additional; ToolTip = 'Specifies whether the posted sales invoices can be synchronized to Shopify.'; } } @@ -1007,7 +1006,7 @@ page 30101 "Shpfy Shop Card" PromotedCategory = Category5; PromotedIsBig = true; PromotedOnly = true; - ToolTip = 'Synchronize posted sales invoices to Shopify. The action can be used only if the Posted Invoice Sync field is enabled in the Shopify shop.'; + 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 diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al index 4fbf4aea99..ae8b2ceedb 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyDraftOrdersAPI.Codeunit.al @@ -97,11 +97,12 @@ codeunit 30159 "Shpfy Draft Orders API" AddShippingAddressToGraphQuery(GraphQuery, TempShpfyOrderHeader); AddBillingAddressToGraphQuery(GraphQuery, TempShpfyOrderHeader); - AddLineItemsToGraphQuery(GraphQuery, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); AddNote(GraphQuery, TempShpfyOrderHeader); if TempShpfyOrderHeader.Unpaid then AddPaymentTerms(GraphQuery, TempShpfyOrderHeader); + AddLineItemsToGraphQuery(GraphQuery, TempShpfyOrderHeader, TempShpfyOrderLine, ShpfyOrderTaxLines); + GraphQuery.Append('}) {draftOrder { legacyResourceId } userErrors {field, message}}'); GraphQuery.Append('}"}'); @@ -293,28 +294,28 @@ codeunit 30159 "Shpfy Draft Orders API" local procedure AddPaymentTerms(var GraphQuery: TextBuilder; var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) var SalesInvoiceHeader: Record "Sales Invoice Header"; - ShpfyPaymentTerm: Record "Shpfy Payment Terms"; + ShpfyPaymentTerms: Record "Shpfy Payment Terms"; DueAtDateTime: DateTime; IssuedAtDateTime: DateTime; begin - if not ShopifyPaymentTermsExists(ShpfyPaymentTerm, TempShpfyOrderHeader, SalesInvoiceHeader) then + if not ShopifyPaymentTermsExists(ShpfyPaymentTerms, TempShpfyOrderHeader, SalesInvoiceHeader) then exit; GraphQuery.Append(', paymentTerms: {'); GraphQuery.Append('paymentTermsTemplateId: \"gid://shopify/PaymentTermsTemplate/'); - GraphQuery.Append(Format(ShpfyPaymentTerm."Id")); + GraphQuery.Append(Format(ShpfyPaymentTerms."Id")); GraphQuery.Append('\"'); Evaluate(IssuedAtDateTime, Format(SalesInvoiceHeader."Document Date")); Evaluate(DueAtDateTime, Format(SalesInvoiceHeader."Due Date")); GraphQuery.Append(', paymentSchedules: {'); - if ShpfyPaymentTerm.Type = 'FIXED' then begin + if ShpfyPaymentTerms.Type = 'FIXED' then begin GraphQuery.Append('dueAt: \"'); GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(DueAtDateTime, 0, 9))); GraphQuery.Append('\"'); end else - if ShpfyPaymentTerm.Type = 'NET' then begin + if ShpfyPaymentTerms.Type = 'NET' then begin GraphQuery.Append(', issuedAt: \"'); GraphQuery.Append(ShpfyCommunicationMgt.EscapeGrapQLData(Format(IssuedAtDateTime, 0, 9))); GraphQuery.Append('\"'); diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al index 0f1d5cd528..b716914733 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -96,6 +96,9 @@ codeunit 30316 "Shpfy Posted Invoice Export" exit(false); end; + if not ShopifyPaymentTermsExists(SalesInvoiceHeader."Payment Terms Code") then + exit(false); + if ShpfyShop."Default Customer No." = SalesInvoiceHeader."Bill-to Customer No." then exit(false); @@ -108,6 +111,24 @@ codeunit 30316 "Shpfy Posted Invoice Export" exit(true); 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"; @@ -229,15 +250,14 @@ codeunit 30316 "Shpfy Posted Invoice Export" 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); - MapBillInformation(TempShpfyOrderHeader, SalesInvoiceHeader); + MapBillToInformation(TempShpfyOrderHeader, SalesInvoiceHeader); MapShipToInformation(TempShpfyOrderHeader, SalesInvoiceHeader); - MapCustomerLedgerEntryInformation(SalesInvoiceHeader, TempShpfyOrderHeader); - TempShpfyOrderHeader."Fulfillment Status" := Enum::"Shpfy Order Fulfill. Status"::Fulfilled; - SalesInvoiceHeader.CalcFields("Invoice Discount Amount"); - TempShpfyOrderHeader."Discount Amount" := SalesInvoiceHeader."Invoice Discount Amount"; - TempShpfyOrderHeader."Shop Code" := ShpfyShop.Code; TempShpfyOrderHeader.Insert(false); end; @@ -252,7 +272,7 @@ codeunit 30316 "Shpfy Posted Invoice Export" exit(GeneralLedgerSetup."LCY Code"); end; - local procedure MapBillInformation( + local procedure MapBillToInformation( var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary; SalesInvoiceHeader: Record "Sales Invoice Header" ) @@ -291,19 +311,10 @@ codeunit 30316 "Shpfy Posted Invoice Export" TempShpfyOrderHeader."Ship-to Country/Region Code" := SalesInvoiceHeader."Ship-to Country/Region Code"; end; - local procedure MapCustomerLedgerEntryInformation( - SalesInvoiceHeader: Record "Sales Invoice Header"; - var TempShpfyOrderHeader: Record "Shpfy Order Header" temporary) - var - CustLedgerEntry: Record "Cust. Ledger Entry"; + local procedure IsInvoiceUnpaid(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean begin - CustLedgerEntry.Get(SalesInvoiceHeader."Cust. Ledger Entry No."); - CustLedgerEntry.CalcFields("Remaining Amount"); - - if CustLedgerEntry."Remaining Amount" = 0 then - TempShpfyOrderHeader.Unpaid := false - else - TempShpfyOrderHeader.Unpaid := true; + SalesInvoiceHeader.CalcFields("Remaining Amount"); + exit(SalesInvoiceHeader."Remaining Amount" <> 0); end; local procedure MapSalesInvoiceLine( diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al index bc5fca3acb..b536ee4781 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al @@ -25,6 +25,8 @@ report 30117 "Shpfy Sync Invoices to Shpfy" if ShopCode = '' then Error(ShopCodeNotSetErr); + ShpfyShop.Get(ShopCode); + if not ShpfyShop."Posted Invoice Sync" then Error(PostedInvoiceSyncNotSetErr); @@ -78,6 +80,12 @@ report 30117 "Shpfy Sync Invoices to Shpfy" TableRelation = "Shpfy Shop"; ToolTip = 'Specifies the Shopify Shop to which the invoice will be exported.'; ShowMandatory = true; + + trigger OnValidate() + begin + if ShopCode <> '' then + ShpfyShop.Get(ShopCode); + end; } } } @@ -99,6 +107,5 @@ report 30117 "Shpfy Sync Invoices to Shpfy" internal procedure SetShop(NewShopCode: Code[20]) begin ShopCode := NewShopCode; - ShpfyShop.Get(ShopCode); 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 index f663e5f834..9011153616 100644 --- a/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al +++ b/Apps/W1/Shopify/app/src/Payments/Tables/ShpfyPaymentTerms.Table.al @@ -47,6 +47,19 @@ table 30157 "Shpfy Payment Terms" 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]) { From 954b2adc0c6b3e84bf6523468d658f4116e51f6e Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Thu, 27 Jun 2024 14:26:36 +0300 Subject: [PATCH 7/8] Fixed PR comments --- .../Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al index b536ee4781..8a0443bfb5 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Reports/ShpfySyncInvoicesToShpfy.Report.al @@ -80,12 +80,6 @@ report 30117 "Shpfy Sync Invoices to Shpfy" TableRelation = "Shpfy Shop"; ToolTip = 'Specifies the Shopify Shop to which the invoice will be exported.'; ShowMandatory = true; - - trigger OnValidate() - begin - if ShopCode <> '' then - ShpfyShop.Get(ShopCode); - end; } } } From 269dab2aa85bdbacc7ac32b0bf51c1c4e875b335 Mon Sep 17 00:00:00 2001 From: Gediminas Gaubys Date: Thu, 27 Jun 2024 16:31:32 +0300 Subject: [PATCH 8/8] Additional changes for Payment Terms and Currency Code --- .../ShpfyPostedInvoiceExport.Codeunit.al | 21 +++++++++++++++++++ .../ShpfyPaymentTermsAPI.Codeunit.al | 13 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al index b716914733..533027c792 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -96,6 +96,9 @@ codeunit 30316 "Shpfy Posted Invoice Export" exit(false); end; + if not CurrencyCodeMatch(SalesInvoiceHeader) then + exit(false); + if not ShopifyPaymentTermsExists(SalesInvoiceHeader."Payment Terms Code") then exit(false); @@ -111,6 +114,24 @@ codeunit 30316 "Shpfy Posted Invoice Export" 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"; diff --git a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al index 43371f35ea..42924713b5 100644 --- a/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Payments/Codeunits/ShpfyPaymentTermsAPI.Codeunit.al @@ -64,9 +64,22 @@ codeunit 30168 "Shpfy Payment Terms API" 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