From 47e79b7421ac9b2159ae2c47a7ddce7ad6245fb6 Mon Sep 17 00:00:00 2001 From: "Daniel F. Rose" Date: Tue, 12 May 2026 20:12:11 +0200 Subject: [PATCH 1/3] feat: implement XML attachment file naming functionality (#253) * feat(eu_einvoice): configurable base name for auto-attached XRechnung XML Add optional pattern on E Invoice Settings (auto_name_format_for_xml_file) for the file base name when auto-attaching XML on Sales Invoice submit. Empty pattern keeps using the document name; otherwise use naming-series-style segments (dot-separated, parse_naming_series, {field} placeholders, date tokens), strip leading # per segment so counter-only parts do not consume Series, sanitize with get_safe_file_name, and fall back to the document name on error. Colocate get_xml_attachment_file_base_name with Sales Invoice custom logic and cover naming edge cases in test_sales_invoice.py. Refresh E Invoice Settings controller and de / template catalogs for the new strings. * refactor(eu_einvoice): enhance download_xrechnung to use configurable base name Update the download_xrechnung function to retrieve the Sales Invoice document and check permissions before generating the XML file. The filename now utilizes a configurable base name from E Invoice Settings, improving flexibility for file naming. This change ensures that the generated XML file adheres to the specified naming format. * chore(eu_einvoice): update E Invoice Settings and localization files Rearrange fields in e_invoice_settings.json for better organization, adding a label for the XML Settings section. Update localization files (de.po and main.pot) to reflect changes in field names and add new translations for XML Settings. Adjust POT creation dates for consistency. * refactor(eu_einvoice): centralize sales invoice xml stem naming Resolve the downloadable XML filename stem in get_xml_attachment_file_base_name: read *Auto name format for XML file* from **E Invoice Settings** when the pattern argument is omitted, accept an optional keyword-only pattern override, and build the stem with parse_naming_series plus a no-op number generator so naming has no series counter DB side effects. Sanitize with get_safe_file_name and fall back to doc.name when the pattern is empty or fails. Call sites use the helper without duplicating settings reads. Tests pass pattern= for empty or whitespace patterns, and assert the settings-backed path by patching frappe.get_single_value only for **E Invoice Settings** / auto_name_format_for_xml_file. --------- Co-authored-by: Daniel Rose <26166128+dafrose@users.noreply.github.com> (cherry picked from commit d45a1f065e02fae62d3d0e2186510d0e106f6ccc) Co-authored-by: Cursor --- .../custom/sales_invoice.py | 45 ++++++++++++-- .../custom/test_sales_invoice.py | 58 +++++++++++++++++++ .../e_invoice_settings.json | 16 ++++- .../e_invoice_settings/e_invoice_settings.py | 1 + 4 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 eu_einvoice/european_e_invoice/custom/test_sales_invoice.py diff --git a/eu_einvoice/european_e_invoice/custom/sales_invoice.py b/eu_einvoice/european_e_invoice/custom/sales_invoice.py index e57ef6e3..32a3edbf 100644 --- a/eu_einvoice/european_e_invoice/custom/sales_invoice.py +++ b/eu_einvoice/european_e_invoice/custom/sales_invoice.py @@ -15,8 +15,10 @@ from drafthorse.models.trade import LogisticsServiceCharge from drafthorse.models.tradelines import LineItem from frappe import _ -from frappe.core.doctype.file.utils import find_file_by_url +from frappe.core.doctype.file.utils import find_file_by_url, get_safe_file_name from frappe.core.utils import html2text +from frappe.model.naming import parse_naming_series +from frappe.utils import cstr from frappe.utils.data import date_diff, flt, getdate, to_markdown from eu_einvoice.common_codes import CommonCodeRetriever @@ -45,8 +47,10 @@ @frappe.whitelist() def download_xrechnung(invoice_id: str): - frappe.local.response.filename = f"{invoice_id}.xml" - frappe.local.response.filecontent = get_einvoice(invoice_id) + invoice = frappe.get_doc("Sales Invoice", invoice_id) + base_name = get_xml_attachment_file_base_name(invoice) + frappe.local.response.filecontent = get_einvoice(invoice) + frappe.local.response.filename = f"{base_name}.xml" frappe.local.response.type = "download" @@ -919,7 +923,8 @@ def _attach_xml_file(doc: SalesInvoice, xml_content: bytes, field_name: str | No ) return - file_name = f"{doc.name}.xml".replace("/", "-") + base_name = get_xml_attachment_file_base_name(doc) + file_name = f"{base_name}.xml" # Create new File document file_doc = frappe.new_doc("File") @@ -1142,3 +1147,35 @@ def attach_xml_to_pdf(invoice_id: str, pdf_data: bytes) -> bytes: xml_bytes = get_einvoice(invoice_id) return attach_xml(pdf_data, xml_bytes, level) + + +def get_xml_attachment_file_base_name(doc, *, pattern: str | None = None) -> str: + """Filename stem (no `.xml`) for the downloadable XML. + + Uses *pattern* when given, otherwise reads *Auto name format for XML file* + from **E Invoice Settings**. Falls back to `doc.name` when the pattern is + empty or fails to resolve. Result is sanitized via `get_safe_file_name` + (same rules as **File** attachments). + """ + if pattern is None: + pattern = frappe.get_single_value("E Invoice Settings", "auto_name_format_for_xml_file") + pattern = cstr(pattern).strip() + if pattern: + try: + base = parse_naming_series(pattern, doc=doc, number_generator=_no_series_counter).strip() + except Exception: + frappe.log_error( + title=_("E Invoice XML file name pattern failed"), + message=frappe.get_traceback(), + reference_doctype=doc.doctype, + reference_name=doc.name, + ) + base = "" + if base: + return get_safe_file_name(base) + return get_safe_file_name(doc.name) + + +def _no_series_counter(_key: str, _digits: int) -> str: + """Disable the series counter so XML naming has no DB side effects.""" + return "" diff --git a/eu_einvoice/european_e_invoice/custom/test_sales_invoice.py b/eu_einvoice/european_e_invoice/custom/test_sales_invoice.py new file mode 100644 index 00000000..6e36ff10 --- /dev/null +++ b/eu_einvoice/european_e_invoice/custom/test_sales_invoice.py @@ -0,0 +1,58 @@ +from unittest.mock import patch + +import frappe +from frappe.tests.utils import FrappeTestCase + +from eu_einvoice.european_e_invoice.custom.sales_invoice import ( + get_xml_attachment_file_base_name, +) + + +class TestXmlAttachmentNaming(FrappeTestCase): + def test_auto_name_format_from_e_invoice_settings(self): + doc = frappe._dict(name="SINV-00001", po_no="PO-42", doctype="Sales Invoice") + field = "auto_name_format_for_xml_file" + pattern = "XMLSTEM.-.{po_no}" + real_gsv = frappe.get_single_value + + def get_single_value(doctype, fname, cache=True): + if doctype == "E Invoice Settings" and fname == field: + return pattern + return real_gsv(doctype, fname, cache=cache) + + with patch.object(frappe, "get_single_value", side_effect=get_single_value): + self.assertEqual(get_xml_attachment_file_base_name(doc), "XMLSTEM-PO-42") + + def test_empty_or_whitespace_uses_doc_name(self): + doc = frappe._dict(name="SINV/00001", doctype="Sales Invoice") + self.assertEqual(get_xml_attachment_file_base_name(doc, pattern=""), "SINV_00001") + self.assertEqual(get_xml_attachment_file_base_name(doc, pattern=" "), "SINV_00001") + + def test_dot_separated_pattern_with_field(self): + doc = frappe._dict(name="SINV-00001", po_no="PO-1", doctype="Sales Invoice") + base = get_xml_attachment_file_base_name(doc, pattern="EXAMPLE.-.{po_no}") + self.assertEqual(base, "EXAMPLE-PO-1") + + def test_dot_separated_inv_field_end(self): + doc = frappe._dict(name="SINV-00001", po_no="X-9", doctype="Sales Invoice") + base = get_xml_attachment_file_base_name(doc, pattern="INV.-.{po_no}.-.END") + self.assertEqual(base, "INV-X-9-END") + + def test_leading_hashes_stripped_per_part_avoids_series_counter(self): + doc = frappe._dict(name="SINV-00001", po_no="PO-1", doctype="Sales Invoice") + base = get_xml_attachment_file_base_name(doc, pattern="EXAMPLE.-.{po_no}.#####") + self.assertEqual(base, "EXAMPLE-PO-1") + + def test_hash_in_literal_sanitized_like_file_utils(self): + doc = frappe._dict(name="SINV-00001", doctype="Sales Invoice") + base = get_xml_attachment_file_base_name(doc, pattern="INV#X") + self.assertEqual(base, "INV_X") + + def test_only_hash_segments_falls_back_to_doc_name(self): + doc = frappe._dict(name="SINV-00001", doctype="Sales Invoice") + self.assertEqual(get_xml_attachment_file_base_name(doc, pattern="#####"), "SINV-00001") + + def test_slash_in_resolved_base_is_sanitized(self): + doc = frappe._dict(name="SINV-00001", po_no="A/B", doctype="Sales Invoice") + base = get_xml_attachment_file_base_name(doc, pattern="{po_no}") + self.assertEqual(base, "A_B") diff --git a/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json b/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json index 5a38368f..316b732f 100644 --- a/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +++ b/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json @@ -15,6 +15,8 @@ "section_break_bt120", "vat_exemption_reason_text", "section_break_yldr", + "auto_name_format_for_xml_file", + "column_break_vlzj", "auto_attach_xml", "attach_field_for_xml_file" ], @@ -55,8 +57,20 @@ "options": "\nWarning Message\nError Message" }, { + "bold": 1, "fieldname": "section_break_yldr", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "XML Settings" + }, + { + "description": "Pattern for the file name of the XML. .xml is appended automatically. Leave empty to use the document name (Sales Invoice ID). In case of an error, the document name is used as fallback.\n

Segments use a dot (.) between literal text and dynamic parts. Example:
EXAMPLE-.MM.-.{po_no}.-.YYYY", + "fieldname": "auto_name_format_for_xml_file", + "fieldtype": "Data", + "label": "Auto Name Format for XML File" + }, + { + "fieldname": "column_break_vlzj", + "fieldtype": "Column Break" }, { "default": "0", diff --git a/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py b/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py index 48197706..18268b3f 100644 --- a/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py +++ b/eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py @@ -18,6 +18,7 @@ class EInvoiceSettings(Document): attach_field_for_xml_file: DF.Autocomplete | None auto_attach_xml: DF.Check + auto_name_format_for_xml_file: DF.Data | None error_action_on_save: DF.Literal["", "Warning Message", "Error Message"] error_action_on_submit: DF.Literal["", "Warning Message", "Error Message"] sales_invoice_number_field: DF.Autocomplete | None From 9a5d59a62e898dc6f17f78d7b08f6c8bbf64bf3a Mon Sep 17 00:00:00 2001 From: Daniel Rose <26166128+dafrose@users.noreply.github.com> Date: Fri, 15 May 2026 15:24:30 +0200 Subject: [PATCH 2/3] fix: use local safe filename helper for xml attachment naming Frappe v15 does not expose get_safe_file_name on file utils; mirror the v17+ implementation beside get_xml_attachment_file_base_name as _get_safe_file_name. --- .../european_e_invoice/custom/sales_invoice.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/eu_einvoice/european_e_invoice/custom/sales_invoice.py b/eu_einvoice/european_e_invoice/custom/sales_invoice.py index 32a3edbf..3e597fd4 100644 --- a/eu_einvoice/european_e_invoice/custom/sales_invoice.py +++ b/eu_einvoice/european_e_invoice/custom/sales_invoice.py @@ -15,7 +15,7 @@ from drafthorse.models.trade import LogisticsServiceCharge from drafthorse.models.tradelines import LineItem from frappe import _ -from frappe.core.doctype.file.utils import find_file_by_url, get_safe_file_name +from frappe.core.doctype.file.utils import find_file_by_url from frappe.core.utils import html2text from frappe.model.naming import parse_naming_series from frappe.utils import cstr @@ -1154,7 +1154,7 @@ def get_xml_attachment_file_base_name(doc, *, pattern: str | None = None) -> str Uses *pattern* when given, otherwise reads *Auto name format for XML file* from **E Invoice Settings**. Falls back to `doc.name` when the pattern is - empty or fails to resolve. Result is sanitized via `get_safe_file_name` + empty or fails to resolve. Result is sanitized via `_get_safe_file_name` (same rules as **File** attachments). """ if pattern is None: @@ -1172,8 +1172,13 @@ def get_xml_attachment_file_base_name(doc, *, pattern: str | None = None) -> str ) base = "" if base: - return get_safe_file_name(base) - return get_safe_file_name(doc.name) + return _get_safe_file_name(base) + return _get_safe_file_name(doc.name) + + +def _get_safe_file_name(file_name: str) -> str: + """Local-only; mirrors ``get_safe_file_name`` in Frappe v17+ file utils.""" + return re.sub(r"[/\\%?#]", "_", file_name) def _no_series_counter(_key: str, _digits: int) -> str: From 79abd159e6ffbada7c6529f497ac5988d936881b Mon Sep 17 00:00:00 2001 From: Daniel Rose <26166128+dafrose@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:36:55 +0200 Subject: [PATCH 3/3] chore: regenerate locale after version-16-hotfix rebase --- eu_einvoice/locale/de.po | 48 ++++++++++++++++++++++++++++--------- eu_einvoice/locale/main.pot | 48 +++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 23 deletions(-) diff --git a/eu_einvoice/locale/de.po b/eu_einvoice/locale/de.po index 55f0576f..81276453 100644 --- a/eu_einvoice/locale/de.po +++ b/eu_einvoice/locale/de.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: European e-Invoice VERSION\n" "Report-Msgid-Bugs-To: hallo@alyf.de\n" -"POT-Creation-Date: 2026-06-08 13:47+0053\n" +"POT-Creation-Date: 2026-06-08 16:32+0053\n" "PO-Revision-Date: 2026-02-05 11:13+0100\n" "Last-Translator: hallo@alyf.de\n" "Language: de\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:803 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:807 msgid "A document level discount is currently not supported in the e-invoice." msgstr "Ein Rabatt auf der Dokumentebene wird in der E-Rechnung derzeit nicht unterstützt." @@ -65,6 +65,12 @@ msgstr "Eine E-Rechnung mit der gleichen Rechnungs-ID und Lieferanten existiert msgid "Attach Field for XML File" msgstr "Anhangsfeld für XML-Datei" +#. Label of the auto_name_format_for_xml_file (Data) field in DocType 'E +#. Invoice Settings' +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +msgid "Auto Name Format for XML File" +msgstr "Namensmuster für die XML-Datei" + #. Label of the auto_attach_xml (Check) field in DocType 'E Invoice Settings' #: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json msgid "Auto-attach XML File" @@ -165,11 +171,11 @@ msgstr "Käufer-Referenz" msgid "Calculation Percent" msgstr "Berechnung Prozentsatz" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:834 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:838 msgid "Cannot create E Invoice." msgstr "E-Rechnung konnte nicht erstellt werden." -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:848 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:852 msgid "Cannot validate E Invoice schematron." msgstr "E-Rechnung konnte nicht validiert werden." @@ -260,7 +266,11 @@ msgstr "E-Rechnungs-Einstellungen" msgid "E Invoice Trade Tax" msgstr "E-Rechnungs-Steuer" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:816 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:1168 +msgid "E Invoice XML file name pattern failed" +msgstr "Namensmuster für E-Rechnungs-XML-Datei fehlgeschlagen" + +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:820 msgid "E Invoice is not correct" msgstr "E-Rechnung ist nicht korrekt" @@ -295,11 +305,11 @@ msgstr "Schema der elektronischen Adresse" msgid "Embedded Document" msgstr "Eingebettetes Dokument" -#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:51 +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:52 msgid "Field '{0}' does not exist on Sales Invoice doctype" msgstr "Feld '{0}' existiert nicht im DocType 'Sales Invoice'" -#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:59 +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:60 msgid "Field '{0}' must be of type 'Attach'. Current type: {1}" msgstr "Feld '{0}' muss vom Typ 'Anhängen' sein. Aktueller Typ: {1}" @@ -355,6 +365,16 @@ msgstr "Nur bei E-Rechnungs-Profil \"XRECHNUNG\" beim Buchen" msgid "Partial Amount" msgstr "Teilbetrag" +#. Description of the 'Auto Name Format for XML File' (Data) field in DocType +#. 'E Invoice Settings' +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +msgid "" +"Pattern for the file name of the XML. .xml is appended automatically. Leave empty to use the document name (Sales Invoice ID). In case of an error, the document name is used as fallback.\n" +"

Segments use a dot (.) between literal text and dynamic parts. Example:
EXAMPLE-.MM.-.{po_no}.-.YYYY" +msgstr "" +"Muster für den Dateinamen der XML-Datei. .xml wird automatisch angehängt. Leer lassen, um den Dokumentnamen zu verwenden (ID der Ausgangsrechnung). Bei einem Fehler wird der Dokumentname als Fallback verwendet.\n" +"

Literaltext und dynamische Teile werden mit einem Punkt (.) getrennt. Beispiel:
BEISPIEL-.MM.-.{po_no}.-.YYYY" + #. Label of the payment_means_section (Section Break) field in DocType 'E #. Invoice Import' #: eu_einvoice/european_e_invoice/doctype/e_invoice_import/e_invoice_import.json @@ -561,19 +581,25 @@ msgstr "Validierungswarnungen" msgid "Warning Message" msgstr "Warnung" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:782 +#. Label of the section_break_yldr (Section Break) field in DocType 'E Invoice +#. Settings' +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +msgid "XML Settings" +msgstr "XML-Einstellungen" + +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:786 msgid "{0} row #{1}: Discount Date should be after Posting Date" msgstr "{0} Zeile {1}: Rabatt-Datum sollte nach Buchungsdatum liegen" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:771 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:775 msgid "{0} row #{1}: The charge type 'Actual' is only supported in the eInvoice profiles 'EXTENDED' and 'XRECHNUNG'." msgstr "Die Gebührenart 'Tatsächlich' wird nur in den E-Rechnungs-Profilen 'EXTENDED' und 'XRECHNUNG' unterstützt." -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:759 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:763 msgid "{0} row #{1}: Type '{2}' is not supported in e-invoice" msgstr "{0} Zeile #{1}: Typ '{2}' wird in der E-Rechnung nicht unterstützt" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:794 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:798 msgid "{0}: Only one mode of payment will be considered in the e-invoice." msgstr "{0}: Nur eine Zahlungsart wird in der E-Rechnung berücksichtigt." diff --git a/eu_einvoice/locale/main.pot b/eu_einvoice/locale/main.pot index 82c42aea..82039ff9 100644 --- a/eu_einvoice/locale/main.pot +++ b/eu_einvoice/locale/main.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: European e-Invoice VERSION\n" "Report-Msgid-Bugs-To: hallo@alyf.de\n" -"POT-Creation-Date: 2026-06-08 13:47+0053\n" -"PO-Revision-Date: 2026-06-08 13:47+0053\n" +"POT-Creation-Date: 2026-06-08 16:32+0053\n" +"PO-Revision-Date: 2026-06-08 16:32+0053\n" "Last-Translator: hallo@alyf.de\n" "Language-Team: hallo@alyf.de\n" "MIME-Version: 1.0\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:803 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:807 msgid "A document level discount is currently not supported in the e-invoice." msgstr "" @@ -63,6 +63,12 @@ msgstr "" msgid "Attach Field for XML File" msgstr "" +#. Label of the auto_name_format_for_xml_file (Data) field in DocType 'E +#. Invoice Settings' +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +msgid "Auto Name Format for XML File" +msgstr "" + #. Label of the auto_attach_xml (Check) field in DocType 'E Invoice Settings' #: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json msgid "Auto-attach XML File" @@ -163,11 +169,11 @@ msgstr "" msgid "Calculation Percent" msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:834 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:838 msgid "Cannot create E Invoice." msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:848 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:852 msgid "Cannot validate E Invoice schematron." msgstr "" @@ -258,7 +264,11 @@ msgstr "" msgid "E Invoice Trade Tax" msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:816 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:1168 +msgid "E Invoice XML file name pattern failed" +msgstr "" + +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:820 msgid "E Invoice is not correct" msgstr "" @@ -293,11 +303,11 @@ msgstr "" msgid "Embedded Document" msgstr "" -#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:51 +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:52 msgid "Field '{0}' does not exist on Sales Invoice doctype" msgstr "" -#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:59 +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.py:60 msgid "Field '{0}' must be of type 'Attach'. Current type: {1}" msgstr "" @@ -353,6 +363,14 @@ msgstr "" msgid "Partial Amount" msgstr "" +#. Description of the 'Auto Name Format for XML File' (Data) field in DocType +#. 'E Invoice Settings' +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +msgid "" +"Pattern for the file name of the XML. .xml is appended automatically. Leave empty to use the document name (Sales Invoice ID). In case of an error, the document name is used as fallback.\n" +"

Segments use a dot (.) between literal text and dynamic parts. Example:
EXAMPLE-.MM.-.{po_no}.-.YYYY" +msgstr "" + #. Label of the payment_means_section (Section Break) field in DocType 'E #. Invoice Import' #: eu_einvoice/european_e_invoice/doctype/e_invoice_import/e_invoice_import.json @@ -560,19 +578,25 @@ msgstr "" msgid "Warning Message" msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:782 +#. Label of the section_break_yldr (Section Break) field in DocType 'E Invoice +#. Settings' +#: eu_einvoice/european_e_invoice/doctype/e_invoice_settings/e_invoice_settings.json +msgid "XML Settings" +msgstr "" + +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:786 msgid "{0} row #{1}: Discount Date should be after Posting Date" msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:771 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:775 msgid "{0} row #{1}: The charge type 'Actual' is only supported in the eInvoice profiles 'EXTENDED' and 'XRECHNUNG'." msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:759 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:763 msgid "{0} row #{1}: Type '{2}' is not supported in e-invoice" msgstr "" -#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:794 +#: eu_einvoice/european_e_invoice/custom/sales_invoice.py:798 msgid "{0}: Only one mode of payment will be considered in the e-invoice." msgstr ""