mirror of
https://github.com/bringout/oca-edi.git
synced 2026-04-23 10:52:08 +02:00
574 lines
24 KiB
Python
574 lines
24 KiB
Python
# Copyright 2016-2017 Akretion (http://www.akretion.com)
|
|
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
|
# Copyright 2019 Onestein (<https://www.onestein.eu>)
|
|
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
|
|
import base64
|
|
import io
|
|
import logging
|
|
|
|
from lxml import etree
|
|
|
|
from odoo import fields, models
|
|
from odoo.tools import float_is_zero, float_round
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_name = "account.move"
|
|
_inherit = ["account.move", "base.ubl"]
|
|
|
|
def _ubl_add_header(self, parent_node, ns, version="2.1"):
|
|
self.ensure_one()
|
|
ubl_version = etree.SubElement(parent_node, ns["cbc"] + "UBLVersionID")
|
|
ubl_version.text = version
|
|
doc_id = etree.SubElement(parent_node, ns["cbc"] + "ID")
|
|
doc_id.text = self.name
|
|
issue_date = etree.SubElement(parent_node, ns["cbc"] + "IssueDate")
|
|
issue_date.text = self.invoice_date.strftime("%Y-%m-%d")
|
|
if (
|
|
self.invoice_date_due
|
|
and version >= "2.1"
|
|
and self.move_type == "out_invoice"
|
|
):
|
|
due_date = etree.SubElement(parent_node, ns["cbc"] + "DueDate")
|
|
due_date.text = fields.Date.to_string(self.invoice_date_due)
|
|
if self.move_type == "out_invoice":
|
|
type_code = etree.SubElement(parent_node, ns["cbc"] + "InvoiceTypeCode")
|
|
elif self.move_type == "out_refund":
|
|
type_code = etree.SubElement(parent_node, ns["cbc"] + "CreditNoteTypeCode")
|
|
type_code.text = self._ubl_get_invoice_type_code()
|
|
if self.narration:
|
|
note = etree.SubElement(parent_node, ns["cbc"] + "Note")
|
|
note.text = self.narration
|
|
doc_currency = etree.SubElement(parent_node, ns["cbc"] + "DocumentCurrencyCode")
|
|
doc_currency.text = self.currency_id.name
|
|
# TODO: enable when below commit of 15.0 is back ported to 14.0
|
|
# [IMP] account_edi(_*): Standalone UBL format + edi.format inheritance
|
|
# https://github.com/odoo/odoo/commit/b58810a77bb4c432a6aef18413659b1ea7b25c71
|
|
# or when migrating to 15.0
|
|
# buyer_reference = etree.SubElement(parent_node, ns["cbc"] + "BuyerReference")
|
|
# buyer_reference.text = self.ref or ""
|
|
|
|
def _ubl_get_invoice_type_code(self):
|
|
if self.move_type == "out_invoice":
|
|
return "380"
|
|
elif self.move_type == "out_refund":
|
|
return "381"
|
|
|
|
def _ubl_get_order_reference(self):
|
|
"""An identifier of a referenced purchase order, issued by the Buyer"""
|
|
return self.ref or "/"
|
|
|
|
def _ubl_get_salesorder_reference(self):
|
|
"""An identifier of a referenced sales order, issued by the Seller"""
|
|
return self.invoice_origin
|
|
|
|
def _ubl_add_order_reference(self, parent_node, ns, version="2.1"):
|
|
self.ensure_one()
|
|
buyer_ref = self._ubl_get_order_reference()
|
|
seller_ref = self._ubl_get_salesorder_reference()
|
|
if buyer_ref or seller_ref:
|
|
node = etree.SubElement(parent_node, ns["cac"] + "OrderReference")
|
|
if buyer_ref:
|
|
node_id = etree.SubElement(node, ns["cbc"] + "ID")
|
|
node_id.text = buyer_ref
|
|
if seller_ref:
|
|
node_salesorderid = etree.SubElement(node, ns["cbc"] + "SalesOrderID")
|
|
node_salesorderid.text = seller_ref
|
|
|
|
def _ubl_get_buyer_reference(self):
|
|
return self.partner_id.name
|
|
|
|
def _ubl_add_buyer_reference(self, parent_node, ns, version="2.1"):
|
|
self.ensure_one()
|
|
buyer_ref = self._ubl_get_buyer_reference()
|
|
if buyer_ref:
|
|
buyer_order_ref = etree.SubElement(
|
|
parent_node, ns["cbc"] + "BuyerReference"
|
|
)
|
|
buyer_order_ref.text = buyer_ref
|
|
|
|
def _ubl_get_contract_document_reference_dict(self):
|
|
"""Result: dict with key = Doc Type Code, value = ID"""
|
|
self.ensure_one()
|
|
return {}
|
|
|
|
def _ubl_add_contract_document_reference(self, parent_node, ns, version="2.1"):
|
|
self.ensure_one()
|
|
cdr_dict = self._ubl_get_contract_document_reference_dict()
|
|
for doc_type_code, doc_id in cdr_dict.items():
|
|
cdr = etree.SubElement(parent_node, ns["cac"] + "ContractDocumentReference")
|
|
cdr_id = etree.SubElement(cdr, ns["cbc"] + "ID")
|
|
cdr_id.text = doc_id
|
|
cdr_type_code = etree.SubElement(cdr, ns["cbc"] + "DocumentTypeCode")
|
|
cdr_type_code.text = doc_type_code
|
|
|
|
def _ubl_add_attachments(self, parent_node, ns, version="2.1"):
|
|
self.ensure_one()
|
|
if self.company_id.embed_pdf_in_ubl_xml_invoice and not self.env.context.get(
|
|
"no_embedded_pdf"
|
|
):
|
|
filename = "Invoice-" + self.name + ".pdf"
|
|
docu_reference = etree.SubElement(
|
|
parent_node, ns["cac"] + "AdditionalDocumentReference"
|
|
)
|
|
docu_reference_id = etree.SubElement(docu_reference, ns["cbc"] + "ID")
|
|
docu_reference_id.text = filename
|
|
attach_node = etree.SubElement(docu_reference, ns["cac"] + "Attachment")
|
|
binary_node = etree.SubElement(
|
|
attach_node,
|
|
ns["cbc"] + "EmbeddedDocumentBinaryObject",
|
|
mimeCode="application/pdf",
|
|
filename=filename,
|
|
)
|
|
ctx = dict()
|
|
ctx["no_embedded_ubl_xml"] = True
|
|
ctx["force_report_rendering"] = True
|
|
pdf_inv = (
|
|
self.env["ir.actions.report"]
|
|
.with_context(**ctx)
|
|
._render_qweb_pdf("account.account_invoices", [self.id])[0]
|
|
)
|
|
binary_node.text = base64.b64encode(pdf_inv)
|
|
|
|
def _ubl_get_invoice_vat_exclusive_amount(self):
|
|
amount = self.amount_untaxed
|
|
# Add also non-VAT taxes that are not subjected to VAT
|
|
for tline in self.line_ids:
|
|
if not tline.tax_line_id:
|
|
continue
|
|
if tline.tax_line_id.unece_type_id.code != "VAT":
|
|
sign = 1 if tline.is_refund else -1
|
|
amount += sign * tline.balance
|
|
return amount
|
|
|
|
def _ubl_get_invoice_vat_amount(self):
|
|
amount = self.amount_tax
|
|
# Remove non-VAT taxes that are not subjected to VAT
|
|
for tline in self.line_ids:
|
|
if not tline.tax_line_id:
|
|
continue
|
|
if tline.tax_line_id.unece_type_id.code != "VAT":
|
|
sign = 1 if tline.is_refund else -1
|
|
amount -= sign * tline.balance
|
|
return amount
|
|
|
|
def _ubl_get_charge_total_amount(self):
|
|
amount = 0.0
|
|
for tline in self.line_ids:
|
|
if not tline.tax_line_id:
|
|
continue
|
|
if tline.tax_line_id.unece_type_id.code != "VAT":
|
|
if not tline.tax_line_id.include_base_amount:
|
|
# For non-VAT taxes, not subject to VAT, they are declared
|
|
# as AllowanceCharge
|
|
sign = 1 if tline.is_refund else -1
|
|
amount += sign * tline.balance
|
|
return amount
|
|
|
|
def _ubl_add_legal_monetary_total(self, parent_node, ns, version="2.1"):
|
|
self.ensure_one()
|
|
monetary_total = etree.SubElement(parent_node, ns["cac"] + "LegalMonetaryTotal")
|
|
cur_name = self.currency_id.name
|
|
prec = self.currency_id.decimal_places
|
|
line_total = etree.SubElement(
|
|
monetary_total, ns["cbc"] + "LineExtensionAmount", currencyID=cur_name
|
|
)
|
|
line_total.text = "%0.*f" % (prec, self.amount_untaxed)
|
|
tax_excl_total = etree.SubElement(
|
|
monetary_total, ns["cbc"] + "TaxExclusiveAmount", currencyID=cur_name
|
|
)
|
|
tax_excl_total.text = "%0.*f" % (
|
|
prec,
|
|
self._ubl_get_invoice_vat_exclusive_amount(),
|
|
)
|
|
tax_incl_total = etree.SubElement(
|
|
monetary_total, ns["cbc"] + "TaxInclusiveAmount", currencyID=cur_name
|
|
)
|
|
tax_incl_total.text = "%0.*f" % (prec, self.amount_total)
|
|
charge_total_amount = self._ubl_get_charge_total_amount()
|
|
if charge_total_amount:
|
|
el_charge_total_amount = etree.SubElement(
|
|
monetary_total, ns["cbc"] + "ChargeTotalAmount", currencyID=cur_name
|
|
)
|
|
el_charge_total_amount.text = "%0.*f" % (prec, charge_total_amount)
|
|
prepaid_amount = etree.SubElement(
|
|
monetary_total, ns["cbc"] + "PrepaidAmount", currencyID=cur_name
|
|
)
|
|
prepaid_value = self.amount_total - self.amount_residual
|
|
prepaid_amount.text = "%0.*f" % (prec, prepaid_value)
|
|
payable_amount = etree.SubElement(
|
|
monetary_total, ns["cbc"] + "PayableAmount", currencyID=cur_name
|
|
)
|
|
payable_amount.text = "%0.*f" % (prec, self.amount_residual)
|
|
|
|
def _ubl_get_invoice_line_price_unit(self, iline):
|
|
"""Compute the base unit price without taxes"""
|
|
price = iline.price_unit
|
|
qty = 1.0
|
|
if iline.tax_ids:
|
|
tax_incl = any(t.price_include for t in iline.tax_ids)
|
|
if tax_incl:
|
|
# To prevent rounding issue, we must declare tax excluded price
|
|
# for the total quantity
|
|
qty = iline.quantity
|
|
taxes = iline.tax_ids.compute_all(
|
|
price,
|
|
self.currency_id,
|
|
qty,
|
|
product=iline.product_id,
|
|
partner=self.partner_id,
|
|
)
|
|
if taxes:
|
|
price = taxes["total_excluded"]
|
|
dpo = self.env["decimal.precision"]
|
|
price_precision = dpo.precision_get("Product Price")
|
|
return price, price_precision, qty
|
|
|
|
def _ubl_get_invoice_line_discount(self, iline, base_price, base_qty):
|
|
# Formula: Net amount = Invoiced quantity * (Item net price/item price
|
|
# base quantity) + Sum of invoice line charge amount - sum of invoice
|
|
# line allowance amount
|
|
discount = iline.quantity / base_qty * base_price - iline.price_subtotal
|
|
dpo = self.env["decimal.precision"]
|
|
price_precision = dpo.precision_get("Product Price")
|
|
discount = float_round(discount, precision_digits=price_precision)
|
|
return discount, price_precision
|
|
|
|
def _ubl_add_invoice_line_discount(
|
|
self, xml_root, iline, base_price, base_qty, ns, version="2.1"
|
|
):
|
|
discount, prec = self._ubl_get_invoice_line_discount(
|
|
iline, base_price, base_qty
|
|
)
|
|
if float_is_zero(discount, precision_digits=prec):
|
|
return
|
|
charge_node = etree.SubElement(xml_root, ns["cac"] + "AllowanceCharge")
|
|
charge_indicator_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "ChargeIndicator"
|
|
)
|
|
charge_indicator_node.text = "false"
|
|
charge_reason_code_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "AllowanceChargeReasonCode"
|
|
)
|
|
charge_reason_code_node.text = "95"
|
|
charge_reason_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "AllowanceChargeReason"
|
|
)
|
|
charge_reason_node.text = "Discount"
|
|
charge_amount_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "Amount", currencyID=self.currency_id.name
|
|
)
|
|
charge_amount_node.text = "%0.*f" % (prec, discount)
|
|
|
|
def _ubl_add_invoice_line(self, parent_node, iline, line_number, ns, version="2.1"):
|
|
self.ensure_one()
|
|
cur_name = self.currency_id.name
|
|
if self.move_type == "out_invoice":
|
|
line_root = etree.SubElement(parent_node, ns["cac"] + "InvoiceLine")
|
|
elif self.move_type == "out_refund":
|
|
line_root = etree.SubElement(parent_node, ns["cac"] + "CreditNoteLine")
|
|
dpo = self.env["decimal.precision"]
|
|
qty_precision = dpo.precision_get("Product Unit of Measure")
|
|
account_precision = self.currency_id.decimal_places
|
|
line_id = etree.SubElement(line_root, ns["cbc"] + "ID")
|
|
line_id.text = str(line_number)
|
|
uom_unece_code = False
|
|
# product_uom_id is not a required field on account.move.line
|
|
if self.move_type == "out_invoice":
|
|
qty_element_name = "InvoicedQuantity"
|
|
elif self.move_type == "out_refund":
|
|
qty_element_name = "CreditedQuantity"
|
|
if iline.product_uom_id.unece_code:
|
|
uom_unece_code = iline.product_uom_id.unece_code
|
|
quantity = etree.SubElement(
|
|
line_root, ns["cbc"] + qty_element_name, unitCode=uom_unece_code
|
|
)
|
|
else:
|
|
quantity = etree.SubElement(line_root, ns["cbc"] + qty_element_name)
|
|
qty = iline.quantity
|
|
quantity.text = "%0.*f" % (qty_precision, qty)
|
|
base_price, price_precision, base_qty = self._ubl_get_invoice_line_price_unit(
|
|
iline
|
|
)
|
|
line_amount = etree.SubElement(
|
|
line_root, ns["cbc"] + "LineExtensionAmount", currencyID=cur_name
|
|
)
|
|
line_amount.text = "%0.*f" % (account_precision, iline.price_subtotal)
|
|
self._ubl_add_invoice_line_discount(
|
|
line_root, iline, base_price, base_qty, ns, version=version
|
|
)
|
|
self._ubl_add_item(
|
|
iline.name,
|
|
iline.product_id,
|
|
line_root,
|
|
ns,
|
|
type_="sale",
|
|
version=version,
|
|
)
|
|
price_node = etree.SubElement(line_root, ns["cac"] + "Price")
|
|
price_amount = etree.SubElement(
|
|
price_node, ns["cbc"] + "PriceAmount", currencyID=cur_name
|
|
)
|
|
price_amount.text = "%0.*f" % (price_precision, base_price)
|
|
if uom_unece_code:
|
|
base_qty_node = etree.SubElement(
|
|
price_node, ns["cbc"] + "BaseQuantity", unitCode=uom_unece_code
|
|
)
|
|
else:
|
|
base_qty_node = etree.SubElement(price_node, ns["cbc"] + "BaseQuantity")
|
|
base_qty_node.text = "%0.*f" % (qty_precision, base_qty)
|
|
|
|
def _ubl_add_tax_total(self, xml_root, ns, version="2.1"):
|
|
self.ensure_one()
|
|
cur_name = self.currency_id.name
|
|
prec = self.currency_id.decimal_places
|
|
|
|
tax_lines = {}
|
|
for tline in self.line_ids:
|
|
sign = 1 if tline.is_refund else -1
|
|
if tline.tax_line_id:
|
|
# There are as many tax line as there are repartition lines
|
|
tax_lines.setdefault(
|
|
tline.tax_line_id,
|
|
{"base": 0.0, "amount": 0.0},
|
|
)
|
|
tax_lines[tline.tax_line_id]["base"] += tline.tax_base_amount
|
|
tax_lines[tline.tax_line_id]["amount"] += sign * tline.balance
|
|
elif tline.tax_ids:
|
|
# In case there are no repartition lines
|
|
for tax in tline.tax_ids:
|
|
if not tline.is_refund and tax.invoice_repartition_line_ids:
|
|
continue
|
|
if tline.is_refund and tax.refund_repartition_line_ids:
|
|
continue
|
|
tax_lines.setdefault(
|
|
tax,
|
|
{"base": 0.0, "amount": 0.0},
|
|
)
|
|
tax_lines[tax]["base"] += sign * tline.balance
|
|
|
|
exempt = 0.0
|
|
exempt_taxes = self.line_ids.tax_line_id.browse()
|
|
for tax, amounts in tax_lines.items():
|
|
if tax.unece_type_id.code != "VAT":
|
|
if tax.include_base_amount:
|
|
continue
|
|
exempt += amounts["amount"]
|
|
exempt_taxes |= tax
|
|
# For non-VAT taxes, not subject to VAT, declare as AllowanceCharge
|
|
charge_node = etree.SubElement(xml_root, ns["cac"] + "AllowanceCharge")
|
|
charge_indicator_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "ChargeIndicator"
|
|
)
|
|
charge_indicator_node.text = "true"
|
|
charge_reason_code_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "AllowanceChargeReasonCode"
|
|
)
|
|
charge_reason_code_node.text = "ABK"
|
|
charge_reason_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "AllowanceChargeReason"
|
|
)
|
|
charge_reason_node.text = "Miscellaneous"
|
|
charge_amount_node = etree.SubElement(
|
|
charge_node, ns["cbc"] + "Amount", currencyID=cur_name
|
|
)
|
|
charge_amount_node.text = "%0.*f" % (prec, amounts["amount"])
|
|
self._ubl_add_tax_category(tax, charge_node, ns, version=version)
|
|
|
|
tax_total_node = etree.SubElement(xml_root, ns["cac"] + "TaxTotal")
|
|
tax_amount_node = etree.SubElement(
|
|
tax_total_node, ns["cbc"] + "TaxAmount", currencyID=cur_name
|
|
)
|
|
tax_amount_node.text = "%0.*f" % (prec, self._ubl_get_invoice_vat_amount())
|
|
for tax, amounts in tax_lines.items():
|
|
if tax.unece_type_id.code == "VAT":
|
|
self._ubl_add_tax_subtotal(
|
|
amounts["base"],
|
|
amounts["amount"],
|
|
tax,
|
|
cur_name,
|
|
tax_total_node,
|
|
ns,
|
|
version=version,
|
|
)
|
|
|
|
if not float_is_zero(exempt, precision_digits=prec):
|
|
self._ubl_add_tax_subtotal(
|
|
exempt,
|
|
0,
|
|
exempt_taxes[0],
|
|
cur_name,
|
|
tax_total_node,
|
|
ns,
|
|
version=version,
|
|
)
|
|
if len(exempt_taxes) > 1:
|
|
# xpath cac:TaxCategory/cbc:Name
|
|
exempt_node = tax_total_node[-1]
|
|
exempt_node = [
|
|
e for e in list(exempt_node) if e.tag == ns["cac"] + "TaxCategory"
|
|
][0]
|
|
exempt_node = [
|
|
e for e in list(exempt_node) if e.tag == ns["cbc"] + "Name"
|
|
][0]
|
|
exempt_node.text = " + ".join([e.name for e in exempt_taxes])
|
|
|
|
def generate_invoice_ubl_xml_etree(self, version="2.1"):
|
|
self.ensure_one()
|
|
if self.move_type == "out_invoice":
|
|
nsmap, ns = self._ubl_get_nsmap_namespace("Invoice-2", version=version)
|
|
xml_root = etree.Element("Invoice", nsmap=nsmap)
|
|
elif self.move_type == "out_refund":
|
|
nsmap, ns = self._ubl_get_nsmap_namespace("CreditNote-2", version=version)
|
|
xml_root = etree.Element("CreditNote", nsmap=nsmap)
|
|
self._ubl_add_header(xml_root, ns, version=version)
|
|
if version == "2.1":
|
|
self._ubl_add_buyer_reference(xml_root, ns, version=version)
|
|
self._ubl_add_order_reference(xml_root, ns, version=version)
|
|
self._ubl_add_contract_document_reference(xml_root, ns, version=version)
|
|
self._ubl_add_attachments(xml_root, ns, version=version)
|
|
self._ubl_add_supplier_party(
|
|
False,
|
|
self.company_id,
|
|
"AccountingSupplierParty",
|
|
xml_root,
|
|
ns,
|
|
version=version,
|
|
)
|
|
self._ubl_add_customer_party(
|
|
self.partner_id,
|
|
False,
|
|
"AccountingCustomerParty",
|
|
xml_root,
|
|
ns,
|
|
version=version,
|
|
)
|
|
# the field 'partner_shipping_id' is defined in the 'sale' module
|
|
if hasattr(self, "partner_shipping_id") and self.partner_shipping_id:
|
|
self._ubl_add_delivery(self.partner_shipping_id, xml_root, ns)
|
|
if self.move_type == "out_invoice":
|
|
# Put paymentmeans block even when invoice is paid ?
|
|
payment_identifier = self.get_payment_identifier()
|
|
self._ubl_add_payment_means(
|
|
self.partner_bank_id,
|
|
self.payment_mode_id,
|
|
self.invoice_date_due,
|
|
xml_root,
|
|
ns,
|
|
payment_identifier=payment_identifier,
|
|
version=version,
|
|
)
|
|
if self.invoice_payment_term_id:
|
|
self._ubl_add_payment_terms(
|
|
self.invoice_payment_term_id, xml_root, ns, version=version
|
|
)
|
|
self._ubl_add_tax_total(xml_root, ns, version=version)
|
|
self._ubl_add_legal_monetary_total(xml_root, ns, version=version)
|
|
|
|
line_number = 0
|
|
invoice_lines = self.invoice_line_ids.filtered(
|
|
lambda line: line.display_type not in ("line_note", "line_section")
|
|
)
|
|
for iline in invoice_lines:
|
|
line_number += 1
|
|
self._ubl_add_invoice_line(
|
|
xml_root, iline, line_number, ns, version=version
|
|
)
|
|
return xml_root
|
|
|
|
def generate_ubl_xml_string(self, version="2.1"):
|
|
self.ensure_one()
|
|
assert self.state == "posted"
|
|
assert self.move_type in ("out_invoice", "out_refund")
|
|
logger.debug("Starting to generate UBL XML Invoice file")
|
|
lang = self.get_ubl_lang()
|
|
# The aim of injecting lang in context
|
|
# is to have the content of the XML in the partner's lang
|
|
# but the problem is that the error messages will also be in
|
|
# that lang. But the error messages should almost never
|
|
# happen except the first days of use, so it's probably
|
|
# not worth the additional code to handle the 2 langs
|
|
xml_root = self.with_context(lang=lang).generate_invoice_ubl_xml_etree(
|
|
version=version
|
|
)
|
|
xml_string = etree.tostring(
|
|
xml_root, pretty_print=True, encoding="UTF-8", xml_declaration=True
|
|
)
|
|
if self.move_type == "out_invoice":
|
|
self._ubl_check_xml_schema(xml_string, "Invoice", version=version)
|
|
elif self.move_type == "out_refund":
|
|
self._ubl_check_xml_schema(xml_string, "CreditNote", version=version)
|
|
logger.debug(
|
|
"Invoice UBL XML file generated for account invoice ID %d " "(state %s)",
|
|
self.id,
|
|
self.state,
|
|
)
|
|
logger.debug(xml_string.decode("utf-8"))
|
|
return xml_string
|
|
|
|
def get_ubl_filename(self, version="2.1"):
|
|
"""This method is designed to be inherited"""
|
|
if self.move_type == "out_invoice":
|
|
return "UBL-Invoice-%s.xml" % version
|
|
elif self.move_type == "out_refund":
|
|
return "UBL-CreditNote-%s.xml" % version
|
|
|
|
def get_ubl_version(self):
|
|
return self.env.context.get("ubl_version") or "2.1"
|
|
|
|
def get_ubl_lang(self):
|
|
self.ensure_one()
|
|
return self.partner_id.lang or "en_US"
|
|
|
|
def _embed_ubl_xml_in_pdf(self, pdf_stream):
|
|
self.ensure_one()
|
|
if self.is_ubl_sale_invoice_posted():
|
|
version = self.get_ubl_version()
|
|
xml_filename = self.get_ubl_filename(version=version)
|
|
xml_string = self.generate_ubl_xml_string(version=version)
|
|
pdf_content = pdf_stream["stream"].getvalue()
|
|
new_content = self.env["pdf.helper"].pdf_embed_xml(
|
|
pdf_content,
|
|
xml_filename,
|
|
xml_string,
|
|
)
|
|
# Replace the current content.
|
|
pdf_stream["stream"].close()
|
|
pdf_stream["stream"] = io.BytesIO(new_content)
|
|
|
|
def attach_ubl_xml_file_button(self):
|
|
self.ensure_one()
|
|
assert self.move_type in ("out_invoice", "out_refund")
|
|
assert self.state == "posted"
|
|
version = self.get_ubl_version()
|
|
xml_string = self.generate_ubl_xml_string(version=version)
|
|
filename = self.get_ubl_filename(version=version)
|
|
attach = (
|
|
self.env["ir.attachment"]
|
|
.with_context(**{})
|
|
.create(
|
|
{
|
|
"name": filename,
|
|
"res_id": self.id,
|
|
"res_model": self._name,
|
|
"datas": base64.b64encode(xml_string),
|
|
# If default_type = 'out_invoice' in context, 'type'
|
|
# would take 'out_invoice' value by default !
|
|
"type": "binary",
|
|
}
|
|
)
|
|
)
|
|
action = self.env["ir.attachment"].action_get()
|
|
action.update({"res_id": attach.id, "views": False, "view_mode": "form,tree"})
|
|
return action
|
|
|
|
def is_ubl_sale_invoice_posted(self):
|
|
self.ensure_one()
|
|
is_ubl = self.company_id.xml_format_in_pdf_invoice == "ubl"
|
|
if is_ubl and self.is_sale_document() and self.state == "posted":
|
|
return True
|
|
return False
|