Skip to content

[FR E-Reporting] Add payment and invoice lifecycle messages - #10437

Open
Milica Đukić (djukicmilica) wants to merge 53 commits into
mainfrom
feature/fr-collected-refused-messages
Open

[FR E-Reporting] Add payment and invoice lifecycle messages#10437
Milica Đukić (djukicmilica) wants to merge 53 commits into
mainfrom
feature/fr-collected-refused-messages

Conversation

@djukicmilica

@djukicmilica Milica Đukić (djukicmilica) commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

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

  • Added a public E-Document message API with IMessageSender and IMessageResponseHandler integration contracts, payload persistence, processing statuses, queued background delivery, response polling, and retry support.
  • Added generic applied and reversed E-Document payment occurrences with replay protection and original-occurrence linkage.
  • Added French Collected and Negative Collected messages for payment applications and unapplications on approved or cleared Peppol BIS 3.0 FR and Factur-X FR documents with reportable unrealized VAT.
  • Added proportional VAT allocation by rate and category, deterministic rounding, reversal of the original VAT breakdown, and frozen sender-platform and invoice context for PPF/CDV lifecycle payloads.
  • Added buyer Accepted and Refused messages for incoming purchase invoices, with optional refusal reasons and validation that only one buyer response can be sent.
  • Added incoming lifecycle-message correlation by external document and message IDs, normalization of Submitted, Accepted, Refused, and Technical Rejected statuses, payload retention, transition validation, and deduplication.
  • Added PPF/CDV profile validation, French service setup fields, lifecycle actions and history pages, and focused Core and French integration tests covering transport, payments, buyer responses, profiles, VAT allocation, and incoming messages.
  • Fixed E-Document test object ID collisions and Factur-X test variable shadowing that blocked BCApps validation against NAV.

Fixes AB#637593

@djukicmilica
Milica Đukić (djukicmilica) requested a review from a team as a code owner August 20, 2026 09:29
@github-actions github-actions Bot added AL: Apps (W1) Add-on apps for W1 Team: Other GitHub request for other area than SCM, Finance or Integration Ownership: Needs Review Ownership is Other, low confidence, or needs manual correction labels Aug 20, 2026
Comment thread src/Apps/FR/EDocument_FR/EReportingFR/test/src/FREInvoiceMessageTests.Codeunit.al Outdated
Comment thread src/Apps/W1/EDocument/App/src/Processing/Message/EDocMessageMgt.Codeunit.al Outdated
Comment thread src/Apps/W1/EDocument/App/src/Processing/Message/EDocMessageSendJob.Codeunit.al Outdated
@djukicmilica Milica Đukić (djukicmilica) changed the title Add French collected and refused E-Document messages [FR E-Reporting] Add payment and invoice lifecycle messages Aug 20, 2026
ventselartur
ventselartur previously approved these changes Sep 8, 2026
Comment on lines +16 to +30
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.';
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Accessibility}$

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.

Suggested change
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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ AppSource}$

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

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

$\textbf{🟠\ High\ Severity\ —\ Data\ Modeling}$

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Security}$

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Security}$

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:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6

if Codeunit.Run(Codeunit::"E-Doc. Message Resp. Runner", EDocumentMessage) then
exit;

LastErrorText := GetLastErrorText();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Privacy}$

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:

👍 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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Privacy}$

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:

👍 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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Telemetry}$

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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Telemetry}$

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AL: Apps (W1) Add-on apps for W1 Ownership: Needs Review Ownership is Other, low confidence, or needs manual correction Team: Other GitHub request for other area than SCM, Finance or Integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants