Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.Formats;

using Microsoft.eServices.EDocument;

permissionset 10988 "E-Reporting FR User"
{
Assignable = true;
Caption = 'E-Reporting FR - User';

IncludedPermissionSets = "E-Doc. Core - User";

Permissions =
table "FR E-Invoice Message" = X,
tabledata "FR E-Invoice Message" = 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}$

The French user role grants RIMD directly on "FR E-Invoice Message", even though the table is consumed through a read-only page and message-management codeunits. That lets assigned users bypass the lifecycle workflow and alter or delete refusal reasons, received message metadata, and sent-status records directly through any client surface that honors table permissions. This table should be readable directly at most, with writes reserved for code-mediated paths.

Knowledge:

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

table "FR E-Invoice Message VAT" = X,
tabledata "FR E-Invoice Message VAT" = R,
codeunit "FR E-Invoice Message Mgt." = X,
codeunit "FR E-Invoice Message Builder" = X,
codeunit "FR E-Invoice Profile Validator" = X,
codeunit "FR E-Invoice Message API" = X,
page "FR E-Invoice Refusal Dialog" = X,
page "FR E-Invoice Messages" = X;
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ codeunit 10978 "CII XML Builder"

AddSellerTradeParty(AgreementElement, CompanyInformation);
AddBuyerTradeParty(AgreementElement, SourceDocumentHeader);
AddInvoiceReferencedDocument(AgreementElement, SourceDocumentHeader);

// BT-13 Purchase order reference
if FREDocHelpers.FindFieldByName(SourceDocumentHeader, 'Order No.', FieldRefVar) then
Expand All @@ -175,6 +176,35 @@ codeunit 10978 "CII XML Builder"
TransactionElement.Add(AgreementElement);
end;

local procedure AddInvoiceReferencedDocument(var AgreementElement: XmlElement; var SourceDocumentHeader: RecordRef)
var
SalesInvoiceHeader: Record "Sales Invoice Header";
FREDocHelpers: Codeunit "EDoc. Helpers";
AppliesToDocumentNoFieldRef: FieldRef;
DateStringElement: XmlElement;
FormattedIssueDateElement: XmlElement;
InvoiceReferenceElement: XmlElement;
AppliesToDocumentNo: Code[20];
begin
if SourceDocumentHeader.Number() <> Database::"Sales Cr.Memo Header" then
exit;
if not FREDocHelpers.FindFieldByName(SourceDocumentHeader, 'Applies-to Doc. No.', AppliesToDocumentNoFieldRef) then
exit;

AppliesToDocumentNo := AppliesToDocumentNoFieldRef.Value();
if (AppliesToDocumentNo = '') or not SalesInvoiceHeader.Get(AppliesToDocumentNo) then
exit;
Comment on lines +194 to +196

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\ —\ Performance}$

AddInvoiceReferencedDocument loads the full Sales Invoice Header row even though it only reads "Document Date". This is the partial-record anti-pattern on a wide posted-document table; call SetLoadFields before Get so the credit-memo reference lookup does not transfer unused columns.

Suggested change
AppliesToDocumentNo := AppliesToDocumentNoFieldRef.Value();
if (AppliesToDocumentNo = '') or not SalesInvoiceHeader.Get(AppliesToDocumentNo) then
exit;
AppliesToDocumentNo := AppliesToDocumentNoFieldRef.Value();
SalesInvoiceHeader.SetLoadFields("Document Date");
if (AppliesToDocumentNo = '') or not SalesInvoiceHeader.Get(AppliesToDocumentNo) then
exit;

Knowledge:

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


InvoiceReferenceElement := XmlElement.Create('InvoiceReferencedDocument', RamNamespaceTok);
InvoiceReferenceElement.Add(XmlElement.Create('IssuerAssignedID', RamNamespaceTok, AppliesToDocumentNo));
FormattedIssueDateElement := XmlElement.Create('FormattedIssueDateTime', RamNamespaceTok);
DateStringElement := XmlElement.Create('DateTimeString', QdtNamespaceTok, FormatDate(SalesInvoiceHeader."Document Date"));
DateStringElement.SetAttribute('format', '102');
FormattedIssueDateElement.Add(DateStringElement);
InvoiceReferenceElement.Add(FormattedIssueDateElement);
AgreementElement.Add(InvoiceReferenceElement);
end;

local procedure AddSellerTradeParty(var AgreementElement: XmlElement; CompanyInformation: Record "Company Information")
var
SellerElement: XmlElement;
Expand Down Expand Up @@ -268,6 +298,31 @@ codeunit 10978 "CII XML Builder"
AgreementElement.Add(BuyerElement);
end;

procedure TryGetBuyerElectronicAddress(Customer: Record Customer; var BuyerElectronicAddress: Text): Boolean
var
VATRegistrationNo: Text;
begin
if Customer."FR Electronic Address" <> '' then begin
BuyerElectronicAddress := Customer."FR Electronic Address";
exit(true);
end;

if Customer."Registration Number" <> '' then begin
BuyerElectronicAddress := CopyStr(Customer."Registration Number", 1, 14);
exit(true);
end;

VATRegistrationNo := UpperCase(DelChr(Customer."VAT Registration No.", '=', ' '));
if (StrLen(VATRegistrationNo) = 13) and (CopyStr(VATRegistrationNo, 1, 2) = 'FR') and
(DelChr(CopyStr(VATRegistrationNo, 3), '=', '0123456789') = '')
then begin
BuyerElectronicAddress := CopyStr(VATRegistrationNo, 5, 9);
exit(true);
end;

exit(false);
end;

local procedure GetHeaderFieldText(var SourceDocumentHeader: RecordRef; PrimaryFieldName: Text; FallbackFieldName: Text): Text
var
FREDocHelpers: Codeunit "EDoc. Helpers";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// ------------------------------------------------------------------------------------------------
// 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.Formats;

using Microsoft.eServices.EDocument;

table 10970 "FR E-Invoice Message"
{
Caption = 'FR E-Invoice Message';
DataClassification = CustomerContent;
InherentEntitlements = X;
InherentPermissions = X;
ReplicateData = false;

fields
{
field(1; "Entry No."; Integer)
{
AutoIncrement = true;
Caption = 'Entry No.';
DataClassification = SystemMetadata;
}
field(2; "E-Document Entry No."; Integer)
{
Caption = 'E-Document Entry No.';
DataClassification = SystemMetadata;
TableRelation = "E-Document"."Entry No";
}
field(3; Type; Enum "FR E-Invoice Message Type")
{
Caption = 'Type';
DataClassification = SystemMetadata;
}
field(4; "Source Occurrence ID"; Guid)
{
Caption = 'Source Occurrence ID';
DataClassification = SystemMetadata;
}
field(5; "Original Entry No."; Integer)
{
Caption = 'Original Entry No.';
DataClassification = SystemMetadata;
TableRelation = "FR E-Invoice Message"."Entry No.";
}
field(6; Amount; Decimal)
{
AutoFormatExpression = Rec."Currency Code";
AutoFormatType = 1;
Caption = 'Amount';
DataClassification = CustomerContent;
}
field(7; "Currency Code"; Code[10])
{
Caption = 'Currency Code';
DataClassification = CustomerContent;
}
field(8; "Event Date"; Date)
{
Caption = 'Event Date';
DataClassification = CustomerContent;
}
field(9; "Detailed Ledger Entry No."; Integer)
{
Caption = 'Detailed Ledger Entry No.';
DataClassification = SystemMetadata;
}
field(10; "Reason Code"; Code[20])
{
Caption = 'Reason Code';
DataClassification = CustomerContent;
}
field(11; "Reason Description"; Text[500])
{
Caption = 'Reason Description';
DataClassification = CustomerContent;
}
field(12; "E-Document Message Entry No."; Integer)
Comment thread
djukicmilica marked this conversation as resolved.
{
Caption = 'E-Document Message Entry No.';
DataClassification = SystemMetadata;
}
Comment thread
djukicmilica marked this conversation as resolved.
field(13; "Created At"; DateTime)
{
Caption = 'Created At';
DataClassification = SystemMetadata;
}
field(14; "External Message ID"; Text[250])
{
Caption = 'External Message ID';
DataClassification = CustomerContent;
}
field(15; "Received At"; DateTime)
{
Caption = 'Received At';
DataClassification = SystemMetadata;
}
field(16; "Sender Platform ID"; Text[50])
{
Caption = 'Sender Platform ID';
DataClassification = OrganizationIdentifiableInformation;
}
field(17; "Sender Platform Scheme"; Code[4])
{
Caption = 'Sender Platform Scheme';
DataClassification = SystemMetadata;
}
field(18; "Sender Platform Name"; Text[100])
{
Caption = 'Sender Platform Name';
DataClassification = OrganizationIdentifiableInformation;
}
field(19; "Invoice Issue Date"; Date)
{
Caption = 'Invoice Issue Date';
DataClassification = CustomerContent;
}
field(20; "Invoice Receipt At"; DateTime)
{
Caption = 'Invoice Receipt At';
DataClassification = CustomerContent;
}
field(21; "Invoice Issuer ID"; Text[50])
{
Caption = 'Invoice Issuer ID';
DataClassification = OrganizationIdentifiableInformation;
}
field(22; "Invoice Issuer Scheme"; Code[4])
{
Caption = 'Invoice Issuer Scheme';
DataClassification = SystemMetadata;
}
field(23; "Invoice Issuer Name"; Text[100])
{
Caption = 'Invoice Issuer Name';
DataClassification = OrganizationIdentifiableInformation;
}
}

keys
{
key(PK; "Entry No.")
{
Clustered = true;
}
key(Occurrence; "E-Document Entry No.", "Source Occurrence ID", Type)
{
Unique = true;
}
key(DetailedLedgerEntry; Type, "Detailed Ledger Entry No.")
{
}
key(EDocumentMessage; "E-Document Message Entry No.")
{
}
}
}
Loading
Loading