diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py
index 354e43bb0d7..db72d23f5f0 100644
--- a/addons/account/models/account_journal.py
+++ b/addons/account/models/account_journal.py
@@ -546,10 +546,8 @@ class AccountJournal(models.Model):
invoices = self.env['account.move']
for attachment in attachments:
- invoice = self.env['account.move'].create({})
attachment.write({'res_model': 'mail.compose.message'})
- invoice.message_post(attachment_ids=[attachment.id])
- invoices += invoice
+ invoices += self._create_invoice_from_single_attachment(attachment)
action_vals = {
'name': _('Generated Documents'),
@@ -565,6 +563,16 @@ class AccountJournal(models.Model):
action_vals['view_mode'] = 'tree,form'
return action_vals
+ def _create_invoice_from_single_attachment(self, attachment):
+ """ Creates an invoice and post the attachment. If the related modules
+ are installed, it will trigger OCR or the import from the EDI.
+
+ :returns: the created invoice.
+ """
+ invoice = self.env['account.move'].create({})
+ invoice.message_post(attachment_ids=[attachment.id])
+ return invoice
+
def _create_secure_sequence(self, sequence_fields):
"""This function creates a no_gap sequence on each journal in self that will ensure
a unique number is given to all posted account.move in such a way that we can always
diff --git a/addons/account/wizard/account_invoice_send.py b/addons/account/wizard/account_invoice_send.py
index 90861b0c91e..56faf87fdd4 100644
--- a/addons/account/wizard/account_invoice_send.py
+++ b/addons/account/wizard/account_invoice_send.py
@@ -84,7 +84,8 @@ class AccountInvoiceSend(models.TransientModel):
def _send_email(self):
if self.is_email:
- self.composer_id.send_mail()
+ # with_context : we don't want to reimport the file we just exported.
+ self.composer_id.with_context(no_new_invoice=True).send_mail()
if self.env.context.get('mark_invoice_as_sent'):
#Salesman send posted invoice, without the right to write
#but they should have the right to change this flag
diff --git a/addons/account_facturx/__init__.py b/addons/account_edi/__init__.py
similarity index 69%
rename from addons/account_facturx/__init__.py
rename to addons/account_edi/__init__.py
index 21ff7c22f3c..7749576f92c 100644
--- a/addons/account_facturx/__init__.py
+++ b/addons/account_edi/__init__.py
@@ -1,3 +1,4 @@
# -*- encoding: utf-8 -*-
from . import models
+from . import wizard
diff --git a/addons/account_edi/__manifest__.py b/addons/account_edi/__manifest__.py
new file mode 100644
index 00000000000..5234e79dc12
--- /dev/null
+++ b/addons/account_edi/__manifest__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+{
+ 'name' : 'Import/Export Invoices From XML/PDF',
+ 'description':"""
+Electronic Data Interchange
+=======================================
+EDI is the electronic interchange of business information using a standardized format.
+
+This is the base module for import and export of invoices in various EDI formats, and the
+the transmission of said documents to various parties involved in the exchange (other company,
+governements, etc.)
+ """,
+ 'version' : '1.0',
+ 'category': 'Accounting/Accounting',
+ 'depends' : ['account'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'wizard/account_invoice_send_views.xml',
+ 'views/account_journal_views.xml'
+ ],
+ 'installable': True,
+ 'application': False,
+ 'auto_install': True,
+}
diff --git a/addons/account_edi/models/__init__.py b/addons/account_edi/models/__init__.py
new file mode 100644
index 00000000000..dea47dee7b9
--- /dev/null
+++ b/addons/account_edi/models/__init__.py
@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+
+from . import account_move
+from . import account_journal
+from . import account_edi_format
+from . import ir_actions_report
+from . import ir_attachment
+from . import mail_template
diff --git a/addons/account_edi/models/account_edi_format.py b/addons/account_edi/models/account_edi_format.py
new file mode 100644
index 00000000000..c36f22e16a3
--- /dev/null
+++ b/addons/account_edi/models/account_edi_format.py
@@ -0,0 +1,300 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields, api
+from odoo.exceptions import UserError
+from odoo.tools.pdf import OdooPdfFileReader, OdooPdfFileWriter
+
+from lxml import etree
+import base64
+import io
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class AccountEdiFormat(models.Model):
+ _name = 'account.edi.format'
+ _description = 'EDI format'
+
+ name = fields.Char()
+ code = fields.Char()
+ hide_on_journal = fields.Selection([('import_export', 'Import/Export'), ('import', 'Import Only')], default='import_export', help='used to hide this EDI format on journals')
+
+ _sql_constraints = [
+ ('unique_code', 'unique (code)', 'This code already exists')
+ ]
+
+ ####################################################
+ # Export method to override based on EDI Format
+ ####################################################
+
+ def _export_invoice_to_attachment(self, invoice):
+ """ Create the file content representing the invoice.
+
+ :param invoice: the invoice to encode.
+ :returns: a dictionary (values are compatible to create an ir.attachment)
+ * name : the name of the file
+ * datas : the content of the file,
+ * res_model : 'account.move',
+ * res_id: the id of invoice
+ * mimetype : the mimetype of the attachment
+ """
+ # TO OVERRIDE
+ self.ensure_one()
+ return False
+
+ def _export_invoice_to_embed_to_pdf(self, pdf_content, invoice):
+ """ Create the file content representing the invoice when it's destined
+ to be embed into a pdf.
+ - default: creates the default EDI document (_export_invoice_to_attachment).
+ - Should return False if this EDI format should not be embedded.
+ - Should be overriden only if a specific behavior (for example,
+ include the pdf content inside the file).
+
+ :param pdf_content: the pdf before any EDI format was added.
+ :param invoice: the invoice to add.
+ :returns: a dictionary or False if this EDI format must not be embedded to pdf.
+ * name : the name of the file
+ * datas : the content of the file,
+ * res_model : 'account.move',
+ * res_id: the id of invoice
+ * mimetype : the mimetype of the attachment
+ """
+ # TO OVERRIDE
+ self.ensure_one()
+ return self._export_invoice_to_attachment(invoice)
+
+ ####################################################
+ # Import methods to override based on EDI Format
+ ####################################################
+
+ def _create_invoice_from_xml_tree(self, filename, tree):
+ """ Create a new invoice with the data inside the xml.
+
+ :param filename: The name of the xml.
+ :param tree: The tree of the xml to import.
+ :returns: The created invoice.
+ """
+ # TO OVERRIDE
+ self.ensure_one()
+ return self.env['account.move']
+
+ def _update_invoice_from_xml_tree(self, filename, tree, invoice):
+ """ Update an existing invoice with the data inside the xml.
+
+ :param filename: The name of the xml.
+ :param tree: The tree of the xml to import.
+ :param invoice: The invoice to update.
+ :returns: The updated invoice.
+ """
+ # TO OVERRIDE
+ self.ensure_one()
+ return self.env['account.move']
+
+ def _create_invoice_from_pdf_reader(self, filename, reader):
+ """ Create a new invoice with the data inside a pdf.
+
+ :param filename: The name of the pdf.
+ :param reader: The OdooPdfFileReader of the pdf to import.
+ :returns: The created invoice.
+ """
+ # TO OVERRIDE
+ self.ensure_one()
+
+ return self.env['account.move']
+
+ def _update_invoice_from_pdf_reader(self, filename, reader, invoice):
+ """ Update an existing invoice with the data inside the pdf.
+
+ :param filename: The name of the pdf.
+ :param reader: The OdooPdfFileReader of the pdf to import.
+ :param invoice: The invoice to update.
+ :returns: The updated invoice.
+ """
+ # TO OVERRIDE
+ self.ensure_one()
+ return self.env['account.move']
+
+ ####################################################
+ # Export Internal methods (not meant to be overridden)
+ ####################################################
+
+ def _embed_edis_to_pdf(self, pdf_content, invoice):
+ """ Create the EDI document of the invoice and embed it in the pdf_content.
+
+ :param pdf_content: the bytes representing the pdf to add the EDIs to.
+ :param invoice: the invoice to generate the EDI from.
+ :returns: the same pdf_content with the EDI of the invoice embed in it.
+ """
+ attachments = []
+ for edi_format in self:
+ try:
+ vals = edi_format._export_invoice_to_embed_to_pdf(pdf_content, invoice)
+ except:
+ continue
+ if vals:
+ attachments.append(vals)
+
+ if attachments:
+ # Add the attachments to the pdf file
+ reader_buffer = io.BytesIO(pdf_content)
+ reader = OdooPdfFileReader(reader_buffer)
+ writer = OdooPdfFileWriter()
+ writer.cloneReaderDocumentRoot(reader)
+ for vals in attachments:
+ writer.addAttachment(vals['name'], vals['datas'])
+ buffer = io.BytesIO()
+ writer.write(buffer)
+ pdf_content = buffer.getvalue()
+ reader_buffer.close()
+ buffer.close()
+ return pdf_content
+
+ def _create_ir_attachments(self, invoice):
+ """ Create ir.attachment for the EDIs from invoice.
+
+ :param invoice: the invoice to generate the EDI from.
+ :returns: the newly created attachments.
+ """
+ attachment_vals_list = []
+ for edi_format in self:
+ vals = edi_format._export_invoice_to_attachment(invoice)
+ if vals:
+ vals['datas'] = base64.encodebytes(vals['datas'])
+ vals['edi_format_id'] = edi_format._origin.id
+ attachment_vals_list.append(vals)
+ res = self.env['ir.attachment'].create(attachment_vals_list)
+ invoice.edi_document_ids |= res
+ return res
+
+ ####################################################
+ # Import Internal methods (not meant to be overridden)
+ ####################################################
+
+ def _decode_xml(self, filename, content):
+ """Decodes an xml into a list of one dictionary representing an attachment.
+
+ :param filename: The name of the xml.
+ :param attachment: The xml as a string.
+ :returns: A list with a dictionary.
+ * filename: The name of the attachment.
+ * content: The content of the attachment.
+ * type: The type of the attachment.
+ * xml_tree: The tree of the xml if type is xml.
+ * pdf_reader: The pdf_reader if type is pdf.
+ """
+ to_process = []
+ try:
+ xml_tree = etree.fromstring(content)
+ except Exception as e:
+ _logger.exception("Error when converting the xml content to etree: %s" % e)
+ return to_process
+ if len(xml_tree):
+ to_process.append({
+ 'filename': filename,
+ 'content': content,
+ 'type': 'xml',
+ 'xml_tree': xml_tree,
+ })
+ return to_process
+
+ def _decode_pdf(self, filename, content):
+ """Decodes a pdf and unwrap sub-attachment into a list of dictionary each representing an attachment.
+
+ :param filename: The name of the pdf.
+ :param content: The bytes representing the pdf.
+ :returns: A list of dictionary for each attachment.
+ * filename: The name of the attachment.
+ * content: The content of the attachment.
+ * type: The type of the attachment.
+ * xml_tree: The tree of the xml if type is xml.
+ * pdf_reader: The pdf_reader if type is pdf.
+ """
+ to_process = []
+ try:
+ buffer = io.BytesIO(content)
+ pdf_reader = OdooPdfFileReader(buffer)
+ except Exception as e:
+ # Malformed pdf
+ _logger.exception("Error when reading the pdf: %s" % e)
+ return to_process
+
+ # Process embedded files.
+ for xml_name, content in pdf_reader.getAttachments():
+ to_process.extend(self._decode_xml(xml_name, content))
+
+ # Process the pdf itself.
+ to_process.append({
+ 'filename': filename,
+ 'content': content,
+ 'type': 'pdf',
+ 'pdf_reader': pdf_reader,
+ })
+
+ return to_process
+
+ def _decode_attachment(self, attachment):
+ """Decodes an ir.attachment and unwrap sub-attachment into a list of dictionary each representing an attachment.
+
+ :param attachment: An ir.attachment record.
+ :returns: A list of dictionary for each attachment.
+ * filename: The name of the attachment.
+ * content: The content of the attachment.
+ * type: The type of the attachment.
+ * xml_tree: The tree of the xml if type is xml.
+ * pdf_reader: The pdf_reader if type is pdf.
+ """
+ content = base64.b64decode(attachment.with_context(bin_size=False).datas)
+ to_process = []
+
+ if 'pdf' in attachment.mimetype:
+ to_process.extend(self._decode_pdf(attachment.name, content))
+ elif 'xml' in attachment.mimetype:
+ to_process.extend(self._decode_xml(attachment.name, content))
+
+ return to_process
+
+ def _create_invoice_from_attachment(self, attachment):
+ """Decodes an ir.attachment to create an invoice.
+
+ :param attachment: An ir.attachment record.
+ :returns: The invoice where to import data.
+ """
+ for file_data in self._decode_attachment(attachment):
+ for edi_format in self:
+ res = False
+ if file_data['type'] == 'xml':
+ res = edi_format._create_invoice_from_xml_tree(file_data['filename'], file_data['xml_tree'])
+ elif file_data['type'] == 'pdf':
+ res = edi_format._create_invoice_from_pdf_reader(file_data['filename'], file_data['pdf_reader'])
+ file_data['pdf_reader'].stream.close()
+ if res:
+ if 'extract_state' in res:
+ # Bypass the OCR to prevent overwriting data when an EDI was succesfully imported.
+ # TODO : remove when we integrate the OCR to the EDI flow.
+ res.write({'extract_state': 'done'})
+ return res
+ return self.env['account.move']
+
+ def _update_invoice_from_attachment(self, attachment, invoice):
+ """Decodes an ir.attachment to update an invoice.
+
+ :param attachment: An ir.attachment record.
+ :returns: The invoice where to import data.
+ """
+ for file_data in self._decode_attachment(attachment):
+ for edi_format in self:
+ res = False
+ if file_data['type'] == 'xml':
+ res = edi_format._update_invoice_from_xml_tree(file_data['filename'], file_data['xml_tree'], invoice)
+ elif file_data['type'] == 'pdf':
+ res = edi_format._update_invoice_from_pdf_reader(file_data['filename'], file_data['pdf_reader'], invoice)
+ file_data['pdf_reader'].stream.close()
+ if res:
+ if 'extract_state' in res:
+ # Bypass the OCR to prevent overwriting data when an EDI was succesfully imported.
+ # TODO : remove when we integrate the OCR to the EDI flow.
+ res.write({'extract_state': 'done'})
+ return res
+ return self.env['account.move']
diff --git a/addons/account_edi/models/account_journal.py b/addons/account_edi/models/account_journal.py
new file mode 100644
index 00000000000..c160d8da249
--- /dev/null
+++ b/addons/account_edi/models/account_journal.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields
+
+
+class AccountJournal(models.Model):
+ _inherit = 'account.journal'
+
+ edi_format_ids = fields.Many2many(comodel_name='account.edi.format',
+ string='Electronic invoicing',
+ help='Send XML/EDI invoices',
+ domain="[('hide_on_journal', '=', 'import_export')]")
+
+ def _create_invoice_from_single_attachment(self, attachment):
+ invoice = self.env['account.edi.format'].search([])._create_invoice_from_attachment(attachment)
+ if invoice:
+ # with_context: we don't want to import the attachment since the invoice was just created from it.
+ invoice.with_context(no_new_invoice=True).message_post(attachment_ids=[attachment.id])
+ return invoice
+ return super()._create_invoice_from_single_attachment(attachment)
diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py
new file mode 100644
index 00000000000..b44a375673b
--- /dev/null
+++ b/addons/account_edi/models/account_move.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, fields
+
+
+class AccountMove(models.Model):
+ _inherit = 'account.move'
+
+ edi_document_ids = fields.One2many('ir.attachment', 'res_id', domain=[('res_model', '=', 'account.move'), ('edi_format_id', '!=', False)])
+
+ def post(self):
+ # OVERRIDE
+ # Generate the electronic documents for the move.
+ existing_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', 'account.move'),
+ ('res_id', 'in', self.ids),
+ ('edi_format_id', 'in', self.journal_id.edi_format_ids.ids)])
+ existing_attachments.unlink()
+ res = super(AccountMove, self).post()
+ for move in self:
+ move.journal_id.edi_format_ids._create_ir_attachments(move)
+
+ return res
+
+ @api.returns('mail.message', lambda value: value.id)
+ def message_post(self, **kwargs):
+ # OVERRIDE
+ # When posting a message, analyse the attachment to check if it is an EDI document and update the invoice
+ # with the data.
+ res = super(AccountMove, self).message_post(**kwargs)
+
+ if len(self) != 1 or self.env.context.get('no_new_invoice') or not self.is_invoice(include_receipts=True):
+ return res
+
+ attachments = self.env['ir.attachment'].browse(kwargs.get('attachment_ids', []))
+ odoobot = self.env.ref('base.partner_root')
+ if attachments and self.state != 'draft':
+ self.message_post(body='The invoice is not a draft, it was not updated from the attachment.',
+ message_type='comment',
+ subtype_xmlid='mail.mt_note',
+ author_id=odoobot.id)
+ return res
+ if attachments and self.line_ids:
+ self.message_post(body='The invoice already contains lines, it was not updated from the attachment.',
+ message_type='comment',
+ subtype_xmlid='mail.mt_note',
+ author_id=odoobot.id)
+ return res
+
+ for attachment in attachments:
+ invoice = self.env['account.edi.format'].search([])._update_invoice_from_attachment(attachment, self)
+ break
+
+ return res
diff --git a/addons/account_edi/models/ir_actions_report.py b/addons/account_edi/models/ir_actions_report.py
new file mode 100644
index 00000000000..9dcba74099c
--- /dev/null
+++ b/addons/account_edi/models/ir_actions_report.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api, _
+
+
+class IrActionsReport(models.Model):
+ _inherit = 'ir.actions.report'
+
+ def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None):
+ # OVERRIDE to embed some EDI documents inside the PDF.
+ if self.model == 'account.move' and res_ids and len(res_ids) == 1 and pdf_content:
+ invoice = self.env['account.move'].browse(res_ids)
+ if invoice.is_sale_document() and invoice.state != 'draft':
+ pdf_content = invoice.journal_id.edi_format_ids._embed_edis_to_pdf(pdf_content, invoice)
+
+ return super(IrActionsReport, self)._post_pdf(save_in_attachment, pdf_content=pdf_content, res_ids=res_ids)
diff --git a/addons/account_edi/models/ir_attachment.py b/addons/account_edi/models/ir_attachment.py
new file mode 100644
index 00000000000..146411bc28e
--- /dev/null
+++ b/addons/account_edi/models/ir_attachment.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, fields
+
+
+class IrAttachment(models.Model):
+ _inherit = 'ir.attachment'
+
+ edi_format_id = fields.Many2one('account.edi.format')
diff --git a/addons/account_edi/models/mail_template.py b/addons/account_edi/models/mail_template.py
new file mode 100644
index 00000000000..209b9160e0e
--- /dev/null
+++ b/addons/account_edi/models/mail_template.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, models
+
+
+class MailTemplate(models.Model):
+ _inherit = "mail.template"
+
+ def generate_email(self, res_ids, fields):
+ res = super().generate_email(res_ids, fields)
+
+ multi_mode = True
+ if isinstance(res_ids, int):
+ res_ids = [res_ids]
+ multi_mode = False
+
+ if self.model != 'account.move':
+ return res
+
+ existing_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', 'account.move'),
+ ('res_id', 'in', res_ids),
+ ('edi_format_id', '!=', False)])
+
+ for record in self.env[self.model].browse(res_ids):
+ available_formats = existing_attachments.filtered(lambda a: a.res_id == record.id)
+ missing_formats = record.journal_id.edi_format_ids.filtered(lambda f: f._origin.id not in available_formats.edi_format_id.ids)
+ new_attachments = missing_formats._create_ir_attachments(record)
+ available_formats |= new_attachments
+ (res[record.id] if multi_mode else res).setdefault('attachments', []).extend([(a.name, a.datas) for a in available_formats])
+
+ return res
diff --git a/addons/account_edi/security/ir.model.access.csv b/addons/account_edi/security/ir.model.access.csv
new file mode 100644
index 00000000000..71c1f8bda06
--- /dev/null
+++ b/addons/account_edi/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_account_edi_format_readonly,account.edi.format,model_account_edi_format,,1,0,0,0
diff --git a/addons/account_edi/views/account_journal_views.xml b/addons/account_edi/views/account_journal_views.xml
new file mode 100644
index 00000000000..cd466c6ee2f
--- /dev/null
+++ b/addons/account_edi/views/account_journal_views.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ account.journal.form.inherited
+ account.journal
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/account_edi/wizard/__init__.py b/addons/account_edi/wizard/__init__.py
new file mode 100644
index 00000000000..edaf0442f27
--- /dev/null
+++ b/addons/account_edi/wizard/__init__.py
@@ -0,0 +1 @@
+from . import account_invoice_send
diff --git a/addons/account_edi/wizard/account_invoice_send.py b/addons/account_edi/wizard/account_invoice_send.py
new file mode 100644
index 00000000000..3cf5e2cef75
--- /dev/null
+++ b/addons/account_edi/wizard/account_invoice_send.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+import base64
+
+
+class AccountInvoiceSend(models.TransientModel):
+ _inherit = 'account.invoice.send'
+ _description = 'Account Invoice Send'
+
+ edi_format_ids = fields.Many2many(related='invoice_ids.journal_id.edi_format_ids', string="Electronic invoicing")
diff --git a/addons/account_edi/wizard/account_invoice_send_views.xml b/addons/account_edi/wizard/account_invoice_send_views.xml
new file mode 100644
index 00000000000..023a56ef709
--- /dev/null
+++ b/addons/account_edi/wizard/account_invoice_send_views.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ account.invoice.send.form.inherited.edi
+ account.invoice.send
+
+
+
+
+
+
+
+
+
+ Please, set electronic invoicing formats on the journal
+
+
+
+
+
+
+
+
+
diff --git a/addons/account_edi_facturx/__init__.py b/addons/account_edi_facturx/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/addons/account_edi_facturx/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/addons/account_facturx/__manifest__.py b/addons/account_edi_facturx/__manifest__.py
similarity index 64%
rename from addons/account_facturx/__manifest__.py
rename to addons/account_edi_facturx/__manifest__.py
index a580e617a4c..3e7e16ffb11 100644
--- a/addons/account_facturx/__manifest__.py
+++ b/addons/account_edi_facturx/__manifest__.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
{
- 'name' : 'Import Bills/Invoices From XML',
+ 'name' : 'Import/Export invoices with Factur-X',
'version' : '1.0',
'category': 'Accounting/Accounting',
- 'depends' : ['account'],
+ 'depends' : ['account_edi'],
'data': [
+ 'data/account_edi_data.xml',
'data/facturx_templates.xml',
],
'installable': True,
diff --git a/addons/account_edi_facturx/data/account_edi_data.xml b/addons/account_edi_facturx/data/account_edi_data.xml
new file mode 100644
index 00000000000..d581c468a5c
--- /dev/null
+++ b/addons/account_edi_facturx/data/account_edi_data.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Factur-X (FR)
+ facturx_1_0_05
+
+
+
+
\ No newline at end of file
diff --git a/addons/account_facturx/data/facturx_templates.xml b/addons/account_edi_facturx/data/facturx_templates.xml
similarity index 97%
rename from addons/account_facturx/data/facturx_templates.xml
rename to addons/account_edi_facturx/data/facturx_templates.xml
index 98b49ee71b9..1ead8f38f46 100644
--- a/addons/account_facturx/data/facturx_templates.xml
+++ b/addons/account_edi_facturx/data/facturx_templates.xml
@@ -133,7 +133,7 @@
-
+
@@ -141,7 +141,7 @@
-
+
@@ -154,7 +154,7 @@
-
+
@@ -174,7 +174,7 @@
-
+
diff --git a/addons/account_edi_facturx/models/__init__.py b/addons/account_edi_facturx/models/__init__.py
new file mode 100644
index 00000000000..9961df8933c
--- /dev/null
+++ b/addons/account_edi_facturx/models/__init__.py
@@ -0,0 +1 @@
+from . import account_edi_format
diff --git a/addons/account_facturx/models/account_move.py b/addons/account_edi_facturx/models/account_edi_format.py
similarity index 60%
rename from addons/account_facturx/models/account_move.py
rename to addons/account_edi_facturx/models/account_edi_format.py
index abab584a0b9..032675fc132 100644
--- a/addons/account_facturx/models/account_move.py
+++ b/addons/account_edi_facturx/models/account_edi_format.py
@@ -10,23 +10,22 @@ from lxml import etree
from PyPDF2 import PdfFileReader
import io
-import base64
import logging
+
_logger = logging.getLogger(__name__)
DEFAULT_FACTURX_DATE_FORMAT = '%Y%m%d'
-class AccountMove(models.Model):
- _inherit = 'account.move'
+class AccountEdiFormat(models.Model):
+ _inherit = 'account.edi.format'
- def _export_as_facturx_xml(self):
- ''' Create the Factur-x xml file content.
- :return: The XML content as str.
- '''
+ def _export_invoice_to_attachment(self, invoice):
self.ensure_one()
+ if self.code != 'facturx_1_0_05':
+ return super()._export_invoice_to_attachment(invoice)
def format_date(dt):
# Format the date in the Factur-x standard.
@@ -39,35 +38,59 @@ class AccountMove(models.Model):
# Create file content.
template_values = {
- 'record': self,
+ 'record': invoice,
'format_date': format_date,
'format_monetary': format_monetary,
}
- content = self.env.ref('account_facturx.account_invoice_facturx_export')._render(template_values)
- return b"" + content
+ xml_content = b""
+ xml_content += self.env.ref('account_edi_facturx.account_invoice_facturx_export')._render(template_values)
+ xml_name = '%s_facturx.xml' % (invoice.name.replace('/', '_'))
+ return {'name': xml_name,
+ 'datas': xml_content,
+ 'res_model': 'account.move',
+ 'res_id': invoice._origin.id,
+ 'mimetype': 'application/xml'
+ }
- def _import_facturx_invoice(self, tree):
- ''' Extract invoice values from the Factur-x xml tree passed as parameter.
+ def _is_facturx(self, filename, tree):
+ return self.code == 'facturx_1_0_05' and tree.tag == '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice'
+
+ def _create_invoice_from_xml_tree(self, filename, tree):
+ self.ensure_one()
+ if self._is_facturx(filename, tree):
+ return self._import_facturx(tree, self.env['account_move'])
+ return super()._create_invoice_from_xml_tree(filename, tree)
+
+ def _update_invoice_from_xml_tree(self, filename, tree, invoice):
+ self.ensure_one()
+ if self._is_facturx(filename, tree):
+ return self._import_facturx(tree, invoice)
+ return super()._update_invoice_from_xml_tree(filename, tree, invoice)
+
+ def _import_facturx(self, tree, invoice):
+ """ Decodes a factur-x invoice into an invoice.
+
+ :param tree: the factur-x tree to decode.
+ :param invoice: the invoice to update or an empty recordset.
+ :returns: the invoice where the factur-x data was imported.
+ """
- :param tree: The tree of the Factur-x xml file.
- :return: A dictionary containing account.invoice values to create/update it.
- '''
amount_total_import = None
- default_type = False
- if self._context.get('default_journal_id'):
+ default_move_type = False
+ if invoice._context.get('default_journal_id'):
journal = self.env['account.journal'].browse(self.env.context['default_journal_id'])
- default_type = 'out_invoice' if journal.type == 'sale' else 'in_invoice'
- elif self._context.get('default_move_type'):
- default_type = self._context['default_move_type']
- elif self.move_type in self.env['account.move'].get_invoice_types(include_receipts=True):
+ default_move_type = 'out_invoice' if journal.type == 'sale' else 'in_invoice'
+ elif invoice._context.get('default_move_type'):
+ default_move_type = self._context['default_move_type']
+ elif invoice.move_type in self.env['account.move'].get_invoice_types(include_receipts=True):
# in case an attachment is saved on a draft invoice previously created, we might
# have lost the default value in context but the type was already set
- default_type = self.move_type
+ default_move_type = invoice.move_type
- if not default_type:
+ if not default_move_type:
raise UserError(_("No information about the journal or the type of invoice is passed"))
- if default_type == 'entry':
+ if default_move_type == 'entry':
return
# Total amount.
@@ -82,31 +105,31 @@ class AccountMove(models.Model):
elements = tree.xpath('//rsm:ExchangedDocument/ram:TypeCode', namespaces=tree.nsmap)
type_code = elements[0].text
- default_type.replace('_refund', '_invoice')
+ default_move_type.replace('_refund', '_invoice')
if type_code == '381':
- default_type = 'out_refund' if default_type == 'out_invoice' else 'in_refund'
+ default_move_type = 'out_refund' if default_move_type == 'out_invoice' else 'in_refund'
refund_sign = -1
else:
# Handle 'b' refund mode.
if total_amount < 0:
- default_type = 'out_refund' if default_type == 'out_invoice' else 'in_refund'
- refund_sign = -1 if 'refund' in default_type else 1
+ default_move_type = 'out_refund' if default_move_type == 'out_invoice' else 'in_refund'
+ refund_sign = -1 if 'refund' in default_move_type else 1
# Write the type as the journal entry is already created.
- self.move_type = default_type
+ invoice.move_type = default_move_type
# self could be a single record (editing) or be empty (new).
- with Form(self.with_context(default_move_type=default_type)) as invoice_form:
+ with Form(invoice.with_context(default_move_type=default_move_type)) as invoice_form:
# Partner (first step to avoid warning 'Warning! You must first select a partner.').
partner_type = invoice_form.journal_id.type == 'purchase' and 'SellerTradeParty' or 'BuyerTradeParty'
- elements = tree.xpath('//ram:'+partner_type+'/ram:SpecifiedTaxRegistration/ram:ID', namespaces=tree.nsmap)
+ elements = tree.xpath('//ram:' + partner_type + '/ram:SpecifiedTaxRegistration/ram:ID', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search([('vat', '=', elements[0].text)], limit=1)
if not partner:
- elements = tree.xpath('//ram:'+partner_type+'/ram:Name', namespaces=tree.nsmap)
+ elements = tree.xpath('//ram:' + partner_type + '/ram:Name', namespaces=tree.nsmap)
partner_name = elements and elements[0].text
partner = elements and self.env['res.partner'].search([('name', 'ilike', partner_name)], limit=1)
if not partner:
- elements = tree.xpath('//ram:'+partner_type+'//ram:URIID[@schemeID=\'SMTP\']', namespaces=tree.nsmap)
+ elements = tree.xpath('//ram:' + partner_type + '//ram:URIID[@schemeID=\'SMTP\']', namespaces=tree.nsmap)
partner = elements and self.env['res.partner'].search([('email', '=', elements[0].text)], limit=1)
if partner:
invoice_form.partner_id = partner
@@ -223,98 +246,3 @@ class AccountMove(models.Model):
invoice_line_form.price_unit = amount_total_import
return invoice_form.save()
-
- @api.returns('mail.message', lambda value: value.id)
- def message_post(self, **kwargs):
- # OVERRIDE
- # /!\ 'default_res_id' in self._context is used to don't process attachment when using a form view.
- res = super(AccountMove, self).message_post(**kwargs)
-
- if not self.env.context.get('no_new_invoice') and len(self) == 1 and self.state == 'draft' and (
- self.env.context.get('default_move_type', self.move_type) in self.env['account.move'].get_invoice_types(include_receipts=True)
- or self.env['account.journal'].browse(self.env.context.get('default_journal_id')).type in ('sale', 'purchase')
- ):
- for attachment in self.env['ir.attachment'].browse(kwargs.get('attachment_ids', [])):
- self._create_invoice_from_attachment(attachment)
- return res
-
- def _create_invoice_from_attachment(self, attachment):
- if 'pdf' in attachment.mimetype:
- for move in self:
- move._create_invoice_from_pdf(attachment)
- if 'xml' in attachment.mimetype:
- for move in self:
- move._create_invoice_from_xml(attachment)
-
- def _create_invoice_from_pdf(self, attachment):
- content = base64.b64decode(attachment.datas)
-
- with io.BytesIO(content) as buffer:
- try:
- reader = PdfFileReader(buffer)
-
- # Search for Factur-x embedded file.
- if reader.trailer['/Root'].get('/Names') and reader.trailer['/Root']['/Names'].get('/EmbeddedFiles'):
- # N.B: embedded_files looks like:
- # ['file.xml', {'/Type': '/Filespec', '/F': 'file.xml', '/EF': {'/F': IndirectObject(22, 0)}}]
- embedded_files = reader.trailer['/Root']['/Names']['/EmbeddedFiles']['/Names']
- # '[::2]' because it's a list [fn_1, content_1, fn_2, content_2, ..., fn_n, content_2]
- for filename_obj, content_obj in list(zip(embedded_files, embedded_files[1:]))[::2]:
- content = content_obj.getObject()['/EF']['/F'].getData()
-
- if filename_obj == 'factur-x.xml':
- try:
- tree = etree.fromstring(content)
- except Exception:
- continue
-
- self._import_facturx_invoice(tree)
- self._remove_ocr_option()
- buffer.close()
- except UserError as e:
- raise e
- except Exception as e:
- # Malformed pdf
- _logger.exception(e)
-
- @api.model
- def _get_xml_decoders(self):
- ''' List of usable decoders to extract invoice from attachments.
-
- :return: a list of triplet (xml_type, check_func, decode_func)
- * xml_type: The format name, e.g 'UBL 2.1'
- * check_func: A function taking an etree and a file name as parameter and returning a dict:
- * flag: The etree is part of this format.
- * error: Error message.
- * decode_func: A function taking an etree as parameter and returning an invoice record.
- '''
- # TO BE OVERWRITTEN
- return []
-
- def _create_invoice_from_xml(self, attachment):
- decoders = self._get_xml_decoders()
-
- # Convert attachment -> etree
- content = base64.b64decode(attachment.datas)
- try:
- tree = etree.fromstring(content)
- except Exception:
- _logger.exception('The xml file is badly formatted : {}'.format(attachment.name))
-
- for xml_type, check_func, decode_func in decoders:
- check_res = check_func(tree, attachment.name)
-
- if check_res.get('flag') and not check_res.get('error'):
- invoice_ids = decode_func(tree)
- if invoice_ids:
- invoice_ids._remove_ocr_option()
- break
-
- try:
- return invoice_ids
- except UnboundLocalError:
- _logger.exception('No decoder was found for the xml file: {}. The file is badly formatted, not supported or the decoder is not installed'.format(attachment.name))
-
- def _remove_ocr_option(self):
- if 'extract_state' in self:
- self.write({'extract_state': 'done'})
diff --git a/addons/account_edi_ubl/__init__.py b/addons/account_edi_ubl/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/addons/account_edi_ubl/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/addons/account_edi_ubl/__manifest__.py b/addons/account_edi_ubl/__manifest__.py
new file mode 100644
index 00000000000..afa10b999c1
--- /dev/null
+++ b/addons/account_edi_ubl/__manifest__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+{
+ 'name' : 'Import/Export invoices with generic UBL',
+ 'version' : '1.0',
+ 'category': 'Accounting/Accounting',
+ 'depends' : ['account_edi'],
+ 'data': [
+ 'data/ubl_templates.xml',
+ ],
+ 'installable': True,
+ 'application': False,
+ 'auto_install': True,
+}
diff --git a/addons/account_edi_ubl/data/ubl_templates.xml b/addons/account_edi_ubl/data/ubl_templates.xml
new file mode 100644
index 00000000000..e591603b070
--- /dev/null
+++ b/addons/account_edi_ubl/data/ubl_templates.xml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ VAT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/account_edi_ubl/models/__init__.py b/addons/account_edi_ubl/models/__init__.py
new file mode 100644
index 00000000000..8c67bc9d5f4
--- /dev/null
+++ b/addons/account_edi_ubl/models/__init__.py
@@ -0,0 +1,2 @@
+from . import account_move
+from . import account_edi_format
diff --git a/addons/l10n_be_edi/models/account_invoice.py b/addons/account_edi_ubl/models/account_edi_format.py
similarity index 70%
rename from addons/l10n_be_edi/models/account_invoice.py
rename to addons/account_edi_ubl/models/account_edi_format.py
index f0acf9ecb9c..7aa98dc9806 100644
--- a/addons/l10n_be_edi/models/account_invoice.py
+++ b/addons/account_edi_ubl/models/account_edi_format.py
@@ -1,52 +1,55 @@
# -*- coding: utf-8 -*-
-
-from odoo import api, fields, models, tools, _
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import api, models, fields, tools, _
+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, float_repr
from odoo.tests.common import Form
+from odoo.exceptions import UserError
from odoo.osv import expression
+from datetime import datetime
+
import logging
+
_logger = logging.getLogger(__name__)
-class AccountMove(models.Model):
- _inherit = 'account.move'
+class AccountEdiFormat(models.Model):
+ _inherit = 'account.edi.format'
- @api.model
- def _get_ubl_namespaces(self, tree):
- ''' If the namespace is declared with xmlns='...', the namespaces map contains the 'None' key that causes an
- TypeError: empty namespace prefix is not supported in XPath
- Then, we need to remap arbitrarily this key.
+ def _import_ubl(self, tree, invoice):
+ """ Decodes an UBL invoice into an invoice.
- :param tree: An instance of etree.
- :return: The namespaces map without 'None' key.
- '''
- namespaces = tree.nsmap
- namespaces['inv'] = namespaces.pop(None)
- return namespaces
+ :param tree: the UBL tree to decode.
+ :param invoice: the invoice to update or an empty recordset.
+ :returns: the invoice where the UBL data was imported.
+ """
- @api.model
- def _detect_ubl_2_1(self, tree, file_name):
- # Quick check the tree looks like an UBL 2.1 file.
- flag = tree.tag == '{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice'
- error = None
+ def _get_ubl_namespaces():
+ ''' If the namespace is declared with xmlns='...', the namespaces map contains the 'None' key that causes an
+ TypeError: empty namespace prefix is not supported in XPath
+ Then, we need to remap arbitrarily this key.
- return {'flag': flag, 'error': error}
+ :param tree: An instance of etree.
+ :return: The namespaces map without 'None' key.
+ '''
+ namespaces = tree.nsmap
+ namespaces['inv'] = namespaces.pop(None)
+ return namespaces
- @api.model
- def _decode_ubl_2_1(self, tree):
- self.ensure_one()
- namespaces = self._get_ubl_namespaces(tree)
+ namespaces = _get_ubl_namespaces()
+ if not invoice:
+ invoice = self.env['account.move'].create({})
elements = tree.xpath('//cbc:InvoiceTypeCode', namespaces=namespaces)
if elements:
type_code = elements[0].text
- type = 'in_refund' if type_code == '381' else 'in_invoice'
+ move_type = 'in_refund' if type_code == '381' else 'in_invoice'
else:
- type = 'in_invoice'
+ move_type = 'in_invoice'
- default_journal = self.with_context(default_move_type=type)._get_default_journal()
+ default_journal = invoice.with_context(default_move_type=move_type)._get_default_journal()
- with Form(self.with_context(default_move_type=type, default_journal_id=default_journal.id)) as invoice_form:
+ with Form(invoice.with_context(default_move_type=move_type, default_journal_id=default_journal.id)) as invoice_form:
# Reference
elements = tree.xpath('//cbc:ID', namespaces=namespaces)
if elements:
@@ -87,6 +90,8 @@ class AccountMove(models.Model):
if elements:
partner_name = elements[0].text
domains.append([('name', 'ilike', partner_name)])
+ else:
+ partner_name = ''
elements = partner_element.xpath('//cac:AccountingSupplierParty/cac:Party//cbc:Telephone', namespaces=namespaces)
if elements:
partner_telephone = elements[0].text
@@ -104,6 +109,7 @@ class AccountMove(models.Model):
partner = self.env['res.partner'].search(expression.OR(domains), limit=1)
if partner:
invoice_form.partner_id = partner
+ partner_name = partner.name
else:
invoice_form.partner_id = self.env['res.partner']
@@ -116,33 +122,18 @@ class AccountMove(models.Model):
if attachment_name and attachment_data:
attachments |= self.env['ir.attachment'].create({
'name': attachment_name[0].text,
- 'res_id': self.id,
+ 'res_id': invoice.id,
'res_model': 'account.move',
'datas': attachment_data[0].text,
'type': 'binary',
})
if attachments:
- self.with_context(no_new_invoice=True).message_post(attachment_ids=attachments.ids)
+ invoice.with_context(no_new_invoice=True).message_post(attachment_ids=attachments.ids)
# Lines
lines_elements = tree.xpath('//cac:InvoiceLine', namespaces=namespaces)
for eline in lines_elements:
with invoice_form.invoice_line_ids.new() as invoice_line_form:
- # Quantity
- elements = eline.xpath('cbc:InvoicedQuantity', namespaces=namespaces)
- quantity = elements and float(elements[0].text) or 1.0
- invoice_line_form.quantity = quantity
-
- # Price Unit
- elements = eline.xpath('cac:Price/cbc:PriceAmount', namespaces=namespaces)
- invoice_line_form.price_unit = elements and float(elements[0].text) or 0.0
-
- # Name
- elements = eline.xpath('cac:Item/cbc:Description', namespaces=namespaces)
- invoice_line_form.name = elements and elements[0].text or ''
- invoice_line_form.name = invoice_line_form.name.replace('%month%', str(fields.Date.to_date(invoice_form.invoice_date).month)) # TODO: full name in locale
- invoice_line_form.name = invoice_line_form.name.replace('%year%', str(fields.Date.to_date(invoice_form.invoice_date).year))
-
# Product
elements = eline.xpath('cac:Item/cac:SellersItemIdentification/cbc:ID', namespaces=namespaces)
domains = []
@@ -158,6 +149,27 @@ class AccountMove(models.Model):
if product:
invoice_line_form.product_id = product
+ # Quantity
+ elements = eline.xpath('cbc:InvoicedQuantity', namespaces=namespaces)
+ quantity = elements and float(elements[0].text) or 1.0
+ invoice_line_form.quantity = quantity
+
+ # Price Unit
+ elements = eline.xpath('cac:Price/cbc:PriceAmount', namespaces=namespaces)
+ price_unit = elements and float(elements[0].text) or 0.0
+ elements = eline.xpath('cbc:LineExtensionAmount', namespaces=namespaces)
+ line_extension_amount = elements and float(elements[0].text) or 0.0
+ invoice_line_form.price_unit = price_unit or line_extension_amount / invoice_line_form.quantity or 0.0
+
+ # Name
+ elements = eline.xpath('cac:Item/cbc:Description', namespaces=namespaces)
+ if elements and elements[0].text:
+ invoice_line_form.name = elements[0].text
+ invoice_line_form.name = invoice_line_form.name.replace('%month%', str(fields.Date.to_date(invoice_form.invoice_date).month)) # TODO: full name in locale
+ invoice_line_form.name = invoice_line_form.name.replace('%year%', str(fields.Date.to_date(invoice_form.invoice_date).year))
+ else:
+ invoice_line_form.name = "%s (%s)" % (partner_name or '', invoice_form.invoice_date)
+
# Taxes
taxes_elements = eline.xpath('cac:TaxTotal/cac:TaxSubtotal', namespaces=namespaces)
invoice_line_form.tax_ids.clear()
@@ -173,9 +185,3 @@ class AccountMove(models.Model):
invoice_line_form.tax_ids.add(tax)
return invoice_form.save()
-
- @api.model
- def _get_xml_decoders(self):
- # Override
- ubl_decoders = [('UBL 2.1', self._detect_ubl_2_1, self._decode_ubl_2_1)]
- return super(AccountMove, self)._get_xml_decoders() + ubl_decoders
diff --git a/addons/account_edi_ubl/models/account_move.py b/addons/account_edi_ubl/models/account_move.py
new file mode 100644
index 00000000000..ce845c29028
--- /dev/null
+++ b/addons/account_edi_ubl/models/account_move.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo import models
+from odoo.tools import float_repr
+
+
+class AccountMove(models.Model):
+ _inherit = 'account.move'
+
+ def _get_ubl_values(self):
+ def format_monetary(amount):
+ # Format the monetary values to avoid trailing decimals (e.g. 90.85000000000001).
+ return float_repr(amount, self.currency_id.decimal_places)
+
+ return {
+ 'invoice': self,
+ 'ubl_version': 2.1,
+ 'type_code': 380 if self.move_type == 'out_invoice' else 381,
+ 'payment_means_code': 42 if self.journal_id.bank_account_id else 31,
+ 'format_monetary': format_monetary,
+ }
diff --git a/addons/account_facturx/i18n/account_facturx.pot b/addons/account_facturx/i18n/account_facturx.pot
deleted file mode 100644
index 6b0ced13208..00000000000
--- a/addons/account_facturx/i18n/account_facturx.pot
+++ /dev/null
@@ -1,37 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~13.2\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-02-13 07:24+0000\n"
-"PO-Revision-Date: 2020-02-13 07:24+0000\n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:0
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/ar.po b/addons/account_facturx/i18n/ar.po
deleted file mode 100644
index 878b8f8f7e7..00000000000
--- a/addons/account_facturx/i18n/ar.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Mustafa Rawi , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Mustafa Rawi , 2019\n"
-"Language-Team: Arabic (https://www.transifex.com/odoo/teams/41243/ar/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: ar\n"
-"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "القيود اليومية"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "إجراء التقرير"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/cs.po b/addons/account_facturx/i18n/cs.po
deleted file mode 100644
index 7cd9b660196..00000000000
--- a/addons/account_facturx/i18n/cs.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Czech (https://www.transifex.com/odoo/teams/41243/cs/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: cs\n"
-"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Položky deníku"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/da.po b/addons/account_facturx/i18n/da.po
deleted file mode 100644
index 3108da823a5..00000000000
--- a/addons/account_facturx/i18n/da.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Ejner Sønniksen , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Ejner Sønniksen , 2019\n"
-"Language-Team: Danish (https://www.transifex.com/odoo/teams/41243/da/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: da\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Journalposter"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/de.po b/addons/account_facturx/i18n/de.po
deleted file mode 100644
index 506f53b6839..00000000000
--- a/addons/account_facturx/i18n/de.po
+++ /dev/null
@@ -1,60 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-# Leon Grill , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Leon Grill , 2019\n"
-"Language-Team: German (https://www.transifex.com/odoo/teams/41243/de/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: de\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Buchungssätze"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-"Es wurde kein Decoder für die Xml-Datei gefunden: {}. Die Datei ist schlecht"
-" formatiert, nicht unterstützt oder der Decoder ist nicht installiert"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-"Es werden keine Informationen über das Journal oder die Art der Rechnung "
-"übergeben"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr "Die Xml-Datei ist schlecht formatiert : {}"
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr "urn:cen.eu:en16931:2017"
diff --git a/addons/account_facturx/i18n/es.po b/addons/account_facturx/i18n/es.po
deleted file mode 100644
index c5eb9ad3c42..00000000000
--- a/addons/account_facturx/i18n/es.po
+++ /dev/null
@@ -1,59 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-# Pedro M. Baeza , 2019
-# John Guardado , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: John Guardado , 2019\n"
-"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: es\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Asientos contables"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-"No se encontró ningún decodificador para el archivo xml: {}. El archivo está"
-" mal formateado, no es compatible o el decodificador no está instalado."
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr "No hay información sobre el diario ni sobre el tipo de factura."
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "Acción de informe"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr "El archivo xml está mal formateado : {}"
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr "urn:cen.eu:en16931:2017"
diff --git a/addons/account_facturx/i18n/fr.po b/addons/account_facturx/i18n/fr.po
deleted file mode 100644
index bf314b325a3..00000000000
--- a/addons/account_facturx/i18n/fr.po
+++ /dev/null
@@ -1,59 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-# Cécile Collart , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Cécile Collart , 2019\n"
-"Language-Team: French (https://www.transifex.com/odoo/teams/41243/fr/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: fr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Pièces comptables"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-"Aucun décodeur n'a été trouvé pour le fichier XML: {}. Le fichier est mal "
-"formaté, non supporté ou le décodeur n'est pas installé"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-"Aucune information sur le journal ou le type de facture n'est transmise"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "Signaler l'action"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr "Le fichier XML est mal formaté: {}"
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr "urn:cen.eu:en16931:2017"
diff --git a/addons/account_facturx/i18n/he.po b/addons/account_facturx/i18n/he.po
deleted file mode 100644
index 1fa3cedf917..00000000000
--- a/addons/account_facturx/i18n/he.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# דודי מלכה , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: דודי מלכה , 2019\n"
-"Language-Team: Hebrew (https://www.transifex.com/odoo/teams/41243/he/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: he\n"
-"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "רשומות יומן"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "דוח פעולה"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/hr.po b/addons/account_facturx/i18n/hr.po
deleted file mode 100644
index 42800d08811..00000000000
--- a/addons/account_facturx/i18n/hr.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Croatian (https://www.transifex.com/odoo/teams/41243/hr/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: hr\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Temeljnice"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/hu.po b/addons/account_facturx/i18n/hu.po
deleted file mode 100644
index 38d04785980..00000000000
--- a/addons/account_facturx/i18n/hu.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Hungarian (https://www.transifex.com/odoo/teams/41243/hu/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: hu\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Könyvelési tételek"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/it.po b/addons/account_facturx/i18n/it.po
deleted file mode 100644
index 060fd0c68f4..00000000000
--- a/addons/account_facturx/i18n/it.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Sergio Zanchetta , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Sergio Zanchetta , 2019\n"
-"Language-Team: Italian (https://www.transifex.com/odoo/teams/41243/it/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: it\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Registrazioni contabili"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "Azione resoconto"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr "urn:cen.eu:en16931:2017"
diff --git a/addons/account_facturx/i18n/lb.po b/addons/account_facturx/i18n/lb.po
deleted file mode 100644
index 4bfa09c7d32..00000000000
--- a/addons/account_facturx/i18n/lb.po
+++ /dev/null
@@ -1,51 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Language-Team: Luxembourgish (https://www.transifex.com/odoo/teams/41243/lb/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: lb\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/lt.po b/addons/account_facturx/i18n/lt.po
deleted file mode 100644
index 9d3bbe48a69..00000000000
--- a/addons/account_facturx/i18n/lt.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Linas Versada , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Linas Versada , 2019\n"
-"Language-Team: Lithuanian (https://www.transifex.com/odoo/teams/41243/lt/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: lt\n"
-"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Žurnalo įrašai"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/mn.po b/addons/account_facturx/i18n/mn.po
deleted file mode 100644
index 7cc7afe3efa..00000000000
--- a/addons/account_facturx/i18n/mn.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Baskhuu Lodoikhuu , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Baskhuu Lodoikhuu , 2019\n"
-"Language-Team: Mongolian (https://www.transifex.com/odoo/teams/41243/mn/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: mn\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Ажил гүйлгээ"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/nb.po b/addons/account_facturx/i18n/nb.po
deleted file mode 100644
index aa0abb10169..00000000000
--- a/addons/account_facturx/i18n/nb.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Norwegian Bokmål (https://www.transifex.com/odoo/teams/41243/nb/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: nb\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Journalregistreringer"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/nl.po b/addons/account_facturx/i18n/nl.po
deleted file mode 100644
index a0bb8abac93..00000000000
--- a/addons/account_facturx/i18n/nl.po
+++ /dev/null
@@ -1,59 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-# Yenthe Van Ginneken , 2019
-# Gunther Clauwaert , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Gunther Clauwaert , 2019\n"
-"Language-Team: Dutch (https://www.transifex.com/odoo/teams/41243/nl/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: nl\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Boekingen"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-"Er werd geen decoder voor de xml file: {} gevonden. De file is slecht "
-"geformatteerd, niet ondersteunt of de decoder is niet geïnstalleerd"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "Rapport actie"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr "Het XML bestand is slecht geformatteerd : {}"
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr "urn:cen.eu:en16931:2017"
diff --git a/addons/account_facturx/i18n/pl.po b/addons/account_facturx/i18n/pl.po
deleted file mode 100644
index 201fa57d803..00000000000
--- a/addons/account_facturx/i18n/pl.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Polish (https://www.transifex.com/odoo/teams/41243/pl/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: pl\n"
-"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Zapisy księgowe"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/pt.po b/addons/account_facturx/i18n/pt.po
deleted file mode 100644
index 5490c0a19fe..00000000000
--- a/addons/account_facturx/i18n/pt.po
+++ /dev/null
@@ -1,51 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Language-Team: Portuguese (https://www.transifex.com/odoo/teams/41243/pt/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: pt\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/pt_BR.po b/addons/account_facturx/i18n/pt_BR.po
deleted file mode 100644
index ccd929f4f23..00000000000
--- a/addons/account_facturx/i18n/pt_BR.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Portuguese (Brazil) (https://www.transifex.com/odoo/teams/41243/pt_BR/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: pt_BR\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Lançamentos de Diário"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/ro.po b/addons/account_facturx/i18n/ro.po
deleted file mode 100644
index 3915785e87d..00000000000
--- a/addons/account_facturx/i18n/ro.po
+++ /dev/null
@@ -1,51 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Language-Team: Romanian (https://www.transifex.com/odoo/teams/41243/ro/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: ro\n"
-"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/ru.po b/addons/account_facturx/i18n/ru.po
deleted file mode 100644
index 6e267ff2124..00000000000
--- a/addons/account_facturx/i18n/ru.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Martin Trigaux, 2019\n"
-"Language-Team: Russian (https://www.transifex.com/odoo/teams/41243/ru/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: ru\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Записи журнала"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/tr.po b/addons/account_facturx/i18n/tr.po
deleted file mode 100644
index 1fd82aee36f..00000000000
--- a/addons/account_facturx/i18n/tr.po
+++ /dev/null
@@ -1,55 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Murat Kaplan , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Murat Kaplan , 2019\n"
-"Language-Team: Turkish (https://www.transifex.com/odoo/teams/41243/tr/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: tr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Yevmiye Kayıtları"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/uk.po b/addons/account_facturx/i18n/uk.po
deleted file mode 100644
index 92b2a03f3c5..00000000000
--- a/addons/account_facturx/i18n/uk.po
+++ /dev/null
@@ -1,58 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-# Alina Lisnenko , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Alina Lisnenko , 2019\n"
-"Language-Team: Ukrainian (https://www.transifex.com/odoo/teams/41243/uk/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: uk\n"
-"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Записи в журналі"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-"Не знайдено декодер для xml фалу: {}. Файл погано відформатований, не "
-"підтримується або декодер не встановлено"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr "Пропущено інформацію про журнал або тип рахунку"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "Дія звіту"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr "Файл xml погано відформатовано : {}"
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr "urn:cen.eu:en16931:2017"
diff --git a/addons/account_facturx/i18n/vi.po b/addons/account_facturx/i18n/vi.po
deleted file mode 100644
index 5046ebf5529..00000000000
--- a/addons/account_facturx/i18n/vi.po
+++ /dev/null
@@ -1,56 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Nancy Momoland , 2019
-# Chinh Chinh , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: Chinh Chinh , 2019\n"
-"Language-Team: Vietnamese (https://www.transifex.com/odoo/teams/41243/vi/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: vi\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "Bút toán phát sinh"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "Báo cáo hành động"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/zh_CN.po b/addons/account_facturx/i18n/zh_CN.po
deleted file mode 100644
index 0f73bed2afc..00000000000
--- a/addons/account_facturx/i18n/zh_CN.po
+++ /dev/null
@@ -1,56 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Jeanphy , 2019
-# niulin lnc. , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: niulin lnc. , 2019\n"
-"Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: zh_CN\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "日记账分录"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "报告动作"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/i18n/zh_TW.po b/addons/account_facturx/i18n/zh_TW.po
deleted file mode 100644
index d82ab1bd4ef..00000000000
--- a/addons/account_facturx/i18n/zh_TW.po
+++ /dev/null
@@ -1,56 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * account_facturx
-#
-# Translators:
-# Martin Trigaux, 2019
-# 敬雲 林 , 2019
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server saas~12.4\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-12 11:32+0000\n"
-"PO-Revision-Date: 2019-08-26 09:08+0000\n"
-"Last-Translator: 敬雲 林 , 2019\n"
-"Language-Team: Chinese (Taiwan) (https://www.transifex.com/odoo/teams/41243/zh_TW/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Language: zh_TW\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_account_move
-msgid "Journal Entries"
-msgstr "日記帳分錄"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:328
-#, python-format
-msgid ""
-"No decoder was found for the xml file: {}. The file is badly formatted, not "
-"supported or the decoder is not installed"
-msgstr ""
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:65
-#, python-format
-msgid "No information about the journal or the type of invoice is passed"
-msgstr ""
-
-#. module: account_facturx
-#: model:ir.model,name:account_facturx.model_ir_actions_report
-msgid "Report Action"
-msgstr "報告動作"
-
-#. module: account_facturx
-#: code:addons/account_facturx/models/account_move.py:314
-#, python-format
-msgid "The xml file is badly formatted : {}"
-msgstr ""
-
-#. module: account_facturx
-#: model_terms:ir.ui.view,arch_db:account_facturx.account_invoice_facturx_export
-msgid "urn:cen.eu:en16931:2017"
-msgstr ""
diff --git a/addons/account_facturx/models/__init__.py b/addons/account_facturx/models/__init__.py
deleted file mode 100644
index d1070b6c5f1..00000000000
--- a/addons/account_facturx/models/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# -*- encoding: utf-8 -*-
-
-from . import account_move
-from . import ir_actions_report
diff --git a/addons/account_facturx/models/ir_actions_report.py b/addons/account_facturx/models/ir_actions_report.py
deleted file mode 100644
index 72244d8e241..00000000000
--- a/addons/account_facturx/models/ir_actions_report.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from odoo import models, fields, api, _
-
-from PyPDF2 import PdfFileWriter, PdfFileReader
-
-import io
-
-
-class IrActionsReport(models.Model):
- _inherit = 'ir.actions.report'
-
- def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None):
- # OVERRIDE
- if self.model == 'account.move' and res_ids and len(res_ids) == 1:
- invoice = self.env['account.move'].browse(res_ids)
- if invoice.is_sale_document() and invoice.state != 'draft':
- xml_content = invoice._export_as_facturx_xml()
-
- # Add attachment.
- reader_buffer = io.BytesIO(pdf_content)
- reader = PdfFileReader(reader_buffer)
- writer = PdfFileWriter()
- writer.cloneReaderDocumentRoot(reader)
- writer.addAttachment('factur-x.xml', xml_content)
- buffer = io.BytesIO()
- writer.write(buffer)
- pdf_content = buffer.getvalue()
-
- reader_buffer.close()
- buffer.close()
- return super(IrActionsReport, self)._post_pdf(save_in_attachment, pdf_content=pdf_content, res_ids=res_ids)
diff --git a/addons/l10n_be_edi/__manifest__.py b/addons/l10n_be_edi/__manifest__.py
index eb4be7f9998..c9bf6a9b841 100644
--- a/addons/l10n_be_edi/__manifest__.py
+++ b/addons/l10n_be_edi/__manifest__.py
@@ -11,8 +11,9 @@ invoices. The UBL standard became the `ISO/IEC 19845
(cf the `official announce `_).
Belgian e-invoicing uses the UBL 2.0 using the e-fff protocol.
""",
- 'depends': ['account', 'l10n_be', 'account_facturx'],
+ 'depends': ['l10n_be', 'account_edi'],
'data': [
+ 'data/account_edi_data.xml'
],
'installable': True,
'auto_install': True,
diff --git a/addons/l10n_be_edi/data/account_edi_data.xml b/addons/l10n_be_edi/data/account_edi_data.xml
new file mode 100644
index 00000000000..de4c58d4ba9
--- /dev/null
+++ b/addons/l10n_be_edi/data/account_edi_data.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+ E-FFF (BE)
+ efff_1
+
+
+
+
\ No newline at end of file
diff --git a/addons/l10n_be_edi/models/__init__.py b/addons/l10n_be_edi/models/__init__.py
index ab922af0d1e..047fc3ef4d9 100644
--- a/addons/l10n_be_edi/models/__init__.py
+++ b/addons/l10n_be_edi/models/__init__.py
@@ -1,3 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- encoding: utf-8 -*-
-from . import account_invoice
+from . import account_edi_format
+from . import account_move
diff --git a/addons/l10n_be_edi/models/account_edi_format.py b/addons/l10n_be_edi/models/account_edi_format.py
new file mode 100644
index 00000000000..23bdf93c953
--- /dev/null
+++ b/addons/l10n_be_edi/models/account_edi_format.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, models, fields
+from odoo.tools import float_repr
+
+
+class AccountEdiFormat(models.Model):
+ _inherit = 'account.edi.format'
+
+ def _is_efff(self, filename, tree):
+ return self.code == 'efff_1' and tree.tag == '{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice'
+
+ def _create_invoice_from_xml_tree(self, filename, tree):
+ self.ensure_one()
+ if self._is_efff(filename, tree):
+ return self._import_ubl(tree, self.env['account.move'])
+ return super()._create_invoice_from_xml_tree(filename, tree)
+
+ def _update_invoice_from_xml_tree(self, filename, tree, invoice):
+ self.ensure_one()
+ if self._is_efff(filename, tree):
+ return self._import_ubl(tree, invoice)
+ return super()._update_invoice_from_xml_tree(filename, tree, invoice)
+
+ def _export_invoice_to_attachment(self, invoice):
+ self.ensure_one()
+ if self.code != 'efff_1':
+ return super()._export_invoice_to_attachment(invoice)
+
+ # Create file content.
+ xml_content = b""
+ xml_content += self.env.ref('account_edi_ubl.export_ubl_invoice')._render(invoice._get_ubl_values())
+ vat = invoice.company_id.partner_id.commercial_partner_id.vat
+ xml_name = 'efff-%s%s%s.xml' % (vat or '', '-' if vat else '', invoice.name.replace('/', '_')) # official naming convention
+ return {'name': xml_name,
+ 'datas': xml_content,
+ 'res_model': 'account.move',
+ 'res_id': invoice._origin.id,
+ 'mimetype': 'application/xml'
+ }
+
+ def _export_invoice_to_embed_to_pdf(self, pdf_content, invoice):
+ self.ensure_one()
+ if self.code != 'efff_1':
+ return super()._export_invoice_to_embed_to_pdf(pdf_content, invoice)
+ return False # ubl must not be embedded to PDF.
diff --git a/addons/l10n_be_edi/models/account_move.py b/addons/l10n_be_edi/models/account_move.py
new file mode 100644
index 00000000000..50ce265a54a
--- /dev/null
+++ b/addons/l10n_be_edi/models/account_move.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models
+
+
+class AccountMove(models.Model):
+ _inherit = 'account.move'
+
+ def _get_ubl_values(self):
+ values = super(AccountMove, self)._get_ubl_values()
+
+ # E-fff uses ubl_version 2.0, account_edi_ubl supports ubl_version 2.1 but generates 2.0 UBL
+ # so we only need to override the version to be compatible with E-FFF
+ values['ubl_version'] = 2.0
+
+ return values
diff --git a/addons/l10n_be_edi/tests/test_ubl.py b/addons/l10n_be_edi/tests/test_ubl.py
index cbbf385b459..1c4348a2aa7 100644
--- a/addons/l10n_be_edi/tests/test_ubl.py
+++ b/addons/l10n_be_edi/tests/test_ubl.py
@@ -25,8 +25,11 @@ class TestUBL(common.TransactionCase):
'res_model': 'account.move',
})
+ invoice_count = len(self.env['account.move'].search([]))
+
invoice.message_post(attachment_ids=[attachment_id.id])
+ self.assertEqual(len(self.env['account.move'].search([])), invoice_count)
self.assertEqual(invoice.amount_total, 666.50)
self.assertEqual(invoice.amount_tax, 115.67)
self.assertEqual(invoice.partner_id, self.partner_id)
diff --git a/addons/l10n_it_edi/__manifest__.py b/addons/l10n_it_edi/__manifest__.py
index 917dc15c231..a119942594c 100644
--- a/addons/l10n_it_edi/__manifest__.py
+++ b/addons/l10n_it_edi/__manifest__.py
@@ -7,6 +7,7 @@
'depends': [
'l10n_it',
'fetchmail',
+ 'account_edi'
],
'author': 'Odoo',
'description': """
@@ -16,6 +17,7 @@ E-invoice implementation
'website': 'http://www.odoo.com/',
'data': [
'security/ir.model.access.csv',
+ 'data/account_edi_data.xml',
'data/invoice_it_template.xml',
'views/l10n_it_view.xml',
],
diff --git a/addons/l10n_it_edi/data/account_edi_data.xml b/addons/l10n_it_edi/data/account_edi_data.xml
new file mode 100644
index 00000000000..9dbd1dec98d
--- /dev/null
+++ b/addons/l10n_it_edi/data/account_edi_data.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Fattura PA (IT)
+ fattura_pa
+ import
+
+
+
+
\ No newline at end of file
diff --git a/addons/l10n_it_edi/models/__init__.py b/addons/l10n_it_edi/models/__init__.py
index a7b2d212437..e2f3f69717a 100644
--- a/addons/l10n_it_edi/models/__init__.py
+++ b/addons/l10n_it_edi/models/__init__.py
@@ -4,5 +4,6 @@
from . import res_partner
from . import res_company
from . import account_invoice
+from . import account_edi_format
from . import ir_mail_server
from . import ddt
diff --git a/addons/l10n_it_edi/models/account_edi_format.py b/addons/l10n_it_edi/models/account_edi_format.py
new file mode 100644
index 00000000000..ab2d7230407
--- /dev/null
+++ b/addons/l10n_it_edi/models/account_edi_format.py
@@ -0,0 +1,421 @@
+# -*- coding:utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, fields, _
+from odoo.tests.common import Form
+from odoo.exceptions import UserError
+from odoo.tools import float_repr
+
+import re
+from datetime import date, datetime
+import logging
+import base64
+
+
+_logger = logging.getLogger(__name__)
+
+DEFAULT_FACTUR_ITALIAN_DATE_FORMAT = '%Y-%m-%d'
+
+
+class AccountEdiFormat(models.Model):
+ _inherit = 'account.edi.format'
+
+ def _check_filename_is_fattura_pa(self, filename):
+ return re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", filename)
+
+ def _is_fattura_pa(self, filename, tree):
+ return self.code == 'fattura_pa' and self._check_filename_is_fattura_pa(filename)
+
+ def _create_invoice_from_xml_tree(self, filename, tree):
+ self.ensure_one()
+ if self._is_fattura_pa(filename, tree):
+ return self._import_fattura_pa(tree, self.env['account.move'])
+ return super()._create_invoice_from_xml_tree(filename, tree)
+
+ def _update_invoice_from_xml_tree(self, filename, tree, invoice):
+ self.ensure_one()
+ if self._is_fattura_pa(filename, tree):
+ if len(tree.xpath('//FatturaElettronicaBody')) > 1:
+ invoice.message_post(body='The attachment contains multiple invoices, this invoice was not updated from it.',
+ message_type='comment',
+ subtype_xmlid='mail.mt_note',
+ author_id=self.env.ref('base.partner_root').id)
+ else:
+ return self._import_fattura_pa(tree, invoice)
+ return super()._update_invoice_from_xml_tree(filename, tree, invoice)
+
+ def _import_fattura_pa(self, tree, invoice):
+ """ Decodes a fattura_pa invoice into an invoice.
+
+ :param tree: the fattura_pa tree to decode.
+ :param invoice: the invoice to update or an empty recordset.
+ :returns: the invoice where the fattura_pa data was imported.
+ """
+ invoices = self.env['account.move']
+ first_run = True
+
+ # possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
+ for body_tree in tree.xpath('//FatturaElettronicaBody'):
+ if not first_run or not invoice:
+ # make sure all the iterations create a new invoice record (except the first which could have already created one)
+ invoice = self.env['account.move']
+ first_run = False
+
+ elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
+ if elements and elements[0].text and elements[0].text == 'TD01':
+ self_ctx = invoice.with_context(default_move_type='in_invoice')
+ elif elements and elements[0].text and elements[0].text == 'TD04':
+ self_ctx = invoice.with_context(default_move_type='in_refund')
+ else:
+ _logger.info(_('Document type not managed: %s.') % (elements[0].text))
+
+ # type must be present in the context to get the right behavior of the _default_journal method (account.move).
+ # journal_id must be present in the context to get the right behavior of the _default_account method (account.move.line).
+
+ elements = tree.xpath('//CessionarioCommittente//IdCodice')
+ company = elements and self.env['res.company'].search([('vat', 'ilike', elements[0].text)], limit=1)
+ if not company:
+ elements = tree.xpath('//CessionarioCommittente//CodiceFiscale')
+ company = elements and self.env['res.company'].search([('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1)
+
+ if company:
+ self_ctx = self_ctx.with_context(company_id=company.id)
+ else:
+ company = self.env.company
+ if elements:
+ _logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text)
+ else:
+ _logger.info(_('Company not found. The company\'s user is set by default.'))
+
+ if not self.env.is_superuser():
+ if self.env.company != company:
+ raise UserError(_("You can only import invoice concern your current company: %s") % self.env.company.display_name)
+
+ # Refund type.
+ # TD01 == invoice
+ # TD02 == advance/down payment on invoice
+ # TD03 == advance/down payment on fee
+ # TD04 == credit note
+ # TD05 == debit note
+ # TD06 == fee
+ elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
+ if elements and elements[0].text and elements[0].text == 'TD01':
+ move_type = 'in_invoice'
+ elif elements and elements[0].text and elements[0].text == 'TD04':
+ move_type = 'in_refund'
+ # move could be a single record (editing) or be empty (new).
+ with Form(invoice.with_context(default_move_type=move_type)) as invoice_form:
+ message_to_log = []
+
+ # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
+ elements = tree.xpath('//CedentePrestatore//IdCodice')
+ partner = elements and self.env['res.partner'].search(['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
+ if not partner:
+ elements = tree.xpath('//CedentePrestatore//CodiceFiscale')
+ partner = elements and self.env['res.partner'].search(['&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
+ if not partner:
+ elements = tree.xpath('//DatiTrasmissione//Email')
+ partner = elements and self.env['res.partner'].search(['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
+ if partner:
+ invoice_form.partner_id = partner
+ else:
+ message_to_log.append("%s %s" % (
+ _("Vendor not found, useful informations from XML file:"),
+ invoice._compose_info_message(
+ tree, './/CedentePrestatore')))
+
+ # Numbering attributed by the transmitter. <1.1.2>
+ elements = tree.xpath('//ProgressivoInvio')
+ if elements:
+ invoice_form.payment_reference = elements[0].text
+
+ elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero')
+ if elements:
+ invoice_form.ref = elements[0].text
+
+ # Currency. <2.1.1.2>
+ elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa')
+ if elements:
+ currency_str = elements[0].text
+ currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False)
+ if currency != self.env.company.currency_id and currency.active:
+ invoice_form.currency_id = currency
+
+ # Date. <2.1.1.3>
+ elements = body_tree.xpath('.//DatiGeneraliDocumento/Data')
+ if elements:
+ date_str = elements[0].text
+ date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
+ invoice_form.invoice_date = date_obj.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
+
+ # Dati Bollo. <2.1.1.6>
+ elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo')
+ if elements:
+ invoice_form.l10n_it_stamp_duty = float(elements[0].text)
+
+ # List of all amount discount (will be add after all article to avoid to have a negative sum)
+ discount_list = []
+ percentage_global_discount = 1.0
+
+ # Global discount. <2.1.1.8>
+ discount_elements = body_tree.xpath('.//DatiGeneraliDocumento/ScontoMaggiorazione')
+ total_discount_amount = 0.0
+ if discount_elements:
+ for discount_element in discount_elements:
+ discount_line = discount_element.xpath('.//Tipo')
+ discount_sign = -1
+ if discount_line and discount_line[0].text == 'SC':
+ discount_sign = 1
+ discount_percentage = discount_element.xpath('.//Percentuale')
+ if discount_percentage and discount_percentage[0].text:
+ percentage_global_discount *= 1 - float(discount_percentage[0].text)/100 * discount_sign
+
+ discount_amount_text = discount_element.xpath('.//Importo')
+ if discount_amount_text and discount_amount_text[0].text:
+ discount_amount = float(discount_amount_text[0].text) * discount_sign * -1
+ discount = {}
+ discount["seq"] = 0
+
+ if discount_amount < 0:
+ discount["name"] = _('GLOBAL DISCOUNT')
+ else:
+ discount["name"] = _('GLOBAL EXTRA CHARGE')
+ discount["amount"] = discount_amount
+ discount["tax"] = []
+ discount_list.append(discount)
+
+ # Comment. <2.1.1.11>
+ elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale')
+ for element in elements:
+ invoice_form.narration = '%s%s\n' % (invoice_form.narration or '', element.text)
+
+ # Informations relative to the purchase order, the contract, the agreement,
+ # the reception phase or invoices previously transmitted
+ # <2.1.2> - <2.1.6>
+ for document_type in ['DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate']:
+ elements = body_tree.xpath('.//DatiGenerali/' + document_type)
+ if elements:
+ for element in elements:
+ message_to_log.append("%s %s %s" % (document_type, _("from XML file:"),
+ invoice._compose_info_message(element, '.')))
+
+ # Dati DDT. <2.1.8>
+ elements = body_tree.xpath('.//DatiGenerali/DatiDDT')
+ if elements:
+ message_to_log.append("%s %s" % (
+ _("Transport informations from XML file:"),
+ invoice._compose_info_message(body_tree, './/DatiGenerali/DatiDDT')))
+
+ # Due date. <2.4.2.5>
+ elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento')
+ if elements:
+ date_str = elements[0].text
+ date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
+ invoice_form.invoice_date_due = fields.Date.to_string(date_obj)
+
+ # Total amount. <2.4.2.6>
+ elements = body_tree.xpath('.//ImportoPagamento')
+ amount_total_import = 0
+ for element in elements:
+ amount_total_import += float(element.text)
+ if amount_total_import:
+ message_to_log.append(_("Total amount from the XML File: %s") % (
+ amount_total_import))
+
+ # Bank account. <2.4.2.13>
+ if invoice_form.move_type not in ('out_invoice', 'in_refund'):
+ elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/IBAN')
+ if elements:
+ if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id:
+ bank = self.env['res.partner.bank'].search([
+ ('acc_number', '=', elements[0].text),
+ ('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id)
+ ])
+ else:
+ bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)])
+ if bank:
+ invoice_form.partner_bank_id = bank
+ else:
+ message_to_log.append("%s %s" % (
+ _("Bank account not found, useful informations from XML file:"),
+ invoice._compose_multi_info_message(
+ body_tree, ['.//DatiPagamento//Beneficiario',
+ './/DatiPagamento//IstitutoFinanziario',
+ './/DatiPagamento//IBAN',
+ './/DatiPagamento//ABI',
+ './/DatiPagamento//CAB',
+ './/DatiPagamento//BIC',
+ './/DatiPagamento//ModalitaPagamento'])))
+ else:
+ elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento')
+ if elements:
+ message_to_log.append("%s %s" % (
+ _("Bank account not found, useful informations from XML file:"),
+ self._compose_info_message(body_tree, './/DatiPagamento')))
+
+ # Invoice lines. <2.2.1>
+ elements = body_tree.xpath('.//DettaglioLinee')
+ if elements:
+ for element in elements:
+ with invoice_form.invoice_line_ids.new() as invoice_line_form:
+
+ # Sequence.
+ line_elements = element.xpath('.//NumeroLinea')
+ if line_elements:
+ invoice_line_form.sequence = int(line_elements[0].text) * 2
+
+ # Product.
+ line_elements = element.xpath('.//Descrizione')
+ if line_elements:
+ invoice_line_form.name = " ".join(line_elements[0].text.split())
+
+ elements_code = element.xpath('.//CodiceArticolo')
+ if elements_code:
+ for element_code in elements_code:
+ type_code = element_code.xpath('.//CodiceTipo')[0]
+ code = element_code.xpath('.//CodiceValore')[0]
+ if type_code.text == 'EAN':
+ product = self.env['product.product'].search([('barcode', '=', code.text)])
+ if product:
+ invoice_line_form.product_id = product
+ break
+ if partner:
+ product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)])
+ if product_supplier and product_supplier.product_id:
+ invoice_line_form.product_id = product_supplier.product_id
+ break
+ if not invoice_line_form.product_id:
+ for element_code in elements_code:
+ code = element_code.xpath('.//CodiceValore')[0]
+ product = self.env['product.product'].search([('default_code', '=', code.text)])
+ if product:
+ invoice_line_form.product_id = product
+ break
+
+ # Price Unit.
+ line_elements = element.xpath('.//PrezzoUnitario')
+ if line_elements:
+ invoice_line_form.price_unit = float(line_elements[0].text)
+
+ # Quantity.
+ line_elements = element.xpath('.//Quantita')
+ if line_elements:
+ invoice_line_form.quantity = float(line_elements[0].text)
+ else:
+ invoice_line_form.quantity = 1
+
+ # Taxes
+ tax_element = element.xpath('.//AliquotaIVA')
+ natura_element = element.xpath('.//Natura')
+ invoice_line_form.tax_ids.clear()
+ if tax_element and tax_element[0].text:
+ percentage = float(tax_element[0].text)
+ if natura_element and natura_element[0].text:
+ l10n_it_kind_exoneration = natura_element[0].text
+ tax = self.env['account.tax'].search([
+ ('company_id', '=', invoice_form.company_id.id),
+ ('amount_type', '=', 'percent'),
+ ('type_tax_use', '=', 'purchase'),
+ ('amount', '=', percentage),
+ ('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration),
+ ], limit=1)
+ else:
+ tax = self.env['account.tax'].search([
+ ('company_id', '=', invoice_form.company_id.id),
+ ('amount_type', '=', 'percent'),
+ ('type_tax_use', '=', 'purchase'),
+ ('amount', '=', percentage),
+ ], limit=1)
+ l10n_it_kind_exoneration = ''
+
+ if tax:
+ invoice_line_form.tax_ids.add(tax)
+ else:
+ if l10n_it_kind_exoneration:
+ message_to_log.append(_("Tax not found with percentage: %s and exoneration %s for the article: %s") % (
+ percentage,
+ l10n_it_kind_exoneration,
+ invoice_line_form.name))
+ else:
+ message_to_log.append(_("Tax not found with percentage: %s for the article: %s") % (
+ percentage,
+ invoice_line_form.name))
+
+ # Discount in cascade mode.
+ # if 3 discounts : -10% -50€ -20%
+ # the result must be : (((price -10%)-50€) -20%)
+ # Generic form : (((price -P1%)-A1€) -P2%)
+ # It will be split in two parts: fix amount and pourcent amount
+ # example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€)
+ # pourcent: 1-(1-P2)*(1-P5)
+ # fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all
+ # percentage present after the fix amount)
+ line_elements = element.xpath('.//ScontoMaggiorazione')
+ total_discount_amount = 0.0
+ total_discount_percentage = percentage_global_discount
+ if line_elements:
+ for line_element in line_elements:
+ discount_line = line_element.xpath('.//Tipo')
+ discount_sign = -1
+ if discount_line and discount_line[0].text == 'SC':
+ discount_sign = 1
+ discount_percentage = line_element.xpath('.//Percentuale')
+ if discount_percentage and discount_percentage[0].text:
+ pourcentage_actual = 1 - float(discount_percentage[0].text)/100 * discount_sign
+ total_discount_percentage *= pourcentage_actual
+ total_discount_amount *= pourcentage_actual
+
+ discount_amount = line_element.xpath('.//Importo')
+ if discount_amount and discount_amount[0].text:
+ total_discount_amount += float(discount_amount[0].text) * discount_sign * -1
+
+ # Save amount discount.
+ if total_discount_amount != 0:
+ discount = {}
+ discount["seq"] = invoice_line_form.sequence + 1
+
+ if total_discount_amount < 0:
+ discount["name"] = _('DISCOUNT: ') + invoice_line_form.name
+ else:
+ discount["name"] = _('EXTRA CHARGE: ') + invoice_line_form.name
+ discount["amount"] = total_discount_amount
+ discount["tax"] = []
+ for tax in invoice_line_form.tax_ids:
+ discount["tax"].append(tax)
+ discount_list.append(discount)
+ invoice_line_form.discount = (1 - total_discount_percentage) * 100
+
+ # Apply amount discount.
+ for discount in discount_list:
+ with invoice_form.invoice_line_ids.new() as invoice_line_form_discount:
+ invoice_line_form_discount.tax_ids.clear()
+ invoice_line_form_discount.sequence = discount["seq"]
+ invoice_line_form_discount.name = discount["name"]
+ invoice_line_form_discount.price_unit = discount["amount"]
+
+ new_invoice = invoice_form.save()
+ new_invoice.l10n_it_send_state = "other"
+
+ elements = body_tree.xpath('.//Allegati')
+ if elements:
+ for element in elements:
+ name_attachment = element.xpath('.//NomeAttachment')[0].text
+ attachment_64 = str.encode(element.xpath('.//Attachment')[0].text)
+ attachment_64 = self.env['ir.attachment'].create({
+ 'name': name_attachment,
+ 'datas': attachment_64,
+ 'type': 'binary',
+ })
+
+ # default_res_id is had to context to avoid facturx to import his content
+ # no_new_invoice to prevent from looping on the message_post that would create a new invoice without it
+ new_invoice.with_context(no_new_invoice=True, default_res_id=new_invoice.id).message_post(
+ body=(_("Attachment from XML")),
+ attachment_ids=[attachment_64.id]
+ )
+
+ for message in message_to_log:
+ new_invoice.message_post(body=message)
+
+ invoices += new_invoice
+ return invoices
diff --git a/addons/l10n_it_edi/models/account_invoice.py b/addons/l10n_it_edi/models/account_invoice.py
index c511de477d8..62e40420247 100644
--- a/addons/l10n_it_edi/models/account_invoice.py
+++ b/addons/l10n_it_edi/models/account_invoice.py
@@ -43,9 +43,17 @@ class AccountMove(models.Model):
l10n_it_ddt_id = fields.Many2one('l10n_it.ddt', string='DDT', readonly=True, states={'draft': [('readonly', False)]}, copy=False)
- l10n_it_einvoice_name = fields.Char(readonly=True, copy=False)
+ l10n_it_einvoice_name = fields.Char(compute='_compute_l10n_it_einvoice')
- l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", copy=False)
+ l10n_it_einvoice_id = fields.Many2one('ir.attachment', string="Electronic invoice", compute='_compute_l10n_it_einvoice')
+
+ @api.depends('edi_document_ids')
+ def _compute_l10n_it_einvoice(self):
+ fattura_pa = self.env.ref('l10n_it_edi.edi_fatturaPA')
+ for invoice in self:
+ einvoice = invoice.edi_document_ids.filtered(lambda d: d.edi_format_id == fattura_pa)
+ invoice.l10n_it_einvoice_id = einvoice
+ invoice.l10n_it_einvoice_name = einvoice.name
def post(self):
# OVERRIDE
@@ -158,17 +166,17 @@ class AccountMove(models.Model):
'codice': invoice.company_id.l10n_it_codice_fiscale,
'progressive_number': progressive_number.zfill(5),
}
- invoice.l10n_it_einvoice_name = report_name
data = b"" + invoice._export_as_xml()
description = _('Italian invoice: %s') % invoice.move_type
- invoice.l10n_it_einvoice_id = self.env['ir.attachment'].create({
+ invoice.edi_document_ids = self.env['ir.attachment'].create({
'name': report_name,
'res_id': invoice.id,
'res_model': invoice._name,
'datas': base64.encodebytes(data),
'description': description,
'type': 'binary',
+ 'edi_format_id': self.env.ref('l10n_it_edi.edi_fatturaPA').id
})
invoice.message_post(
@@ -276,8 +284,8 @@ class AccountMove(models.Model):
raise UserError(_("%s isn't in a right state. It must be in a 'Not yet send' or 'Invalid' state.") % (self.display_name))
message = self.env['mail.message'].create({
- 'subject': _('Sending file: %s') % (self.l10n_it_einvoice_id.name),
- 'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_id.name, self.env.company.l10n_it_address_recipient_fatturapa),
+ 'subject': _('Sending file: %s') % (self.l10n_it_einvoice_name),
+ 'body': _('Sending file: %s to ES: %s') % (self.l10n_it_einvoice_name, self.env.company.l10n_it_address_recipient_fatturapa),
'author_id': self.env.user.partner_id.id,
'email_from': self.env.company.l10n_it_address_send_fatturapa,
'reply_to': self.env.company.l10n_it_address_send_fatturapa,
@@ -301,382 +309,6 @@ class AccountMove(models.Model):
)
self.l10n_it_send_state = 'invalid'
- def _import_xml_invoice(self, tree):
- ''' Extract invoice values from the E-Invoice xml tree passed as parameter.
-
- :param content: The tree of the xml file.
- :return: A dictionary containing account.invoice values to create/update it.
- '''
-
- invoices = self.env['account.move']
- multi = False
-
- # possible to have multiple invoices in the case of an invoice batch, the batch itself is repeated for every invoice of the batch
- for body_tree in tree.xpath('//FatturaElettronicaBody'):
- if multi:
- # make sure all the iterations create a new invoice record (except the first which could have already created one)
- self = self.env['account.move']
- multi = True
-
- elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
- if elements and elements[0].text and elements[0].text == 'TD01':
- self_ctx = self.with_context(default_move_type='in_invoice')
- elif elements and elements[0].text and elements[0].text == 'TD04':
- self_ctx = self.with_context(default_move_type='in_refund')
- else:
- _logger.info(_('Document type not managed: %s.') % (elements[0].text))
-
- # type must be present in the context to get the right behavior of the _default_journal method (account.move).
- # journal_id must be present in the context to get the right behavior of the _default_account method (account.move.line).
-
- elements = tree.xpath('//CessionarioCommittente//IdCodice')
- company = elements and self.env['res.company'].search([('vat', 'ilike', elements[0].text)], limit=1)
- if not company:
- elements = tree.xpath('//CessionarioCommittente//CodiceFiscale')
- company = elements and self.env['res.company'].search([('l10n_it_codice_fiscale', 'ilike', elements[0].text)], limit=1)
-
- if company:
- self_ctx = self_ctx.with_context(company_id=company.id)
- else:
- company = self.env.company
- if elements:
- _logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text)
- else:
- _logger.info(_('Company not found. The company\'s user is set by default.'))
-
- if not self.env.is_superuser():
- if self.env.company != company:
- raise UserError(_("You can only import invoice concern your current company: %s") % self.env.company.display_name)
-
- # Refund type.
- # TD01 == invoice
- # TD02 == advance/down payment on invoice
- # TD03 == advance/down payment on fee
- # TD04 == credit note
- # TD05 == debit note
- # TD06 == fee
- elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
- if elements and elements[0].text and elements[0].text == 'TD01':
- type = 'in_invoice'
- elif elements and elements[0].text and elements[0].text == 'TD04':
- type = 'in_refund'
- # self could be a single record (editing) or be empty (new).
- with Form(self.with_context(default_move_type=type)) as invoice_form:
- message_to_log = []
-
- # Partner (first step to avoid warning 'Warning! You must first select a partner.'). <1.2>
- elements = tree.xpath('//CedentePrestatore//IdCodice')
- partner = elements and self.env['res.partner'].search(['&', ('vat', 'ilike', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
- if not partner:
- elements = tree.xpath('//CedentePrestatore//CodiceFiscale')
- partner = elements and self.env['res.partner'].search(['&', ('l10n_it_codice_fiscale', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
- if not partner:
- elements = tree.xpath('//DatiTrasmissione//Email')
- partner = elements and self.env['res.partner'].search(['&', '|', ('email', '=', elements[0].text), ('l10n_it_pec_email', '=', elements[0].text), '|', ('company_id', '=', company.id), ('company_id', '=', False)], limit=1)
- if partner:
- invoice_form.partner_id = partner
- else:
- message_to_log.append("%s %s" % (
- _("Vendor not found, useful informations from XML file:"),
- self._compose_info_message(
- tree, './/CedentePrestatore')))
-
- # Numbering attributed by the transmitter. <1.1.2>
- elements = tree.xpath('//ProgressivoInvio')
- if elements:
- invoice_form.payment_reference = elements[0].text
-
- elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero')
- if elements:
- invoice_form.ref = elements[0].text
-
- # Currency. <2.1.1.2>
- elements = body_tree.xpath('.//DatiGeneraliDocumento/Divisa')
- if elements:
- currency_str = elements[0].text
- currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False)
- if currency != self.env.company.currency_id and currency.active:
- invoice_form.currency_id = currency
-
- # Date. <2.1.1.3>
- elements = body_tree.xpath('.//DatiGeneraliDocumento/Data')
- if elements:
- date_str = elements[0].text
- date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
- invoice_form.invoice_date = date_obj.strftime(DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
-
- # Dati Bollo. <2.1.1.6>
- elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo')
- if elements:
- invoice_form.l10n_it_stamp_duty = float(elements[0].text)
-
- # List of all amount discount (will be add after all article to avoid to have a negative sum)
- discount_list = []
- percentage_global_discount = 1.0
-
- # Global discount. <2.1.1.8>
- discount_elements = body_tree.xpath('.//DatiGeneraliDocumento/ScontoMaggiorazione')
- total_discount_amount = 0.0
- if discount_elements:
- for discount_element in discount_elements:
- discount_line = discount_element.xpath('.//Tipo')
- discount_sign = -1
- if discount_line and discount_line[0].text == 'SC':
- discount_sign = 1
- discount_percentage = discount_element.xpath('.//Percentuale')
- if discount_percentage and discount_percentage[0].text:
- percentage_global_discount *= 1 - float(discount_percentage[0].text)/100 * discount_sign
-
- discount_amount_text = discount_element.xpath('.//Importo')
- if discount_amount_text and discount_amount_text[0].text:
- discount_amount = float(discount_amount_text[0].text) * discount_sign * -1
- discount = {}
- discount["seq"] = 0
-
- if discount_amount < 0:
- discount["name"] = _('GLOBAL DISCOUNT')
- else:
- discount["name"] = _('GLOBAL EXTRA CHARGE')
- discount["amount"] = discount_amount
- discount["tax"] = []
- discount_list.append(discount)
-
- # Comment. <2.1.1.11>
- elements = body_tree.xpath('.//DatiGeneraliDocumento//Causale')
- for element in elements:
- invoice_form.narration = '%s%s\n' % (invoice_form.narration or '', element.text)
-
- # Informations relative to the purchase order, the contract, the agreement,
- # the reception phase or invoices previously transmitted
- # <2.1.2> - <2.1.6>
- for document_type in ['DatiOrdineAcquisto', 'DatiContratto', 'DatiConvenzione', 'DatiRicezione', 'DatiFattureCollegate']:
- elements = body_tree.xpath('.//DatiGenerali/' + document_type)
- if elements:
- for element in elements:
- message_to_log.append("%s %s %s" % (document_type, _("from XML file:"),
- self._compose_info_message(element, '.')))
-
- # Dati DDT. <2.1.8>
- elements = body_tree.xpath('.//DatiGenerali/DatiDDT')
- if elements:
- message_to_log.append("%s %s" % (
- _("Transport informations from XML file:"),
- self._compose_info_message(body_tree, './/DatiGenerali/DatiDDT')))
-
- # Due date. <2.4.2.5>
- elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento')
- if elements:
- date_str = elements[0].text
- date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
- invoice_form.invoice_date_due = fields.Date.to_string(date_obj)
-
- # Total amount. <2.4.2.6>
- elements = body_tree.xpath('.//ImportoPagamento')
- amount_total_import = 0
- for element in elements:
- amount_total_import += float(element.text)
- if amount_total_import:
- message_to_log.append(_("Total amount from the XML File: %s") % (
- amount_total_import))
-
- # Bank account. <2.4.2.13>
- if invoice_form.type not in ('out_invoice', 'in_refund'):
- elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/IBAN')
- if elements:
- if invoice_form.partner_id and invoice_form.partner_id.commercial_partner_id:
- bank = self.env['res.partner.bank'].search([
- ('acc_number', '=', elements[0].text),
- ('partner_id.id', '=', invoice_form.partner_id.commercial_partner_id.id)
- ])
- else:
- bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)])
- if bank:
- invoice_form.partner_bank_id = bank
- else:
- message_to_log.append("%s %s" % (
- _("Bank account not found, useful informations from XML file:"),
- self._compose_multi_info_message(
- body_tree, ['.//DatiPagamento//Beneficiario',
- './/DatiPagamento//IstitutoFinanziario',
- './/DatiPagamento//IBAN',
- './/DatiPagamento//ABI',
- './/DatiPagamento//CAB',
- './/DatiPagamento//BIC',
- './/DatiPagamento//ModalitaPagamento'])))
- else:
- elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento')
- if elements:
- message_to_log.append("%s %s" % (
- _("Bank account not found, useful informations from XML file:"),
- self._compose_info_message(body_tree, './/DatiPagamento')))
-
- # Invoice lines. <2.2.1>
- elements = body_tree.xpath('.//DettaglioLinee')
- if elements:
- for element in elements:
- with invoice_form.invoice_line_ids.new() as invoice_line_form:
-
- # Sequence.
- line_elements = element.xpath('.//NumeroLinea')
- if line_elements:
- invoice_line_form.sequence = int(line_elements[0].text) * 2
-
- # Product.
- line_elements = element.xpath('.//Descrizione')
- if line_elements:
- invoice_line_form.name = " ".join(line_elements[0].text.split())
-
- elements_code = element.xpath('.//CodiceArticolo')
- if elements_code:
- for element_code in elements_code:
- type_code = element_code.xpath('.//CodiceTipo')[0]
- code = element_code.xpath('.//CodiceValore')[0]
- if type_code.text == 'EAN':
- product = self.env['product.product'].search([('barcode', '=', code.text)])
- if product:
- invoice_line_form.product_id = product
- break
- if partner:
- product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)])
- if product_supplier and product_supplier.product_id:
- invoice_line_form.product_id = product_supplier.product_id
- break
- if not invoice_line_form.product_id:
- for element_code in elements_code:
- code = element_code.xpath('.//CodiceValore')[0]
- product = self.env['product.product'].search([('default_code', '=', code.text)])
- if product:
- invoice_line_form.product_id = product
- break
-
- # Price Unit.
- line_elements = element.xpath('.//PrezzoUnitario')
- if line_elements:
- invoice_line_form.price_unit = float(line_elements[0].text)
-
- # Quantity.
- line_elements = element.xpath('.//Quantita')
- if line_elements:
- invoice_line_form.quantity = float(line_elements[0].text)
- else:
- invoice_line_form.quantity = 1
-
- # Taxes
- tax_element = element.xpath('.//AliquotaIVA')
- natura_element = element.xpath('.//Natura')
- invoice_line_form.tax_ids.clear()
- if tax_element and tax_element[0].text:
- percentage = float(tax_element[0].text)
- if natura_element and natura_element[0].text:
- l10n_it_kind_exoneration = natura_element[0].text
- tax = self.env['account.tax'].search([
- ('company_id', '=', invoice_form.company_id.id),
- ('amount_type', '=', 'percent'),
- ('type_tax_use', '=', 'purchase'),
- ('amount', '=', percentage),
- ('l10n_it_kind_exoneration', '=', l10n_it_kind_exoneration),
- ], limit=1)
- else:
- tax = self.env['account.tax'].search([
- ('company_id', '=', invoice_form.company_id.id),
- ('amount_type', '=', 'percent'),
- ('type_tax_use', '=', 'purchase'),
- ('amount', '=', percentage),
- ], limit=1)
- l10n_it_kind_exoneration = ''
-
- if tax:
- invoice_line_form.tax_ids.add(tax)
- else:
- if l10n_it_kind_exoneration:
- message_to_log.append(_("Tax not found with percentage: %s and exoneration %s for the article: %s") % (
- percentage,
- l10n_it_kind_exoneration,
- invoice_line_form.name))
- else:
- message_to_log.append(_("Tax not found with percentage: %s for the article: %s") % (
- percentage,
- invoice_line_form.name))
-
- # Discount in cascade mode.
- # if 3 discounts : -10% -50€ -20%
- # the result must be : (((price -10%)-50€) -20%)
- # Generic form : (((price -P1%)-A1€) -P2%)
- # It will be split in two parts: fix amount and pourcent amount
- # example: (((((price - A1€) -P2%) -A3€) -A4€) -P5€)
- # pourcent: 1-(1-P2)*(1-P5)
- # fix amount: A1*(1-P2)*(1-P5)+A3*(1-P5)+A4*(1-P5) (we must take account of all
- # percentage present after the fix amount)
- line_elements = element.xpath('.//ScontoMaggiorazione')
- total_discount_amount = 0.0
- total_discount_percentage = percentage_global_discount
- if line_elements:
- for line_element in line_elements:
- discount_line = line_element.xpath('.//Tipo')
- discount_sign = -1
- if discount_line and discount_line[0].text == 'SC':
- discount_sign = 1
- discount_percentage = line_element.xpath('.//Percentuale')
- if discount_percentage and discount_percentage[0].text:
- pourcentage_actual = 1 - float(discount_percentage[0].text)/100 * discount_sign
- total_discount_percentage *= pourcentage_actual
- total_discount_amount *= pourcentage_actual
-
- discount_amount = line_element.xpath('.//Importo')
- if discount_amount and discount_amount[0].text:
- total_discount_amount += float(discount_amount[0].text) * discount_sign * -1
-
- # Save amount discount.
- if total_discount_amount != 0:
- discount = {}
- discount["seq"] = invoice_line_form.sequence + 1
-
- if total_discount_amount < 0:
- discount["name"] = _('DISCOUNT: ') + invoice_line_form.name
- else:
- discount["name"] = _('EXTRA CHARGE: ') + invoice_line_form.name
- discount["amount"] = total_discount_amount
- discount["tax"] = []
- for tax in invoice_line_form.tax_ids:
- discount["tax"].append(tax)
- discount_list.append(discount)
- invoice_line_form.discount = (1 - total_discount_percentage) * 100
-
- # Apply amount discount.
- for discount in discount_list:
- with invoice_form.invoice_line_ids.new() as invoice_line_form_discount:
- invoice_line_form_discount.tax_ids.clear()
- invoice_line_form_discount.sequence = discount["seq"]
- invoice_line_form_discount.name = discount["name"]
- invoice_line_form_discount.price_unit = discount["amount"]
-
- new_invoice = invoice_form.save()
- new_invoice.l10n_it_send_state = "other"
-
- elements = body_tree.xpath('.//Allegati')
- if elements:
- for element in elements:
- name_attachment = element.xpath('.//NomeAttachment')[0].text
- attachment_64 = str.encode(element.xpath('.//Attachment')[0].text)
- attachment_64 = self.env['ir.attachment'].create({
- 'name': name_attachment,
- 'datas': attachment_64,
- 'type': 'binary',
- })
-
- # default_res_id is had to context to avoid facturx to import his content
- # no_new_invoice to prevent from looping on the message_post that would create a new invoice without it
- new_invoice.with_context(no_new_invoice=True, default_res_id=new_invoice.id).message_post(
- body=(_("Attachment from XML")),
- attachment_ids=[attachment_64.id]
- )
-
- for message in message_to_log:
- new_invoice.message_post(body=message)
-
- invoices += new_invoice
- return invoices
-
def _compose_info_message(self, tree, element_tags):
output_str = ""
elements = tree.xpath(element_tags)
@@ -703,20 +335,6 @@ class AccountMove(models.Model):
output_str += "
%s: %s
" % (element.tag, text)
return output_str + ""
- @api.model
- def _get_xml_decoders(self):
- # Override
- ubl_decoders = [('Italy', self._detect_italy_edi, self._import_xml_invoice)]
- return super(AccountMove, self)._get_xml_decoders() + ubl_decoders
-
- @api.model
- def _detect_italy_edi(self, tree, file_name):
- # Quick check the file name looks like the one of an Italian EDI XML.
- flag = re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", file_name)
- error = None
-
- return {'flag': flag, 'error': error}
-
class AccountTax(models.Model):
_name = "account.tax"
_inherit = "account.tax"
diff --git a/addons/l10n_it_edi/models/ir_mail_server.py b/addons/l10n_it_edi/models/ir_mail_server.py
index 24ccd392a8e..8830af16b93 100644
--- a/addons/l10n_it_edi/models/ir_mail_server.py
+++ b/addons/l10n_it_edi/models/ir_mail_server.py
@@ -140,7 +140,7 @@ class FetchmailServer(models.Model):
except Exception:
raise UserError(_('The xml file is badly formatted : {}').format(att_name))
- invoice = self.env['account.move']._import_xml_invoice(tree)
+ invoice = self.env.ref('l10n_it_edi.edi_fatturaPA')._create_invoice_from_xml_tree(att_name, tree)
invoice.l10n_it_send_state = "new"
invoice.source_email = from_address
self._cr.commit()
diff --git a/odoo/tools/pdf.py b/odoo/tools/pdf.py
index 61105ed1c90..9003ad64789 100644
--- a/odoo/tools/pdf.py
+++ b/odoo/tools/pdf.py
@@ -1,25 +1,37 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import io
-import PyPDF2
+from PyPDF2 import PdfFileWriter, PdfFileReader
+from PyPDF2.generic import DictionaryObject, DecodedStreamObject, NameObject, createStringObject, ArrayObject
+from PyPDF2.utils import b_
+from datetime import datetime
-class BrandedFileWriter(PyPDF2.PdfFileWriter):
+import io
+import hashlib
+
+
+DEFAULT_PDF_DATETIME_FORMAT = "D:%Y%m%d%H%M%S+00'00'"
+
+
+class BrandedFileWriter(PdfFileWriter):
def __init__(self):
super().__init__()
self.addMetadata({
'/Creator': "Odoo",
'/Producer': "Odoo",
})
-PyPDF2.PdfFileWriter = BrandedFileWriter
+
+
+PdfFileWriter = BrandedFileWriter
+
def merge_pdf(pdf_data):
''' Merge a collection of PDF documents in one
:param list pdf_data: a list of PDF datastrings
:return: a unique merged PDF datastring
'''
- writer = PyPDF2.PdfFileWriter()
+ writer = PdfFileWriter()
for document in pdf_data:
- reader = PyPDF2.PdfFileReader(io.BytesIO(document), strict=False)
+ reader = PdfFileReader(io.BytesIO(document), strict=False)
for page in range(0, reader.getNumPages()):
writer.addPage(reader.getPage(page))
with io.BytesIO() as _buffer:
@@ -32,8 +44,8 @@ def rotate_pdf(pdf):
:param pdf: a PDF to rotate
:return: a PDF rotated
'''
- writer = PyPDF2.PdfFileWriter()
- reader = PyPDF2.PdfFileReader(io.BytesIO(pdf), strict=False)
+ writer = PdfFileWriter()
+ reader = PdfFileReader(io.BytesIO(pdf), strict=False)
for page in range(0, reader.getNumPages()):
page = reader.getPage(page)
page.rotateClockwise(90)
@@ -41,3 +53,67 @@ def rotate_pdf(pdf):
with io.BytesIO() as _buffer:
writer.write(_buffer)
return _buffer.getvalue()
+
+
+class OdooPdfFileReader(PdfFileReader):
+ # OVERRIDE of PdfFileReader to add the management of multiple embedded files.
+
+ def getAttachments(self):
+ if "/Names" not in self.trailer["/Root"]:
+ return []
+ for i in range(0, len(self.trailer["/Root"]["/Names"]["/EmbeddedFiles"]["/Names"]), 2):
+ attachment = self.trailer["/Root"]["/Names"]["/EmbeddedFiles"]["/Names"][i+1].getObject()
+ yield (attachment["/F"], attachment["/EF"]["/F"].getObject().getData())
+
+
+class OdooPdfFileWriter(PdfFileWriter):
+ # OVERRIDE of PdfFileWriter to add the management of multiple embedded files.
+
+ def _create_attachment_object(self, attachment):
+ ''' Create a PyPdf2.generic object representing an embedded file.
+
+ :param attachment: A dictionary containing:
+ * filename: The name of the file to embed (require).
+ * content: The content of the file encoded in base64 (require).
+ :return:
+ '''
+ file_entry = DecodedStreamObject()
+ file_entry.setData(attachment['content'])
+ file_entry.update({
+ NameObject("/Type"): NameObject("/EmbeddedFile"),
+ NameObject("/Params"):
+ DictionaryObject({
+ NameObject('/CheckSum'): createStringObject(hashlib.md5(attachment['content']).hexdigest()),
+ NameObject('/ModDate'): createStringObject(datetime.now().strftime(DEFAULT_PDF_DATETIME_FORMAT)),
+ NameObject('/Size'): NameObject(str(len(attachment['content']))),
+ }),
+ })
+ if attachment.get('subtype'):
+ file_entry.update({
+ NameObject("/Subtype"): NameObject(attachment['subtype']),
+ })
+ file_entry_object = self._addObject(file_entry)
+ filename_object = createStringObject(attachment['filename'])
+ filespec_object = DictionaryObject({
+ NameObject("/AFRelationship"): NameObject("/Data"),
+ NameObject("/Type"): NameObject("/Filespec"),
+ NameObject("/F"): filename_object,
+ NameObject("/EF"):
+ DictionaryObject({
+ NameObject("/F"): file_entry_object,
+ NameObject('/UF'): file_entry_object,
+ }),
+ NameObject("/UF"): filename_object,
+ })
+ if attachment.get('description'):
+ filespec_object.update({NameObject("/Desc"): createStringObject(attachment['description'])})
+ return self._addObject(filespec_object)
+
+ def addAttachment(self, fname, fdata):
+ # OVERRIDE of the AddAttachment method to allow appending attachemnts when some already exist
+ if self._root_object.get('/Names') and self._root_object['/Names'].get('/EmbeddedFiles'):
+ attachments = self._root_object["/Names"]["/EmbeddedFiles"]["/Names"]
+ new_attachment = self._create_attachment_object({'filename': fname, 'content': fdata})
+ attachments.extend([new_attachment.getObject()['/F'], new_attachment])
+ else:
+ super().addAttachment(fname, fdata)