[FR E-Reporting] Add payment and invoice lifecycle messages - #10437
[FR E-Reporting] Add payment and invoice lifecycle messages#10437Milica Đukić (djukicmilica) wants to merge 53 commits into
Conversation
Pull request was closed
| field(ReasonCode; ReasonCode) | ||
| { | ||
| ApplicationArea = Basic, Suite; | ||
| Caption = 'Reason Code'; | ||
| NotBlank = true; | ||
| ToolTip = 'Specifies the code that identifies why the invoice is refused.'; | ||
| } | ||
| field(ReasonDescription; ReasonDescription) | ||
| { | ||
| ApplicationArea = Basic, Suite; | ||
| Caption = 'Reason Description'; | ||
| MultiLine = true; | ||
| NotBlank = true; | ||
| ToolTip = 'Specifies why the invoice is refused.'; | ||
| } |
There was a problem hiding this comment.
The refusal dialog marks both inputs with NotBlank = true, so they are intended to be required before sending a refusal, but neither editable field sets ShowMandatory = true. That leaves the dialog presenting both fields as optional instead of showing the required-field marker on a blocking workflow.
| field(ReasonCode; ReasonCode) | |
| { | |
| ApplicationArea = Basic, Suite; | |
| Caption = 'Reason Code'; | |
| NotBlank = true; | |
| ToolTip = 'Specifies the code that identifies why the invoice is refused.'; | |
| } | |
| field(ReasonDescription; ReasonDescription) | |
| { | |
| ApplicationArea = Basic, Suite; | |
| Caption = 'Reason Description'; | |
| MultiLine = true; | |
| NotBlank = true; | |
| ToolTip = 'Specifies why the invoice is refused.'; | |
| } | |
| field(ReasonCode; ReasonCode) | |
| { | |
| ApplicationArea = Basic, Suite; | |
| Caption = 'Reason Code'; | |
| NotBlank = true; | |
| ShowMandatory = true; | |
| ToolTip = 'Specifies the code that identifies why the invoice is refused.'; | |
| } | |
| field(ReasonDescription; ReasonDescription) | |
| { | |
| ApplicationArea = Basic, Suite; | |
| Caption = 'Reason Description'; | |
| MultiLine = true; | |
| NotBlank = true; | |
| ShowMandatory = true; | |
| ToolTip = 'Specifies why the invoice is refused.'; | |
| } |
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| #endif | ||
| codeunit "E-Doc. Attachment Processor" = X, | ||
| codeunit "E-Doc. Hist. Line Data Loader" = X, | ||
| codeunit "E-Doc. Message Context" = X, |
There was a problem hiding this comment.
The new message-response workflow omits execute permission for codeunit "E-Doc. Message Resp. Runner" (and likewise "E-Doc. Message Send Runner", "E-Doc. Message Send Job", "E-Doc. Message Response Job" are absent from the same list). "E-Doc. Message Response Job" calls Codeunit.Run(Codeunit::"E-Doc. Message Resp. Runner", ...), but the assignable E-Document permission chain never grants X on that runner, so a non-SUPER user can reach this path and then fail when background response polling continues.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
codeunit "E-Doc. Message Context" = X,
codeunit "E-Doc. Message Mgt." = X,
codeunit "E-Doc. Message Resp. Runner" = X,
codeunit "E-Doc. Msg. Transport Default" = X,
codeunit "E-Doc. Payment Occ. Dispatcher" = X,
codeunit "E-Doc. Payment Occ. Runner" = X,Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
|
The new "E-Doc. Payment Occurrence" table stores dependent rows keyed by "E-Document Entry No.", but the owning "E-Document" cleanup path still deletes messages and external references without deleting payment occurrences. Deleting an E-Document will therefore leave orphan payment-occurrence rows behind, because TableRelation does not cascade deletes. Knowledge: Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6 |
|
|
||
| Permissions = | ||
| codeunit "E-Document Message API" = X, | ||
| codeunit "E-Doc. Message Response Job" = X, |
There was a problem hiding this comment.
E-Doc. Core - User now grants execute permission on internal job/runner codeunits (E-Doc. Message Response Job, E-Doc. Message Send Job, E-Doc. Message Send Runner, E-Doc. Payment Occ. Dispatcher, E-Doc. Payment Occ. Runner) whose OnRun triggers directly poll/send child messages or dispatch payment occurrences under inherent permissions. Access = Internal is not a runtime authorization boundary, so any runtime caller running under this permission set can invoke those codeunits directly and bypass the intended page/API flow.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| tabledata "E-Doc. Data Storage" = imd, | ||
| tabledata "E-Document Integration Log" = imd, | ||
| tabledata "E-Document Message" = imd, | ||
| tabledata "E-Doc. Payment Occurrence" = rimd, |
There was a problem hiding this comment.
E-Doc. Core - User gives direct rimd rights to E-Doc. Payment Occurrence and E-Doc. External Reference. Those tables hold immutable payment-lifecycle history and the service-specific external-document correlation map used by incoming-message matching, and this change only consumes them through codeunits and read-only UI. Direct rights let assignees modify or delete that state through any available client or API surface, bypassing the mediated processing path.
Knowledge:
- microsoft/knowledge/security/indirect-permissions-for-elevated-access.md
- microsoft/knowledge/security/internal-access-is-not-a-security-boundary.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
f2d4900
| if Codeunit.Run(Codeunit::"E-Doc. Message Resp. Runner", EDocumentMessage) then | ||
| exit; | ||
|
|
||
| LastErrorText := GetLastErrorText(); |
There was a problem hiding this comment.
The response-job wrapper rethrow creates ErrorInfo directly from parameterless GetLastErrorText(). That makes raw connector text the ErrorInfo.Message telemetry payload again instead of using a stable, deliberately classified message.
Knowledge:
- microsoft/knowledge/privacy/errorinfo-telemetry-classification-and-errortype.md
- microsoft/knowledge/privacy/getlasterrortext-customer-content-in-errors.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| if Codeunit.Run(Codeunit::"E-Doc. Message Send Runner", EDocumentMessage) then | ||
| exit; | ||
|
|
||
| LastErrorText := GetLastErrorText(); |
There was a problem hiding this comment.
The job-wrapper rethrow creates ErrorInfo directly from parameterless GetLastErrorText(). That makes raw connector text the ErrorInfo.Message telemetry payload again instead of using a stable, deliberately classified message.
Knowledge:
- microsoft/knowledge/privacy/errorinfo-telemetry-classification-and-errortype.md
- microsoft/knowledge/privacy/getlasterrortext-customer-content-in-errors.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| exit(EDocumentBackgroundJobs.TryScheduleMessageResponse(EDocMessage)); | ||
| end; | ||
|
|
||
| local procedure SetMessageSchedulingError(var EDocMessage: Record "E-Document Message"; ErrorStatus: Enum "E-Doc. Message Status") |
There was a problem hiding this comment.
SetMessageSchedulingError silently reduces child-message queueing failures to record state only. The QueueMessage, RetryMessage, and response-reschedule paths increment Retry Count and persist Last Error, but they emit no error telemetry, unlike the later job-execution failures in EDocMessageSendJob/EDocMessageResponseJob. Add an Error-level telemetry event here, including message entry number, message type, service, retry count, and whether send or response scheduling failed, so scheduling failures are visible in environment telemetry.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| exit; | ||
| end; | ||
|
|
||
| LastErrorText := GetLastErrorText(); |
There was a problem hiding this comment.
ProcessPaymentOccurrence records background payment-occurrence publication failures only on the E-Doc. Payment Occurrence row. When Codeunit.Run("E-Doc. Payment Occ. Runner") fails, the code updates Status, Retry Count, Next Attempt At, and Last Error, but emits no error telemetry, so recurring failures are invisible in tenant monitoring. Log an Error-level telemetry event here with the occurrence entry number, parent E-Document entry number, occurrence type, and retry count.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
Why
French electronic invoicing requires lifecycle communication beyond the parent E-Document processing status. Eligible outgoing invoice payment applications and reversals must produce collected or negative-collected messages, buyers must be able to accept or refuse incoming invoices, and platform lifecycle messages must be correlated without overwriting the invoice's own state.
This change models these exchanges as child E-Document messages and adds reusable transport, queuing, response polling, payment-occurrence, payload, and external-reference infrastructure to E-Document Core.
Summary
IMessageSenderandIMessageResponseHandlerintegration contracts, payload persistence, processing statuses, queued background delivery, response polling, and retry support.CollectedandNegative Collectedmessages for payment applications and unapplications on approved or cleared Peppol BIS 3.0 FR and Factur-X FR documents with reportable unrealized VAT.AcceptedandRefusedmessages for incoming purchase invoices, with optional refusal reasons and validation that only one buyer response can be sent.Submitted,Accepted,Refused, andTechnical Rejectedstatuses, payload retention, transition validation, and deduplication.Fixes AB#637593