diff --git a/Apps/W1/EDocument/app/Permissions/EDocCoreBasic.PermissionSet.al b/Apps/W1/EDocument/app/Permissions/EDocCoreBasic.PermissionSet.al index 149349c3ad..e81a34511d 100644 --- a/Apps/W1/EDocument/app/Permissions/EDocCoreBasic.PermissionSet.al +++ b/Apps/W1/EDocument/app/Permissions/EDocCoreBasic.PermissionSet.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.IO.Peppol; using Microsoft.EServices.EDocument.OrderMatch; using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.eServices.EDocument.Payments; permissionset 6103 "E-Doc. Core - Basic" { @@ -29,5 +30,6 @@ permissionset 6103 "E-Doc. Core - Basic" tabledata "E-Doc. Service Supported Type" = im, tabledata "E-Doc. Imported Line" = imd, tabledata "E-Doc. Order Match" = imd, - tabledata "Service Participant" = imd; + tabledata "Service Participant" = imd, + tabledata "E-Document Payment" = imd; } diff --git a/Apps/W1/EDocument/app/Permissions/EDocCoreEdit.PermissionSet.al b/Apps/W1/EDocument/app/Permissions/EDocCoreEdit.PermissionSet.al index 81acc2e21f..59f059e7f1 100644 --- a/Apps/W1/EDocument/app/Permissions/EDocCoreEdit.PermissionSet.al +++ b/Apps/W1/EDocument/app/Permissions/EDocCoreEdit.PermissionSet.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.IO.Peppol; using Microsoft.EServices.EDocument.OrderMatch; using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.eServices.EDocument.Payments; permissionset 6102 "E-Doc. Core - Edit" { @@ -29,5 +30,6 @@ permissionset 6102 "E-Doc. Core - Edit" tabledata "E-Doc. Service Supported Type" = IMD, tabledata "E-Doc. Imported Line" = IMD, tabledata "E-Doc. Order Match" = IMD, - tabledata "Service Participant" = IMD; + tabledata "Service Participant" = IMD, + tabledata "E-Document Payment" = IMD; } diff --git a/Apps/W1/EDocument/app/Permissions/EDocCoreObjects.PermissionSet.al b/Apps/W1/EDocument/app/Permissions/EDocCoreObjects.PermissionSet.al index 3000c3ad9d..42292c47c6 100644 --- a/Apps/W1/EDocument/app/Permissions/EDocCoreObjects.PermissionSet.al +++ b/Apps/W1/EDocument/app/Permissions/EDocCoreObjects.PermissionSet.al @@ -5,10 +5,12 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.IO; +using Microsoft.eServices.EDocument.Integration.Payments; using Microsoft.eServices.EDocument.IO.Peppol; using Microsoft.EServices.EDocument.OrderMatch; using Microsoft.EServices.EDocument.OrderMatch.Copilot; using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.eServices.EDocument.Payments; permissionset 6100 "E-Doc. Core - Objects" { Assignable = false; @@ -28,6 +30,7 @@ permissionset 6100 "E-Doc. Core - Objects" table "E-Doc. Imported Line" = X, table "E-Doc. PO Match Prop. Buffer" = X, table "Service Participant" = X, + table "E-Document Payment" = X, codeunit "E-Document Import Job" = X, codeunit "E-Doc. Integration Management" = X, codeunit "E-Doc. Mapping" = X, @@ -69,6 +72,12 @@ permissionset 6100 "E-Doc. Core - Objects" codeunit "E-Doc. PO Copilot Matching" = X, codeunit "E-Doc. Attachment Processor" = X, codeunit "Service Participant" = X, + codeunit "Payment Integration Management" = X, + codeunit "Empty Payment Handler" = X, + codeunit "Send Payment" = X, + codeunit "Receive Payments" = X, + codeunit "Get Payment Details" = X, + codeunit "Sync Payments Job" = X, page "E-Doc. Changes Part" = X, page "E-Doc. Changes Preview" = X, page "E-Document Activities" = X, @@ -91,5 +100,6 @@ permissionset 6100 "E-Doc. Core - Objects" page "E-Doc. PO Copilot Prop" = X, page "E-Doc. PO Match Prop. Sub" = X, page "E-Doc. Order Match Act." = X, - page "Service Participants" = X; + page "Service Participants" = X, + page "E-Document Payments" = X; } diff --git a/Apps/W1/EDocument/app/Permissions/EDocCoreRead.PermissionSet.al b/Apps/W1/EDocument/app/Permissions/EDocCoreRead.PermissionSet.al index 30362c9850..6b14fa6ff3 100644 --- a/Apps/W1/EDocument/app/Permissions/EDocCoreRead.PermissionSet.al +++ b/Apps/W1/EDocument/app/Permissions/EDocCoreRead.PermissionSet.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.IO.Peppol; using Microsoft.EServices.EDocument.OrderMatch; using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.eServices.EDocument.Payments; permissionset 6101 "E-Doc. Core - Read" { @@ -26,5 +27,6 @@ permissionset 6101 "E-Doc. Core - Read" tabledata "E-Doc. Service Supported Type" = R, tabledata "E-Doc. Imported Line" = R, tabledata "E-Doc. Order Match" = R, - tabledata "Service Participant" = R; + tabledata "Service Participant" = R, + tabledata "E-Document Payment" = R; } \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Document/EDocument.Page.al b/Apps/W1/EDocument/app/src/Document/EDocument.Page.al index d975f90883..03029d00a8 100644 --- a/Apps/W1/EDocument/app/src/Document/EDocument.Page.al +++ b/Apps/W1/EDocument/app/src/Document/EDocument.Page.al @@ -11,6 +11,8 @@ using Microsoft.eServices.EDocument.Integration.Receive; using Microsoft.Bank.Reconciliation; using Microsoft.eServices.EDocument.OrderMatch; using Microsoft.eServices.EDocument.OrderMatch.Copilot; +using Microsoft.eServices.EDocument.Integration.Payments; +using Microsoft.eServices.EDocument.Payments; page 6121 "E-Document" { @@ -119,6 +121,17 @@ page 6121 "E-Document" Importance = Additional; ToolTip = 'Specifies the electronic document posting date.'; } + field("Payment Status"; this.PaymentStatus) + { + Caption = 'Payment Status'; + Visible = false; + Editable = false; + ToolTip = 'Specifies the electronic document payment status.'; + } + field("Paid Amount"; Rec."Paid Amount") + { + Visible = false; + } } group(ReceivingCompanyInfo) { @@ -346,6 +359,35 @@ page 6121 "E-Document" end; } } + group(Payments) + { + Caption = 'Payments'; + + action(ReceivePayments) + { + Caption = 'Receive Payments'; + ToolTip = 'Receive payments for the electronic document.'; + Image = Payment; + Visible = false; + + trigger OnAction() + begin + this.ReceivePaymentsForEDoc(); + end; + } + action(SendPayments) + { + Caption = 'Send Payment'; + ToolTip = 'Send payment for the electronic document.'; + Image = Payment; + Visible = false; + + trigger OnAction() + begin + this.SendPaymentsForEDoc(); + end; + } + } group(Troubleshoot) { Caption = 'Troubleshoot'; @@ -507,6 +549,8 @@ page 6121 "E-Document" SetIncomingDocActions(); EDocImport.ProcessEDocPendingOrderMatch(Rec); + + this.SetPaymentStatus(); end; local procedure SetStyle() @@ -589,6 +633,49 @@ page 6121 "E-Document" ShowRelink := false; end; + local procedure SetPaymentStatus() + begin + if Rec."Paid Amount" = 0 then + this.PaymentStatus := this.PaymentStatus::"Not Paid" + else + if Rec."Paid Amount" < Rec."Amount Incl. VAT" then + this.PaymentStatus := this.PaymentStatus::"Partially Paid" + else + this.PaymentStatus := this.PaymentStatus::Paid; + end; + + local procedure ReceivePaymentsForEDoc() + var + EDocService: Record "E-Document Service"; + PaymentContext: Codeunit PaymentContext; + PaymentIntegrationManagement: Codeunit "Payment Integration Management"; + EDocServices: Page "E-Document Services"; + begin + EDocServices.LookupMode(true); + if EDocServices.RunModal() <> Action::LookupOK then + exit; + + EDocServices.GetRecord(EDocService); + this.EDocumentErrorHelper.ClearPaymentErrorMessages(Rec); + PaymentIntegrationManagement.ReceivePayments(Rec, EDocService, PaymentContext); + end; + + local procedure SendPaymentsForEDoc() + var + EDocService: Record "E-Document Service"; + PaymentContext: Codeunit PaymentContext; + PaymentIntegrationManagement: Codeunit "Payment Integration Management"; + EDocServices: Page "E-Document Services"; + begin + EDocServices.LookupMode(true); + if EDocServices.RunModal() <> Action::LookupOK then + exit; + + EDocServices.GetRecord(EDocService); + this.EDocumentErrorHelper.ClearPaymentErrorMessages(Rec); + PaymentIntegrationManagement.SendPayments(Rec, EDocService, PaymentContext); + end; + var EDocumentBackgroundjobs: Codeunit "E-Document Background Jobs"; EDocIntegrationManagement: Codeunit "E-Doc. Integration Management"; @@ -596,6 +683,7 @@ page 6121 "E-Document" EDocumentErrorHelper: Codeunit "E-Document Error Helper"; EDocumentHelper: Codeunit "E-Document Processing"; ErrorsAndWarningsNotification: Notification; + PaymentStatus: Enum "E-Document Payment Progress"; RecordLinkTxt, StyleStatusTxt : Text; ShowRelink, ShowMapToOrder, HasErrorsOrWarnings, HasErrors, IsIncomingDoc, IsProcessed, CopilotVisible : Boolean; EDocHasErrorOrWarningMsg: Label 'Errors or warnings found for E-Document. Please review below in "Error Messages" section.'; diff --git a/Apps/W1/EDocument/app/src/Document/EDocument.Table.al b/Apps/W1/EDocument/app/src/Document/EDocument.Table.al index 4ec6ab4122..e357c0879b 100644 --- a/Apps/W1/EDocument/app/src/Document/EDocument.Table.al +++ b/Apps/W1/EDocument/app/src/Document/EDocument.Table.al @@ -11,6 +11,7 @@ using System.Automation; using System.IO; using System.Reflection; using System.Threading; +using Microsoft.eServices.EDocument.Payments; table 6121 "E-Document" { @@ -182,8 +183,16 @@ table 6121 "E-Document" Caption = 'Receiving Company Id'; ToolTip = 'Specifies the receiving company id, such as PEPPOL id, or other identifiers used in the electronic document exchange.'; } - + field(32; "Paid Amount"; Decimal) + { + Caption = 'Paid Amount'; + Editable = false; + FieldClass = FlowField; + CalcFormula = sum("E-Document Payment".Amount where("E-Document Entry No." = field("Entry No"))); + ToolTip = 'Specifies the amount that has already been paid.'; + } } + keys { key(Key1; "Entry No") diff --git a/Apps/W1/EDocument/app/src/Helpers/EDocumentErrorHelper.Codeunit.al b/Apps/W1/EDocument/app/src/Helpers/EDocumentErrorHelper.Codeunit.al index b58704193d..21bf573122 100644 --- a/Apps/W1/EDocument/app/src/Helpers/EDocumentErrorHelper.Codeunit.al +++ b/Apps/W1/EDocument/app/src/Helpers/EDocumentErrorHelper.Codeunit.al @@ -6,6 +6,7 @@ namespace Microsoft.eServices.EDocument; using System.Telemetry; using System.Utilities; +using Microsoft.eServices.EDocument.Payments; codeunit 6115 "E-Document Error Helper" { @@ -105,6 +106,19 @@ codeunit 6115 "E-Document Error Helper" ErrorMessage.LogSimpleMessage(ErrorMessage."Message Type"::Error, Message); end; + /// + /// Use it to clear errors for E-Document related to payments. + /// + /// The E-Document record. + procedure ClearPaymentErrorMessages(EDocument: Record "E-Document") + var + ErrorMessage: Record "Error Message"; + begin + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + ErrorMessage.SetRange("Table Number", Database::"E-Document Payment"); + ErrorMessage.DeleteAll(false); + end; + internal procedure GetTelemetryImplErrLbl(): Text begin exit(EDocTelemetryImplErr); diff --git a/Apps/W1/EDocument/app/src/Integration/Interfaces/IDocumentPaymentHandler.Interface.al b/Apps/W1/EDocument/app/src/Integration/Interfaces/IDocumentPaymentHandler.Interface.al new file mode 100644 index 0000000000..c7f9f45a12 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Interfaces/IDocumentPaymentHandler.Interface.al @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Interfaces; + +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration.Payments; +using System.Utilities; + +/// +/// Interface for sending and receiving payment information for E-Documents using E-Document service +/// +interface IDocumentPaymentHandler +{ + procedure Send(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + + procedure Receive(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var PaymentsMetadata: Codeunit "Temp Blob List"; PaymentContext: Codeunit PaymentContext) + + procedure GetDetails(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; PaymentContext: Codeunit PaymentContext) +} diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/GetPaymentDetails.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/GetPaymentDetails.Codeunit.al new file mode 100644 index 0000000000..54ca76f054 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/GetPaymentDetails.Codeunit.al @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using System.Utilities; +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration.Interfaces; + +codeunit 6107 "Get Payment Details" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + trigger OnRun() + begin + this.EDocumentService.TestField(Code); + this.IDocumentPaymentHandler.GetDetails(this.EDocument, this.EDocumentService, this.PaymentMetadata, this.PaymentContext); + end; + + /// + /// Sets the global variable PaymentContext. + /// + /// Payment context codeunit. + procedure SetContext(PaymentContext: Codeunit PaymentContext) + begin + this.PaymentContext := PaymentContext; + end; + + /// + /// Sets the IDocumentPaymentHandler instance. + /// + /// IDocumentPaymentHandler implementation used for receiving payment details. + procedure SetInstance(PaymentHandler: Interface IDocumentPaymentHandler) + begin + this.IDocumentPaymentHandler := PaymentHandler; + end; + + /// + /// Sets the parameters for the payment information receiving. + /// + /// Electronic document for which payment is received. + /// Service for receiving payments. + /// TempBlob which contains received payment information. + procedure SetParameters(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob") + begin + this.EDocument.Copy(EDocument); + this.EDocumentService.Copy(EDocumentService); + this.PaymentMetadata := PaymentMetadata; + end; + + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + PaymentMetadata: Codeunit "Temp Blob"; + PaymentContext: Codeunit PaymentContext; + IDocumentPaymentHandler: Interface IDocumentPaymentHandler; +} diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/Implementations/EmptyPaymentHandler.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/Implementations/EmptyPaymentHandler.Codeunit.al new file mode 100644 index 0000000000..8ca1839684 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/Implementations/EmptyPaymentHandler.Codeunit.al @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration.Interfaces; +using Microsoft.eServices.EDocument.Payments; +using System.Utilities; + +/// +/// This codeunit is used to provide a default implementation for the "Document Payment Handler" interface. +/// +codeunit 6105 "Empty Payment Handler" implements IDocumentPaymentHandler +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + procedure Send(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + begin + // This method serves as a placeholder implementation for the IDocumentPaymentHandler interface. + end; + + procedure Receive(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var PaymentsMetadata: Codeunit "Temp Blob List"; PaymentContext: Codeunit PaymentContext) + begin + // This method serves as a placeholder implementation for the IDocumentPaymentHandler interface. + end; + + procedure GetDetails(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; PaymentContext: Codeunit PaymentContext) + begin + // This method serves as a placeholder implementation for the IDocumentPaymentHandler interface. + end; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/PaymentContext.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/PaymentContext.Codeunit.al new file mode 100644 index 0000000000..ec3cb5cba7 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/PaymentContext.Codeunit.al @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using Microsoft.eServices.EDocument.Integration; +using Microsoft.eServices.EDocument.Payments; + +codeunit 6104 PaymentContext +{ + /// + /// Get the Http Message State codeunit. + /// + /// codeunit Http Message State that contains http request and response messages. + procedure Http(): Codeunit "Http Message State" + begin + exit(this.HttpMessageState); + end; + + /// + /// Retrieves the payment date. + /// + /// Date when the payment was made. + procedure GetDate(): Date + begin + exit(this.Date); + end; + + /// + /// Retrieves the payment amount. + /// + /// Amount of the payment. + procedure GetAmount(): Decimal + begin + exit(this.Amount); + end; + + /// + /// Sets the payment date and amount. + /// + /// Date when the payment was made. + /// Amount of the payment. + procedure SetPaymentInformation(Date: Date; Amount: Decimal) + begin + this.Date := Date; + this.Amount := Amount; + end; + + /// + /// Retrieves the payment status. + /// + /// Current payment status. + procedure GetPaymentStatus(): Enum "Payment Status" + begin + exit(this.PaymentStatus); + end; + + /// + /// Sets the payment status. + /// + /// New payment status. + procedure SetPaymentStatus(NewPaymentStatus: Enum "Payment Status") + begin + this.PaymentStatus := NewPaymentStatus; + end; + + var + HttpMessageState: Codeunit "Http Message State"; + PaymentStatus: Enum "Payment Status"; + Date: Date; + Amount: Decimal; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/PaymentIntegration.Enum.al b/Apps/W1/EDocument/app/src/Integration/Payments/PaymentIntegration.Enum.al new file mode 100644 index 0000000000..89c3f23781 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/PaymentIntegration.Enum.al @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using Microsoft.eServices.EDocument.Integration.Interfaces; + +enum 6105 "Payment Integration" implements IDocumentPaymentHandler +{ + Extensible = true; + Access = Public; + + value(0; "No Integration") + { + Caption = 'No Integration'; + Implementation = IDocumentPaymentHandler = "Empty Payment Handler"; + } +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/PaymentIntegrationManagement.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/PaymentIntegrationManagement.Codeunit.al new file mode 100644 index 0000000000..e0e6821980 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/PaymentIntegrationManagement.Codeunit.al @@ -0,0 +1,129 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using System.Utilities; +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Payments; +using Microsoft.eServices.EDocument.Integration.Interfaces; + +codeunit 6117 "Payment Integration Management" +{ + internal procedure ReceivePayments(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + var + PaymentMetadata: Codeunit "Temp Blob"; + PaymentsMetadata: Codeunit "Temp Blob List"; + IDocumentPaymentHandler: Interface IDocumentPaymentHandler; + Index: Integer; + begin + IDocumentPaymentHandler := EDocumentService."Payment Integration"; + this.RunReceivePayments(EDocument, EDocumentService, PaymentsMetadata, IDocumentPaymentHandler, PaymentContext); + + if PaymentsMetadata.IsEmpty() then + exit; + + for Index := 1 to PaymentsMetadata.Count() do begin + PaymentsMetadata.Get(Index, PaymentMetadata); + this.ReceivePaymentDetails(EDocument, EDocumentService, PaymentMetadata, IDocumentPaymentHandler); + end; + end; + + internal procedure SendPayments(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + var + Payment: Record "E-Document Payment"; + begin + Payment.SetRange("E-Document Entry No.", EDocument."Entry No"); + Payment.SetFilter(Status, '%1|%2', Payment.Status::Created, Payment.Status::"Sending Error"); + + if not Payment.FindSet() then + exit; + + repeat + this.SendPayment(EDocument, EDocumentService, Payment); + until Payment.Next() = 0; + end; + + local procedure RunReceivePayments(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; Payments: Codeunit "Temp Blob List"; IDocumentPaymentHandler: Interface IDocumentPaymentHandler; PaymentContext: Codeunit PaymentContext) + var + ReceivePayments: Codeunit "Receive Payments"; + begin + // Commit needed for "if codeunit run" pattern when catching errors. + Commit(); + + ReceivePayments.SetInstance(IDocumentPaymentHandler); + ReceivePayments.SetService(EDocumentService); + ReceivePayments.SetDocument(EDocument); + ReceivePayments.SetContext(PaymentContext); + ReceivePayments.SetPayments(Payments); + if not ReceivePayments.Run() then + this.EDocumentErrorHelper.LogErrorMessage(EDocument, Database::"E-Document Payment", EDocument.FieldNo("Paid Amount"), StrSubstNo(this.PaymentReceiveErr, GetLastErrorText())); + end; + + local procedure ReceivePaymentDetails(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; IDocumentPaymentHandler: Interface IDocumentPaymentHandler) + var + Payment: Record "E-Document Payment"; + PaymentContext: Codeunit PaymentContext; + begin + PaymentContext.SetPaymentStatus("Payment Status"::Received); + + this.RunGetPaymentDetails(EDocument, EDocumentService, PaymentMetadata, IDocumentPaymentHandler, PaymentContext); + + if (PaymentContext.GetAmount() = 0) or (PaymentContext.GetDate() = 0D) then + exit; + + Payment.Init(); + Payment.Validate("E-Document Entry No.", EDocument."Entry No"); + Payment.Date := PaymentContext.GetDate(); + Payment.Validate(Amount, PaymentContext.GetAmount()); + Payment.Status := PaymentContext.GetPaymentStatus(); + Payment.Insert(true); + end; + + local procedure RunGetPaymentDetails(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; IDocumentPaymentHandler: Interface IDocumentPaymentHandler; PaymentContext: Codeunit PaymentContext): Boolean + var + GetPaymentDetails: Codeunit "Get Payment Details"; + begin + // Commit needed for "if codeunit run" pattern when catching errors. + Commit(); + + GetPaymentDetails.SetInstance(IDocumentPaymentHandler); + GetPaymentDetails.SetContext(PaymentContext); + GetPaymentDetails.SetParameters(EDocument, EDocumentService, PaymentMetadata); + if not GetPaymentDetails.Run() then + this.EDocumentErrorHelper.LogErrorMessage(EDocument, Database::"E-Document Payment", EDocument.FieldNo("Paid Amount"), StrSubstNo(this.PaymentReceiveErr, GetLastErrorText())); + end; + + local procedure SendPayment(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; Payment: Record "E-Document Payment") + var + PaymentContext: Codeunit PaymentContext; + begin + PaymentContext.SetPaymentStatus("Payment Status"::Sent); + + this.RunSendPayment(EDocument, EDocumentService, Payment, EDocumentService."Payment Integration", PaymentContext); + + Payment.Status := PaymentContext.GetPaymentStatus(); + Payment.Modify(false); + end; + + local procedure RunSendPayment(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; Payment: Record "E-Document Payment"; IDocumentPaymentHandler: Interface IDocumentPaymentHandler; PaymentContext: Codeunit PaymentContext): Boolean + var + SendPayment: Codeunit "Send Payment"; + begin + // Commit needed for "if codeunit run" pattern when catching errors. + Commit(); + + SendPayment.SetInstance(IDocumentPaymentHandler); + SendPayment.SetDocumentAndService(EDocument, EDocumentService); + SendPayment.SetContext(PaymentContext); + + if not SendPayment.Run() then + this.EDocumentErrorHelper.LogErrorMessage(EDocument, Payment, EDocument.FieldNo("Paid Amount"), StrSubstNo(this.PaymentSendErr, GetLastErrorText())); + end; + + var + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + PaymentSendErr: Label 'Sending payments for this document failed with error: %1', Comment = '%1 - error message'; + PaymentReceiveErr: Label 'Receiving payments for this document failed with error: %1', Comment = '%1 - error message'; +} diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/ReceivePayments.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/ReceivePayments.Codeunit.al new file mode 100644 index 0000000000..1770e9fc28 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/ReceivePayments.Codeunit.al @@ -0,0 +1,74 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using System.Utilities; +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration.Interfaces; + +codeunit 6106 "Receive Payments" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + trigger OnRun() + begin + this.EDocumentService.TestField(Code); + this.IDocumentPaymentHandler.Receive(this.EDocument, this.EDocumentService, this.PaymentsMetadata, this.PaymentContext); + end; + + /// + /// Sets the IDocumentPaymentHandler instance. + /// + /// IDocumentPaymentHandler implementation used for receiving payments. + procedure SetInstance(PaymentHandler: Interface IDocumentPaymentHandler) + begin + this.IDocumentPaymentHandler := PaymentHandler; + end; + + /// + /// Sets the global variable PaymentContext. + /// + /// Payment context codeunit. + procedure SetContext(PaymentContext: Codeunit PaymentContext) + begin + this.PaymentContext := PaymentContext; + end; + + /// + /// Sets the received payments to Temp Blob List. + /// + /// Temp Blob List to save received payments. + procedure SetPayments(PaymentsMetadata: Codeunit "Temp Blob List") + begin + this.PaymentsMetadata := PaymentsMetadata + end; + + /// + /// Sets the E-Document Service used for receiving payments. + /// + /// Service for receiving payments. + procedure SetService(var EDocumentService: Record "E-Document Service") + begin + this.EDocumentService.Copy(EDocumentService); + end; + + /// + /// Sets the E-Document for which payments are received. + /// + /// E-Document for which payments are received. + procedure SetDocument(var EDocument: Record "E-Document") + begin + this.EDocument.Copy(EDocument); + end; + + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + PaymentsMetadata: Codeunit "Temp Blob List"; + PaymentContext: Codeunit PaymentContext; + IDocumentPaymentHandler: Interface IDocumentPaymentHandler; +} diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/SendPayment.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/SendPayment.Codeunit.al new file mode 100644 index 0000000000..a4f3a5e55d --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/SendPayment.Codeunit.al @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration.Interfaces; +using Microsoft.eServices.EDocument.Payments; + +codeunit 6116 "Send Payment" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + trigger OnRun() + begin + this.EDocumentService.TestField(Code); + this.IPaymentHandler.Send(this.EDocument, this.EDocumentService, this.PaymentContext); + end; + + /// + /// Sets the IDocumentPaymentHandler instance. + /// + /// IDocumentPaymentHandler implementation used for sending payments. + procedure SetInstance(PaymentHandler: Interface IDocumentPaymentHandler) + begin + this.IPaymentHandler := PaymentHandler; + end; + + /// + /// Sets the global variable PaymentContext. + /// + /// Payment context codeunit. + procedure SetContext(PaymentContext: Codeunit PaymentContext) + begin + this.PaymentContext := PaymentContext; + end; + + /// + /// Sets the parameters for the payment handler. + /// + /// Electronic document for which payments are sent. + /// Service for sending payments. + procedure SetDocumentAndService(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service") + begin + this.EDocument.Copy(EDocument); + this.EDocumentService.Copy(EDocumentService); + end; + + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + PaymentContext: Codeunit PaymentContext; + IPaymentHandler: Interface IDocumentPaymentHandler; +} diff --git a/Apps/W1/EDocument/app/src/Integration/Payments/SyncPaymentsJob.Codeunit.al b/Apps/W1/EDocument/app/src/Integration/Payments/SyncPaymentsJob.Codeunit.al new file mode 100644 index 0000000000..24ebeb9b07 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Integration/Payments/SyncPaymentsJob.Codeunit.al @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Integration.Payments; + +using System.Threading; +using Microsoft.eServices.EDocument; + +codeunit 6119 "Sync Payments Job" +{ + Access = Internal; + TableNo = "Job Queue Entry"; + + trigger OnRun() + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocumentServiceStatus: Record "E-Document Service Status"; + begin + EDocumentService.Get(Rec."Record ID to Process"); + if EDocumentService."Payment Integration" = EDocumentService."Payment Integration"::"No Integration" then + exit; + + EDocumentServiceStatus.SetRange("E-Document Service Code", EDocumentService."Code"); + if EDocumentServiceStatus.FindSet() then + repeat + EDocument.Get(EDocumentServiceStatus."E-Document Entry No"); + if EDocument."Direction" = EDocument.Direction::Incoming then + this.SendPayments(EDocument, EDocumentService) + else + this.ReceivePayments(EDocument, EDocumentService); + until EDocumentServiceStatus.Next() = 0; + end; + + local procedure SendPayments(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service") + var + PaymentContext: Codeunit PaymentContext; + PaymentIntegrationManagement: Codeunit "Payment Integration Management"; + begin + PaymentIntegrationManagement.SendPayments(EDocument, EDocumentService, PaymentContext); + end; + + local procedure ReceivePayments(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service") + var + PaymentContext: Codeunit PaymentContext; + PaymentIntegrationManagement: Codeunit "Payment Integration Management"; + begin + PaymentIntegrationManagement.ReceivePayments(EDocument, EDocumentService, PaymentContext); + end; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Payments/EDocumentPayment.Table.al b/Apps/W1/EDocument/app/src/Payments/EDocumentPayment.Table.al new file mode 100644 index 0000000000..3e3573d1a8 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Payments/EDocumentPayment.Table.al @@ -0,0 +1,113 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Payments; + +using Microsoft.eServices.EDocument; + +table 6101 "E-Document Payment" +{ + Caption = 'E-Document Payment'; + DataClassification = CustomerContent; + DrillDownPageId = "E-Document Payments"; + LookupPageId = "E-Document Payments"; + + fields + { + field(1; "E-Document Entry No."; Integer) + { + Caption = 'E-Document Entry No.'; + TableRelation = "E-Document"."Entry No"; + AllowInCustomizations = Always; + + trigger OnValidate() + begin + this.SetPaymentDirection(); + end; + } + field(2; "Payment No."; Integer) + { + Caption = 'Payment No.'; + AutoIncrement = true; + AllowInCustomizations = Always; + } + field(20; Date; Date) + { + Caption = 'Date'; + ToolTip = 'Specifies the date when the payment was made.'; + } + field(21; Amount; Decimal) + { + Caption = 'Amount'; + DecimalPlaces = 2; + ToolTip = 'Specifies the total payment amount including VAT.'; + + trigger OnValidate() + begin + this.CalculateVAT(); + end; + } + field(22; "VAT Base"; Decimal) + { + Caption = 'VAT Base'; + DecimalPlaces = 2; + Editable = false; + ToolTip = 'Specifies the net amount used as basis for VAT calculation for this payment transaction.'; + } + field(23; "VAT Amount"; Decimal) + { + Caption = 'VAT Amount'; + DecimalPlaces = 2; + Editable = false; + ToolTip = 'Specifies the calculated tax amount for this payment transaction.'; + } + field(24; Status; Enum "Payment Status") + { + Caption = 'Status'; + Editable = false; + InitValue = Created; + ToolTip = 'Specifies the current state of the payment.'; + } + field(25; Direction; Enum "E-Document Direction") + { + Caption = 'Direction'; + Editable = false; + ToolTip = 'Specifies whether this payment is being received (incoming) or sent (outgoing).'; + } + } + + keys + { + key(PK; "E-Document Entry No.", "Payment No.") + { + Clustered = true; + } + } + + local procedure CalculateVAT() + var + EDocument: Record "E-Document"; + EDocumentService: Record "E-Document Service"; + EDocLog: Codeunit "E-Document Log"; + begin + EDocument.Get(Rec."E-Document Entry No."); + EDocumentService := EDocLog.GetLastServiceFromLog(EDocument); + if not EDocumentService."Calculate Payment VAT" then + exit; + + Rec."VAT Base" := Rec.Amount / (EDocument."Amount Incl. VAT" / EDocument."Amount Excl. VAT"); + Rec."VAT Amount" := Rec.Amount - Rec."VAT Base"; + end; + + local procedure SetPaymentDirection() + var + EDocument: Record "E-Document"; + begin + EDocument.Get(Rec."E-Document Entry No."); + if EDocument.Direction = EDocument.Direction::Outgoing then + Rec.Direction := Rec.Direction::Incoming + else + Rec.Direction := Rec.Direction::Outgoing; + end; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/src/Payments/EDocumentPaymentProgress.Enum.al b/Apps/W1/EDocument/app/src/Payments/EDocumentPaymentProgress.Enum.al new file mode 100644 index 0000000000..122d29a77f --- /dev/null +++ b/Apps/W1/EDocument/app/src/Payments/EDocumentPaymentProgress.Enum.al @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Payments; + +enum 6104 "E-Document Payment Progress" +{ + Extensible = false; + + value(0; " ") + { + Caption = ' '; + } + value(1; "Not Paid") + { + Caption = 'Not Paid'; + } + value(2; "Partially Paid") + { + Caption = 'Partially Paid'; + } + value(3; Paid) + { + Caption = 'Paid In Full'; + } +} diff --git a/Apps/W1/EDocument/app/src/Payments/EDocumentPayments.Page.al b/Apps/W1/EDocument/app/src/Payments/EDocumentPayments.Page.al new file mode 100644 index 0000000000..6953951c15 --- /dev/null +++ b/Apps/W1/EDocument/app/src/Payments/EDocumentPayments.Page.al @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Payments; + +page 6101 "E-Document Payments" +{ + ApplicationArea = All; + Caption = 'E-Document Payments'; + PageType = List; + SourceTable = "E-Document Payment"; + UsageCategory = Lists; + + layout + { + area(Content) + { + repeater(General) + { + field(Date; Rec.Date) + { + Editable = this.PaymentEditable; + } + field(Amount; Rec.Amount) + { + Editable = this.PaymentEditable; + } + field("VAT Base"; Rec."VAT Base") + { + Visible = false; + } + field("VAT Amount"; Rec."VAT Amount") + { + Visible = false; + } + field(Status; Rec.Status) + { + } + field(Direction; Rec.Direction) + { + } + } + } + } + + var + PaymentEditable: Boolean; + + trigger OnAfterGetRecord() + begin + this.PaymentEditable := Rec.Status <> Rec.Status::Sent; + end; +} diff --git a/Apps/W1/EDocument/app/src/Payments/PaymentStatus.Enum.al b/Apps/W1/EDocument/app/src/Payments/PaymentStatus.Enum.al new file mode 100644 index 0000000000..ef7ad64afe --- /dev/null +++ b/Apps/W1/EDocument/app/src/Payments/PaymentStatus.Enum.al @@ -0,0 +1,31 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Payments; + +enum 6103 "Payment Status" +{ + Extensible = false; + + value(0; " ") + { + Caption = ' '; + } + value(1; Created) + { + Caption = 'Created'; + } + value(2; Sent) + { + Caption = 'Sent'; + } + value(3; Received) + { + Caption = 'Received'; + } + value(4; "Sending Error") + { + Caption = 'Sending Error'; + } +} diff --git a/Apps/W1/EDocument/app/src/Processing/EDocumentBackgroundJobs.Codeunit.al b/Apps/W1/EDocument/app/src/Processing/EDocumentBackgroundJobs.Codeunit.al index d688b52eec..33a62d2a04 100644 --- a/Apps/W1/EDocument/app/src/Processing/EDocumentBackgroundJobs.Codeunit.al +++ b/Apps/W1/EDocument/app/src/Processing/EDocumentBackgroundJobs.Codeunit.al @@ -6,6 +6,7 @@ namespace Microsoft.eServices.EDocument; using System.Telemetry; using System.Threading; +using Microsoft.eServices.EDocument.Integration.Payments; codeunit 6133 "E-Document Background Jobs" { @@ -104,6 +105,39 @@ codeunit 6133 "E-Document Background Jobs" Telemetry.LogMessage('0000LC5', EDocumentJobTelemetryLbl, Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, TelemetryScope::All, TelemetryDimensions); end; + procedure ScheduleRecurrentPaymentSyncJob(var EDocumentService: Record "E-Document Service") + var + JobQueueEntry: Record "Job Queue Entry"; + begin + if EDocumentService.Code = '' then + exit; + + if not this.IsRecurrentJobScheduledForAService(EDocumentService."Payment Sync Recurrent Job Id") then begin + JobQueueEntry.ScheduleRecurrentJobQueueEntryWithFrequency( + JobQueueEntry."Object Type to Run"::Codeunit, + Codeunit::"Sync Payments Job", + EDocumentService.RecordId, + EDocumentService."Payment Sync Min between runs", + EDocumentService."Payment Sync Start Time"); + + EDocumentService."Payment Sync Recurrent Job Id" := JobQueueEntry.ID; + EDocumentService.Modify(false); + + JobQueueEntry."Rerun Delay (sec.)" := 600; + JobQueueEntry."No. of Attempts to Run" := 0; + JobQueueEntry."Job Queue Category Code" := this.JobQueueCategoryTok; + JobQueueEntry.Modify(false); + end else begin + JobQueueEntry.Get(EDocumentService."Payment Sync Recurrent Job Id"); + JobQueueEntry."Starting Time" := EDocumentService."Payment Sync Start Time"; + JobQueueEntry."No. of Minutes between Runs" := EDocumentService."Payment Sync Min between runs"; + JobQueueEntry."No. of Attempts to Run" := 0; + JobQueueEntry.Modify(false); + if not JobQueueEntry.IsReadyToStart() then + JobQueueEntry.Restart(); + end; + end; + procedure HandleRecurrentBatchJob(var EDocumentService: Record "E-Document Service") begin if (EDocumentService."Use Batch Processing") and (EDocumentService."Batch Mode" = EDocumentService."Batch Mode"::Recurrent) then begin @@ -124,6 +158,16 @@ codeunit 6133 "E-Document Background Jobs" RemoveJob(EDocumentService."Import Recurrent Job Id"); end; + procedure HandleRecurrentPaymentSyncJob(var EDocumentService: Record "E-Document Service") + begin + if EDocumentService."Auto Sync Payments" then begin + EDocumentService.TestField("Payment Sync Start Time"); + EDocumentService.TestField("Payment Sync Min between runs"); + this.ScheduleRecurrentPaymentSyncJob(EDocumentService); + end else + this.RemoveJob(EDocumentService."Payment Sync Recurrent Job Id"); + end; + procedure RemoveJob(JobId: Guid) var JobQueueEntry: Record "Job Queue Entry"; diff --git a/Apps/W1/EDocument/app/src/Service/EDocumentService.Table.al b/Apps/W1/EDocument/app/src/Service/EDocumentService.Table.al index 7f152dd481..cd99201a77 100644 --- a/Apps/W1/EDocument/app/src/Service/EDocumentService.Table.al +++ b/Apps/W1/EDocument/app/src/Service/EDocumentService.Table.al @@ -6,6 +6,7 @@ namespace Microsoft.eServices.EDocument; using Microsoft.Finance.GeneralLedger.Journal; using System.Privacy; +using Microsoft.eServices.EDocument.Integration.Payments; using Microsoft.eServices.EDocument.Integration; using Microsoft.eServices.EDocument.Integration.Action; @@ -251,6 +252,50 @@ table 6103 "E-Document Service" ToolTip = 'Specifies the implementation of actions that can be performed after the document is sent to the service.'; DataClassification = SystemMetadata; } + field(29; "Payment Integration"; Enum "Payment Integration") + { + Caption = 'Payment Integration'; + ToolTip = 'Specifies the integration for sending and receiving payments from the service.'; + DataClassification = SystemMetadata; + } + field(30; "Calculate Payment VAT"; Boolean) + { + Caption = 'Calculate Payment VAT'; + ToolTip = 'Specifies whether the VAT amount should be calculated for the payment.'; + DataClassification = SystemMetadata; + } + field(31; "Auto Sync Payments"; Boolean) + { + Caption = 'Auto Sync Payments'; + ToolTip = 'Specifies whether the payments should be automatically synchronized with the service.'; + DataClassification = SystemMetadata; + + trigger OnValidate() + begin + this.EDocumentBackgroundJobs.HandleRecurrentPaymentSyncJob(Rec); + end; + } + field(32; "Payment Sync Start Time"; Time) + { + Caption = 'Sync Start Time'; + ToolTip = 'Specifies the time when the synchronization should start.'; + DataClassification = SystemMetadata; + NotBlank = true; + InitValue = 0T; + } + field(33; "Payment Sync Min Between Runs"; Integer) + { + Caption = 'Minutes between runs'; + ToolTip = 'Specifies the time between synchronization runs.'; + DataClassification = SystemMetadata; + InitValue = 1440; + } + field(34; "Payment Sync Recurrent Job Id"; Guid) + { + Caption = 'Sync Recurrent Job Id'; + ToolTip = 'Specifies the ID of the job that is used for the synchronization.'; + DataClassification = SystemMetadata; + } } keys { diff --git a/Apps/W1/EDocument/app/src/Service/EdocumentService.Page.al b/Apps/W1/EDocument/app/src/Service/EdocumentService.Page.al index 76d903cfa8..2427643df4 100644 --- a/Apps/W1/EDocument/app/src/Service/EdocumentService.Page.al +++ b/Apps/W1/EDocument/app/src/Service/EdocumentService.Page.al @@ -56,6 +56,14 @@ page 6133 "E-Document Service" { ToolTip = 'Specifies integration code for sent e-document actions.'; } + field("Payment Integration"; Rec."Payment Integration") + { + Visible = false; + } + field("Calculate Payment VAT"; Rec."Calculate Payment VAT") + { + Visible = false; + } field("Use Batch Processing"; Rec."Use Batch Processing") { ToolTip = 'Specifies if service uses batch processing for export.'; @@ -177,6 +185,26 @@ page 6133 "E-Document Service" } } } + group(Payment) + { + Caption = 'Payment Settings'; + Visible = false; + + field("Auto Sync Payments"; Rec."Auto Sync Payments") + { + } + group(PaymentSyncSettings) + { + ShowCaption = false; + Visible = Rec."Auto Sync Payments"; + field("Payment Sync Start Time"; Rec."Payment Sync Start Time") + { + } + field("Payment Sync Min between runs"; Rec."Payment Sync Min between runs") + { + } + } + } part(EDocumentDataExchDef; "E-Doc. Service Data Exch. Sub") { ApplicationArea = All; diff --git a/Apps/W1/EDocument/test/src/LibraryEDocument.Codeunit.al b/Apps/W1/EDocument/test/src/LibraryEDocument.Codeunit.al index f331edeb50..32dfade4d7 100644 --- a/Apps/W1/EDocument/test/src/LibraryEDocument.Codeunit.al +++ b/Apps/W1/EDocument/test/src/LibraryEDocument.Codeunit.al @@ -759,6 +759,18 @@ codeunit 139629 "Library - E-Document" end; #endif + procedure CreateTestPaymentServiceForEDoc(var EDocService: Record "E-Document Service"; ServiceIntegration: Enum "Service Integration"; PaymentIntegration: Enum "Payment Integration") + begin + if not EDocService.Get('TESTPAYMENT') then begin + EDocService.Init(); + EDocService.Code := 'TESTPAYMENT'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := ServiceIntegration; + EDocService."Payment Integration" := PaymentIntegration; + EDocService.Insert(true); + end; + end; + procedure CreateDirectMapping(var EDocMapping: Record "E-Doc. Mapping"; EDocService: Record "E-Document Service"; FindValue: Text; ReplaceValue: Text) begin CreateDirectMapping(EDocMapping, EDocService, FindValue, ReplaceValue, 0, 0); diff --git a/Apps/W1/EDocument/test/src/Mock/EDocPaymentImplState.Codeunit.al b/Apps/W1/EDocument/test/src/Mock/EDocPaymentImplState.Codeunit.al new file mode 100644 index 0000000000..7373225eb2 --- /dev/null +++ b/Apps/W1/EDocument/test/src/Mock/EDocPaymentImplState.Codeunit.al @@ -0,0 +1,28 @@ +codeunit 139503 "E-Doc. Payment Impl. State" +{ + EventSubscriberInstance = Manual; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc Payment Integration Mock", 'OnSendPayment', '', false, false)] + local procedure OnSendPayment(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + begin + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc Payment Integration Mock", 'OnReceivePayment', '', false, false)] + local procedure OnReceivePayment(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var PaymentsMetadata: Codeunit "Temp Blob List"; PaymentContext: Codeunit PaymentContext) + var + TempBlob: Codeunit "Temp Blob"; + PaymentText: Text; + OutStream: OutStream; + begin + PaymentText := '{"PaymentId": "123456"}'; + TempBlob.CreateOutStream(OutStream); + OutStream.Write(PaymentText); + PaymentsMetadata.Add(TempBlob); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc Payment Integration Mock", 'OnGetPaymentDetails', '', false, false)] + local procedure OnGetPaymentDetails(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; PaymentContext: Codeunit PaymentContext) + begin + PaymentContext.SetPaymentInformation(Today(), 1); + end; +} diff --git a/Apps/W1/EDocument/test/src/Mock/EDocPaymentIntegrationMock.Codeunit.al b/Apps/W1/EDocument/test/src/Mock/EDocPaymentIntegrationMock.Codeunit.al new file mode 100644 index 0000000000..52757ea95a --- /dev/null +++ b/Apps/W1/EDocument/test/src/Mock/EDocPaymentIntegrationMock.Codeunit.al @@ -0,0 +1,34 @@ +codeunit 139502 "E-Doc Payment Integration Mock" implements IDocumentPaymentHandler +{ + Access = Internal; + + procedure Send(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + begin + OnSendPayment(EDocument, EDocumentService, PaymentContext); + end; + + procedure Receive(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var PaymentsMetadata: Codeunit "Temp Blob List"; PaymentContext: Codeunit PaymentContext) + begin + OnReceivePayment(EDocument, EDocumentService, PaymentsMetadata, PaymentContext); + end; + + procedure GetDetails(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; PaymentContext: Codeunit PaymentContext) + begin + OnGetPaymentDetails(EDocument, EDocumentService, PaymentMetadata, PaymentContext); + end; + + [IntegrationEvent(false, false)] + procedure OnSendPayment(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentContext: Codeunit PaymentContext) + begin + end; + + [IntegrationEvent(false, false)] + procedure OnReceivePayment(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; var PaymentsMetadata: Codeunit "Temp Blob List"; PaymentContext: Codeunit PaymentContext) + begin + end; + + [IntegrationEvent(false, false)] + procedure OnGetPaymentDetails(var EDocument: Record "E-Document"; var EDocumentService: Record "E-Document Service"; PaymentMetadata: Codeunit "Temp Blob"; PaymentContext: Codeunit PaymentContext) + begin + end; +} diff --git a/Apps/W1/EDocument/test/src/Mock/EDocPaymentIntegrationMock.EnumExt.al b/Apps/W1/EDocument/test/src/Mock/EDocPaymentIntegrationMock.EnumExt.al new file mode 100644 index 0000000000..d80d5cf13f --- /dev/null +++ b/Apps/W1/EDocument/test/src/Mock/EDocPaymentIntegrationMock.EnumExt.al @@ -0,0 +1,8 @@ +enumextension 139501 "E-Doc Payment Integration Mock" extends "Payment Integration" +{ + value(139500; Mock) + { + Caption = 'Mock'; + Implementation = IDocumentPaymentHandler = "E-Doc Payment Integration Mock"; + } +} diff --git a/Apps/W1/EDocument/test/src/Payments/EDocPaymentTest.Codeunit.al b/Apps/W1/EDocument/test/src/Payments/EDocPaymentTest.Codeunit.al new file mode 100644 index 0000000000..1cba85a023 --- /dev/null +++ b/Apps/W1/EDocument/test/src/Payments/EDocPaymentTest.Codeunit.al @@ -0,0 +1,366 @@ +codeunit 139501 "E-Doc. Payment Test" +{ + Subtype = Test; + TestPermissions = Disabled; + + var + Customer: Record "Customer"; + EDocService: Record "E-Document Service"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + Vendor: Record Vendor; + Assert: Codeunit "Assert"; + EDocExportMgt: Codeunit "E-Doc. Export"; + EDocImplState: Codeunit "E-Doc. Impl. State"; + EDocLogTest: Codeunit "E-Doc Log Test"; + EDocPaymentImplState: Codeunit "E-Doc. Payment Impl. State"; + LibraryEDoc: Codeunit "Library - E-Document"; + LibraryPurchase: Codeunit "Library - Purchase"; + LibraryRandom: Codeunit "Library - Random"; + LibraryInventory: Codeunit "Library - Inventory"; + PurchOrderTestBuffer: Codeunit "E-Doc. Test Buffer"; + + //Create partial payment + [Test] + procedure CreateOutgoingPartialPaymentTest() + var + EDocument: Record "E-Document"; + EDocServicePage: TestPage "E-Document Service"; + PaymentAmount: Decimal; + i: Integer; + begin + // [FEATURE] [E-Document] + // [SCENARIO] Create partial payment for received E-Document + this.Initialize(); + + // [GIVEN] Create E-Document service setup + this.LibraryEDoc.CreateTestPaymentServiceForEDoc(this.EDocService, Enum::"Service Integration"::Mock, Enum::"Payment Integration"::Mock); + this.SetDefaultEDocServiceValues(this.EDocService, false); + BindSubscription(this.EDocImplState); + + // [GIVEN] Create received E-Document + this.LibraryPurchase.CreateVendorWithAddress(this.Vendor); + this.Vendor."Receive E-Document To" := this.Vendor."Receive E-Document To"::"Purchase Invoice"; + this.Vendor.Modify(); + this.LibraryPurchase.CreatePurchHeader(this.PurchaseHeader, this.PurchaseHeader."Document Type"::Invoice, this.Vendor."No."); + + for i := 1 to 3 do begin + this.LibraryPurchase.CreatePurchaseLine(this.PurchaseLine, this.PurchaseHeader, this.PurchaseLine.Type::Item, this.LibraryInventory.CreateItemNo(), this.LibraryRandom.RandInt(100)); + this.PurchaseLine.Validate("Direct Unit Cost", this.LibraryRandom.RandDecInRange(1, 100, 2)); + this.PurchaseLine.Modify(true); + end; + + this.PurchOrderTestBuffer.ClearTempVariables(); + this.PurchOrderTestBuffer.AddPurchaseDocToTemp(this.PurchaseHeader); + + // [WHEN] Running Receive + EDocServicePage.OpenView(); + EDocServicePage.Filter.SetFilter(Code, this.EDocService.Code); + EDocServicePage.Receive.Invoke(); + UnbindSubscription(this.EDocImplState); + + // [THEN] Purchase invoice is created with corresponding values + EDocument.FindLast(); + + // [THEN] Check that Paid Amount for the document is 0 + this.CheckPaidAmount(EDocument, 0); + + // [GIVEN] Create partial payment for E-Document + PaymentAmount := 100; + this.CreateEDocumentPayment(EDocument, PaymentAmount); + + // [THEN] Check that Paid Amount for the document is updated + this.CheckPaidAmount(EDocument, PaymentAmount); + + // [THEN] Check that Payments Direction is correct + this.CheckPaymentDirection(EDocument, Enum::"E-Document Direction"::Outgoing); + end; + + //Create full payment + [Test] + procedure CreateOutgoingFullPaymentTest() + var + EDocument: Record "E-Document"; + EDocServicePage: TestPage "E-Document Service"; + i: Integer; + begin + // [FEATURE] [E-Document] + // [SCENARIO] Send payment for received E-Document + this.Initialize(); + + // [GIVEN] Create E-Document service setup + this.LibraryEDoc.CreateTestPaymentServiceForEDoc(this.EDocService, Enum::"Service Integration"::Mock, Enum::"Payment Integration"::Mock); + this.SetDefaultEDocServiceValues(this.EDocService, false); + BindSubscription(this.EDocImplState); + + // [GIVEN] Create received E-Document + this.LibraryPurchase.CreateVendorWithAddress(this.Vendor); + this.Vendor."Receive E-Document To" := this.Vendor."Receive E-Document To"::"Purchase Invoice"; + this.Vendor.Modify(); + this.LibraryPurchase.CreatePurchHeader(this.PurchaseHeader, this.PurchaseHeader."Document Type"::Invoice, this.Vendor."No."); + + for i := 1 to 3 do begin + this.LibraryPurchase.CreatePurchaseLine(this.PurchaseLine, this.PurchaseHeader, this.PurchaseLine.Type::Item, this.LibraryInventory.CreateItemNo(), this.LibraryRandom.RandInt(100)); + this.PurchaseLine.Validate("Direct Unit Cost", this.LibraryRandom.RandDecInRange(1, 100, 2)); + this.PurchaseLine.Modify(true); + end; + + this.PurchOrderTestBuffer.ClearTempVariables(); + this.PurchOrderTestBuffer.AddPurchaseDocToTemp(this.PurchaseHeader); + + // [WHEN] Running Receive + EDocServicePage.OpenView(); + EDocServicePage.Filter.SetFilter(Code, this.EDocService.Code); + EDocServicePage.Receive.Invoke(); + UnbindSubscription(this.EDocImplState); + + // [THEN] Purchase invoice is created with corresponding values + EDocument.FindLast(); + + // [THEN] Check that Paid Amount for the document is 0 + this.CheckPaidAmount(EDocument, 0); + + // [GIVEN] Create partial payment for E-Document + this.CreateFullEDocumentPayments(EDocument); + + // [THEN] Check that Paid Amount for the document is updated + this.CheckPaidAmount(EDocument, EDocument."Amount Incl. VAT"); + + // [THEN] Check that Payments Direction is correct + this.CheckPaymentDirection(EDocument, Enum::"E-Document Direction"::Outgoing); + end; + + //Create partial payment + [Test] + procedure CreateOutgoingPaymentWithVATCalculationTest() + var + EDocument: Record "E-Document"; + EDocServicePage: TestPage "E-Document Service"; + PaymentAmount: Decimal; + i: Integer; + begin + // [FEATURE] [E-Document] + // [SCENARIO] Create partial payment for received E-Document + this.Initialize(); + + // [GIVEN] Create E-Document service setup and set VAT calculation + this.LibraryEDoc.CreateTestPaymentServiceForEDoc(this.EDocService, Enum::"Service Integration"::Mock, Enum::"Payment Integration"::Mock); + this.SetDefaultEDocServiceValues(this.EDocService, true); + BindSubscription(this.EDocImplState); + + // [GIVEN] Create received E-Document + this.LibraryPurchase.CreateVendorWithAddress(this.Vendor); + this.Vendor."Receive E-Document To" := this.Vendor."Receive E-Document To"::"Purchase Invoice"; + this.Vendor.Modify(); + this.LibraryPurchase.CreatePurchHeader(this.PurchaseHeader, this.PurchaseHeader."Document Type"::Invoice, this.Vendor."No."); + + for i := 1 to 3 do begin + this.LibraryPurchase.CreatePurchaseLine(this.PurchaseLine, this.PurchaseHeader, this.PurchaseLine.Type::Item, this.LibraryInventory.CreateItemNo(), this.LibraryRandom.RandInt(100)); + this.PurchaseLine.Validate("Direct Unit Cost", this.LibraryRandom.RandDecInRange(1, 100, 2)); + this.PurchaseLine.Modify(true); + end; + + this.PurchOrderTestBuffer.ClearTempVariables(); + this.PurchOrderTestBuffer.AddPurchaseDocToTemp(this.PurchaseHeader); + + // [WHEN] Running Receive + EDocServicePage.OpenView(); + EDocServicePage.Filter.SetFilter(Code, this.EDocService.Code); + EDocServicePage.Receive.Invoke(); + UnbindSubscription(this.EDocImplState); + + // [THEN] Purchase invoice is created with corresponding values + EDocument.FindLast(); + + // [THEN] Check that Paid Amount for the document is 0 + this.CheckPaidAmount(EDocument, 0); + + // [GIVEN] Create partial payment for E-Document + PaymentAmount := 100; + this.CreateEDocumentPayment(EDocument, PaymentAmount); + + // [THEN] Check that Paid Amount for the document is updated + this.CheckPaidAmount(EDocument, PaymentAmount); + + // [THEN] Check that VAT amount is calculated + this.CheckPaymentVATAmount(EDocument); + end; + + //Create incoming partial payment + [Test] + procedure CreateIncomingPartialPaymentTest() + var + EDocument: Record "E-Document"; + PaymentAmount: Decimal; + begin + // [FEATURE] [E-Document] + // [SCENARIO] Receive payment for sent E-Document + this.Initialize(); + + // [GIVEN] Setup E-Document service to send E-Document and receive payment + this.LibraryEDoc.SetupStandardVAT(); + this.LibraryEDoc.SetupStandardSalesScenario(this.Customer, this.EDocService, Enum::"E-Document Format"::"PEPPOL BIS 3.0", Enum::"Service Integration"::Mock); + this.EDocService."Payment Integration" := Enum::"Payment Integration"::Mock; + this.EDocService."Calculate Payment VAT" := false; + this.EDocService.Modify(); + + // [WHEN] Create and post sales invoice to create E-Document + this.LibraryEDoc.PostInvoice(this.Customer); + EDocument.FindLast(); + + // [WHEN] Export EDocument + BindSubscription(this.EDocLogTest); + this.EDocExportMgt.ExportEDocument(EDocument, this.EDocService); + UnbindSubscription(this.EDocLogTest); + + // [THEN] Check that Paid Amount for the document is 0 + this.CheckPaidAmount(EDocument, 0); + + // [WHEN] Receive payment for E-Document + PaymentAmount := 1; + this.CreateEDocumentPayment(EDocument, PaymentAmount); + + // [THEN] Check that Paid Amount for the document is updated + this.CheckPaidAmount(EDocument, PaymentAmount); + + // [THEN] Check that Payments Direction is correct + this.CheckPaymentDirection(EDocument, Enum::"E-Document Direction"::Incoming); + end; + + //Receive incoming payment + [HandlerFunctions('EDocumentServiceSelectionHandler')] + [Test] + procedure ReceivePaymentTest() + var + EDocument: Record "E-Document"; + begin + // [FEATURE] [E-Document] + // [SCENARIO] Receive payment for sent E-Document + this.Initialize(); + + // [GIVEN] Setup E-Document service to send E-Document and receive payment + this.LibraryEDoc.SetupStandardVAT(); + this.LibraryEDoc.SetupStandardSalesScenario(this.Customer, this.EDocService, Enum::"E-Document Format"::"PEPPOL BIS 3.0", Enum::"Service Integration"::Mock); + this.EDocService."Payment Integration" := Enum::"Payment Integration"::Mock; + this.EDocService."Calculate Payment VAT" := false; + this.EDocService.Modify(); + + // [WHEN] Create and post sales invoice to create E-Document + this.LibraryEDoc.PostInvoice(this.Customer); + EDocument.FindLast(); + + // [WHEN] Export EDocument + BindSubscription(this.EDocLogTest); + this.EDocExportMgt.ExportEDocument(EDocument, this.EDocService); + UnbindSubscription(this.EDocLogTest); + + // [THEN] Check that Paid Amount for the document is 0 + this.CheckPaidAmount(EDocument, 0); + + // [WHEN] Receive payment for E-Document + this.ReceivePayment(EDocument); + + // [THEN] Check that Paid Amount for the document is updated + this.CheckPaidAmount(EDocument, 1); + + // [THEN] Check that Payments Direction is correct + this.CheckPaymentDirection(EDocument, Enum::"E-Document Direction"::Incoming); + end; + + local procedure Initialize() + var + EDocument: Record "E-Document"; + EDocumentPayment: Record "E-Document Payment"; + begin + Clear(this.EDocPaymentImplState); + EDocument.DeleteAll(false); + EDocumentPayment.DeleteAll(false); + + Clear(this.PurchaseHeader); + this.PurchaseHeader.DeleteAll(false); + + Clear(this.EDocService); + end; + + local procedure SetDefaultEDocServiceValues(var EDocService: Record "E-Document Service"; CalculateVAT: Boolean) + begin + EDocService."Lookup Account Mapping" := false; + EDocService."Lookup Item GTIN" := false; + EDocService."Lookup Item Reference" := false; + EDocService."Resolve Unit Of Measure" := false; + EDocService."Validate Line Discount" := false; + EDocService."Verify Totals" := false; + EDocService."Use Batch Processing" := false; + EDocService."Calculate Payment VAT" := CalculateVAT; + EDocService.Modify(false); + end; + + local procedure CreateEDocumentPayment(EDocument: Record "E-Document"; PaymentAmount: Decimal) + begin + this.CreateEDocumentPaymentRecord(EDocument."Entry No", PaymentAmount) + end; + + local procedure CreateFullEDocumentPayments(EDocument: Record "E-Document") + begin + this.CreateEDocumentPaymentRecord(EDocument."Entry No", 100); + this.CreateEDocumentPaymentRecord(EDocument."Entry No", EDocument."Amount Incl. VAT" - 100) + end; + + local procedure CreateEDocumentPaymentRecord(EDocumentEntryNo: Integer; Amount: Decimal) + var + EDocumentPayment: Record "E-Document Payment"; + begin + EDocumentPayment.Init(); + EDocumentPayment.Validate("E-Document Entry No.", EDocumentEntryNo); + EDocumentPayment.Date := Today(); + EDocumentPayment.Validate(Amount, Amount); + EDocumentPayment.Insert(true); + end; + + local procedure ReceivePayment(EDocument: Record "E-Document") + var + EDocumentPage: TestPage "E-Document"; + begin + BindSubscription(this.EDocPaymentImplState); + EDocumentPage.OpenView(); + EDocumentPage.GoToRecord(EDocument); + EDocumentPage.ReceivePayments.Invoke(); + UnbindSubscription(this.EDocPaymentImplState); + end; + + local procedure CheckPaidAmount(EDocument: Record "E-Document"; ExpectedPaidAmount: Decimal) + begin + EDocument.CalcFields("Paid Amount"); + this.Assert.AreEqual(ExpectedPaidAmount, EDocument."Paid Amount", 'Paid Amount is not updated.'); + end; + + local procedure CheckPaymentVATAmount(EDocument: Record "E-Document") + var + EDocumentPayment: Record "E-Document Payment"; + begin + EDocumentPayment.SetRange("E-Document Entry No.", EDocument."Entry No"); + if EDocumentPayment.FindSet() then + repeat + this.Assert.AreNotEqual(0, EDocumentPayment."VAT Base", 'Payment Base Amount is not calculated.'); + this.Assert.AreNotEqual(0, EDocumentPayment."VAT Amount", 'Payment VAT Amount is not calculated.'); + until EDocumentPayment.Next() = 0; + end; + + local procedure CheckPaymentDirection(EDocument: Record "E-Document"; ExpectedDirection: Enum "E-Document Direction") + var + EDocumentPayment: Record "E-Document Payment"; + begin + EDocumentPayment.SetRange("E-Document Entry No.", EDocument."Entry No"); + if EDocumentPayment.FindSet() then + repeat + this.Assert.AreEqual(ExpectedDirection, EDocumentPayment.Direction, 'Payment Direction is not correct.'); + until EDocumentPayment.Next() = 0; + end; + + [ModalPageHandler] + procedure EDocumentServiceSelectionHandler(var EDocumentServices: TestPage "E-Document Services") + begin + EDocumentServices.GoToRecord(this.EDocService); + EDocumentServices.OK().Invoke(); + end; +}