Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 14 additions & 1 deletion eu_einvoice/european_e_invoice/custom/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def __init__(
self.doc = None
self.item_tax_rates = set()
self.delivery_dates = []
self.vat_exemption_reason_text = ""

def get_einvoice(self) -> Document | None:
"""Return the einvoice document as a Python object."""
Expand All @@ -135,6 +136,9 @@ def get_einvoice(self) -> Document | None:
def create_einvoice(self):
"""Create the einvoice document as a Python object."""
self.doc = Document()
self.vat_exemption_reason_text = str(
frappe.db.get_single_value("E Invoice Settings", "vat_exemption_reason_text") or ""
).strip()

self._set_context()
self._set_header()
Expand Down Expand Up @@ -273,7 +277,10 @@ def _set_seller(self):
self._set_seller_address()

def _set_seller_id(self):
for row in self.customer.supplier_numbers:
supplier_numbers = getattr(self.customer, "supplier_numbers", None)
if not supplier_numbers:
return
for row in supplier_numbers:
if row.company == self.invoice.company and row.supplier_number:
self.doc.trade.agreement.seller.id = row.supplier_number
break
Expand Down Expand Up @@ -486,6 +493,7 @@ def _add_line_item(self, item: SalesInvoiceItem):
("Sales Taxes and Charges Template", self.invoice.taxes_and_charges),
]
).upper()
self._set_optional_vat_exemption_reason_text(li.settlement.trade_tax)

li.settlement.monetary_summation.total_amount = flt(item.net_amount, item.precision("net_amount"))
self.doc.trade.items.add(li)
Expand Down Expand Up @@ -614,8 +622,13 @@ def _add_empty_tax(self):
("Sales Taxes and Charges Template", self.invoice.taxes_and_charges),
]
).upper()
self._set_optional_vat_exemption_reason_text(trade_tax)
self.doc.trade.settlement.trade_tax.add(trade_tax)

def _set_optional_vat_exemption_reason_text(self, trade_tax: ApplicableTradeTax) -> None:
if self.vat_exemption_reason_text:
trade_tax.exemption_reason = self.vat_exemption_reason_text

def _add_delivery_date(self):
if self.delivery_dates:
delivery_date = sorted(self.delivery_dates)[-1]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2024, ALYF GmbH and contributors
# For license information, please see license.txt

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -223,7 +224,7 @@ def _validate_schematron(self, xml_bytes):
if any(validation_warnings):
self.validation_warnings += "\n".join(validation_warnings)

def parse_seller(self, seller: "TradeParty"):
def parse_seller(self, seller: TradeParty):
self.seller_name = str(seller.name)
self.seller_tax_id = (
seller.tax_registrations.children[0].id._text if seller.tax_registrations.children else None
Expand All @@ -232,13 +233,13 @@ def parse_seller(self, seller: "TradeParty"):
self.seller_electronic_address_scheme = str(seller.electronic_address.uri_ID._scheme_id)
self.parse_address(seller.address, "seller")

def parse_buyer(self, buyer: "TradeParty"):
def parse_buyer(self, buyer: TradeParty):
self.buyer_name = str(buyer.name)
self.buyer_electronic_address = str(buyer.electronic_address.uri_ID._text)
self.buyer_electronic_address_scheme = str(buyer.electronic_address.uri_ID._scheme_id)
self.parse_address(buyer.address, "buyer")

def parse_address(self, address: "PostalTradeAddress", prefix: str) -> _dict:
def parse_address(self, address: PostalTradeAddress, prefix: str) -> _dict:
country = frappe.db.get_value("Country", {"code": str(address.country_id).lower()}, "name")

self.set(f"{prefix}_city", str(address.city_name))
Expand All @@ -247,7 +248,7 @@ def parse_address(self, address: "PostalTradeAddress", prefix: str) -> _dict:
self.set(f"{prefix}_postcode", str(address.postcode))
self.set(f"{prefix}_country", str(country))

def parse_line_item(self, li: "LineItem"):
def parse_line_item(self, li: LineItem):
item = self.append("items")

net_rate = float(li.agreement.net.amount._value)
Expand All @@ -274,13 +275,15 @@ def parse_line_item(self, li: "LineItem"):
item.tax_rate = flt_or_none(li.settlement.trade_tax.rate_applicable_percent._value)
item.total_amount = flt_or_none(li.settlement.monetary_summation.total_amount._value)

def parse_tax(self, tax: "ApplicableTradeTax"):
def parse_tax(self, tax: ApplicableTradeTax):
t = self.append("taxes")
t.basis_amount = flt_or_none(tax.basis_amount._value)
t.rate_applicable_percent = flt_or_none(tax.rate_applicable_percent._value)
t.calculated_amount = flt_or_none(tax.calculated_amount._value)
reason_text = tax.exemption_reason._text
t.vat_exemption_reason_text = str(reason_text).strip() if reason_text else None

def parse_payment_term(self, term: "PaymentTerms"):
def parse_payment_term(self, term: PaymentTerms):
if not term.partial_amount.children:
self.due_date = term.due._value
return
Expand Down Expand Up @@ -309,7 +312,7 @@ def parse_payment_term(self, term: "PaymentTerms"):
if term.discount_terms.actual_amount._value:
t.discount_actual_amount = float(term.discount_terms.actual_amount._value)

def parse_monetary_summation(self, summation: "MonetarySummation"):
def parse_monetary_summation(self, summation: MonetarySummation):
self.line_total = flt_or_none(summation.line_total._value)
self.allowance_total = flt_or_none(summation.allowance_total._value)
self.charge_total = flt_or_none(summation.charge_total._value)
Expand All @@ -322,14 +325,14 @@ def parse_monetary_summation(self, summation: "MonetarySummation"):
self.total_prepaid = flt_or_none(summation.prepaid_total._value)
self.due_payable = flt_or_none(summation.due_amount._value)

def parse_bank_details(self, payment_means: "PaymentMeans"):
def parse_bank_details(self, payment_means: PaymentMeans):
self.payee_iban = payment_means.payee_account.iban._text or None

if EInvoiceProfile(self.profile) >= EInvoiceProfile.EN16931:
self.payee_account_name = payment_means.payee_account.account_name._text or None
self.payee_bic = payment_means.payee_institution.bic._text or None

def parse_billing_period(self, period: "BillingSpecifiedPeriod"):
def parse_billing_period(self, period: BillingSpecifiedPeriod):
self.billing_period_start = period.start._value
self.billing_period_end = period.end._value

Expand Down Expand Up @@ -466,8 +469,8 @@ def relative_url_to_path(url: str) -> Path:


@frappe.whitelist()
def create_purchase_invoice(source_name, target_doc=None):
def post_process(source, target: "PurchaseInvoice"):
def create_purchase_invoice(source_name: str, target_doc: Document | None = None):
def post_process(source, target: PurchaseInvoice):
target.set_missing_values()

def process_item_row(source, target, source_parent) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"error_action_on_save",
"error_action_on_submit",
"sales_invoice_number_field",
"section_break_bt120",
"vat_exemption_reason_text",
"section_break_yldr",
"auto_attach_xml",
"attach_field_for_xml_file"
Expand Down Expand Up @@ -74,13 +76,24 @@
"fieldname": "sales_invoice_number_field",
"fieldtype": "Autocomplete",
"label": "Sales Invoice Number Field"
},
{
"fieldname": "section_break_bt120",
"fieldtype": "Section Break",
"label": "VAT exemption (e-invoice)"
},
{
"description": "If set, written to BT-120 (<code>ram:ExemptionReason</code>) on VAT breakdowns where an exemption reason code is emitted. Left unset in the XML when empty.",
"fieldname": "vat_exemption_reason_text",
"fieldtype": "Small Text",
"label": "Default VAT exemption reason"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-02-06 17:52:09.990716",
"modified": "2026-06-08 13:02:59.672943",
"modified_by": "Administrator",
"module": "European e-Invoice",
"name": "E Invoice Settings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class EInvoiceSettings(Document):
sales_invoice_number_field: DF.Autocomplete | None
validate_sales_invoice_on_save: DF.Check
validate_sales_invoice_on_submit: DF.Check
vat_exemption_reason_text: DF.SmallText | None
# end: auto-generated types

def before_validate(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"basis_amount",
"rate_applicable_percent",
"calculated_amount",
"vat_exemption_reason_text",
"tax_account"
],
"fields": [
Expand All @@ -35,6 +36,12 @@
"options": "currency",
"read_only": 1
},
{
"fieldname": "vat_exemption_reason_text",
"fieldtype": "Small Text",
"label": "VAT exemption reason",
"read_only": 1
},
{
"fieldname": "tax_account",
"fieldtype": "Link",
Expand All @@ -45,12 +52,13 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-09-26 21:18:13.837830",
"modified": "2026-06-08 13:04:01.003993",
"modified_by": "Administrator",
"module": "European e-Invoice",
"name": "E Invoice Trade Tax",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class EInvoiceTradeTax(Document):
parenttype: DF.Data
rate_applicable_percent: DF.Percent
tax_account: DF.Link | None
vat_exemption_reason_text: DF.SmallText | None
# end: auto-generated types

pass
Loading
Loading