diff --git a/addons/account/controllers/__init__.py b/addons/account/controllers/__init__.py
index ffbfd584f8f..750860a28c1 100644
--- a/addons/account/controllers/__init__.py
+++ b/addons/account/controllers/__init__.py
@@ -3,4 +3,4 @@
from . import portal
from . import terms
-from . import download_edi_docs
+from . import download_docs
diff --git a/addons/account/controllers/download_edi_docs.py b/addons/account/controllers/download_docs.py
similarity index 57%
rename from addons/account/controllers/download_edi_docs.py
rename to addons/account/controllers/download_docs.py
index e8d0cbfb1ba..67038111377 100644
--- a/addons/account/controllers/download_edi_docs.py
+++ b/addons/account/controllers/download_docs.py
@@ -6,19 +6,16 @@ import zipfile
from odoo import http, _
from odoo.http import request, content_disposition
-class AccountEdiDocumentDownloadController(http.Controller):
- @http.route('/account/export_edi_documents', type='http', auth='user')
- def export_edi_documents(self, **args):
+
+class AccountDocumentDownloadController(http.Controller):
+ @http.route('/account/export_zip_documents', type='http', auth='user')
+ def export_zip_documents(self, **args):
+ """ Download zipped attachments. """
ids = list(map(int, request.httprequest.args.getlist('ids')))
-
- moves = request.env['account.move'].browse(ids)
- moves.check_access_rights('read')
- moves.check_access_rule('read')
-
- attachments = moves._get_edi_doc_attachments_to_export()
- if not attachments:
- error_msg = _("No EDI documents found for export.")
- return request.not_found(error_msg)
+ filename = request.httprequest.args.get('filename')
+ attachments = request.env['ir.attachment'].browse(ids)
+ attachments.check_access_rights('read')
+ attachments.check_access_rule('read')
# Create zip file
buffer = io.BytesIO()
@@ -31,6 +28,6 @@ class AccountEdiDocumentDownloadController(http.Controller):
('Content-Type', 'zip'),
('X-Content-Type-Options', 'nosniff'),
('Content-Length', len(content)),
- ('Content-Disposition', content_disposition('edi_documents.zip')),
+ ('Content-Disposition', content_disposition(filename)),
]
return request.make_response(content, headers)
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 2d295b6e1dc..d8818f1d6e8 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
-
-from werkzeug.urls import url_encode
-
from collections import defaultdict
from contextlib import ExitStack, contextmanager
from datetime import date, timedelta
@@ -3099,27 +3096,6 @@ class AccountMove(models.Model):
return success
- ####################################################
- # Export Electronic Document
- ####################################################
-
- def _action_download_electronic_invoice(self):
- if not self:
- return False
- return {
- 'type': 'ir.actions.act_url',
- 'url': '/account/export_edi_documents?%s' % url_encode({'ids': self.ids}),
- 'target': 'new',
- }
-
- def _get_edi_doc_attachments_to_export(self):
- """
- To override
- :return a recordset of ir.attachment to export
- """
- return self.env['ir.attachment']
-
-
# -------------------------------------------------------------------------
# BUSINESS METHODS
# -------------------------------------------------------------------------
@@ -4347,10 +4323,10 @@ class AccountMove(models.Model):
else:
return self.env['ir.actions.report']._render('account.account_invoices', self.ids)[0], "Invoices.pdf"
- def _get_invoice_pdf_report_filename(self):
- """ Get the filename of the generated PDF invoice report. """
+ def _get_invoice_report_filename(self, extension='pdf'):
+ """ Get the filename of the generated invoice report with extension file. """
self.ensure_one()
- return f"{self.name.replace('/', '_')}.pdf"
+ return f"{self.name.replace('/', '_')}.{extension}"
def _get_invoice_proforma_pdf_report_filename(self):
""" Get the filename of the generated proforma PDF invoice report. """
diff --git a/addons/account/tests/test_account_move_send.py b/addons/account/tests/test_account_move_send.py
index ee1b4e2c3d3..2566b6bdb05 100644
--- a/addons/account/tests/test_account_move_send.py
+++ b/addons/account/tests/test_account_move_send.py
@@ -550,8 +550,8 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
wizard = self.create_send_and_print(invoice1 + invoice2)
wizard_values = {
'mode': 'invoice_multi',
- 'enable_download': False,
- 'checkbox_download': False,
+ 'enable_download': True,
+ 'checkbox_download': True,
'enable_send_mail': True,
'display_mail_composer': False,
'send_mail_readonly': False,
@@ -564,6 +564,8 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
}
self.assertRecordValues(wizard, [wizard_values])
+ wizard.checkbox_download = False # We don't want to download the zip (since then we process synchronously)
+
# Process.
results = wizard.action_send_and_print()
self.assertEqual(results['type'], 'ir.actions.act_window_close')
@@ -619,6 +621,73 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
])
self.assertEqual(len(invoice_attachments), 1)
+ def test_invoice_multi_with_download(self):
+ invoice1 = self.init_invoice("out_invoice", partner=self.partner_a, amounts=[1000], post=True)
+ invoice2 = self.init_invoice("out_invoice", partner=self.partner_b, amounts=[1000], post=True)
+
+ wizard = self.create_send_and_print(invoice1 + invoice2)
+ wizard_values = {
+ 'mode': 'invoice_multi',
+ 'enable_download': True,
+ 'checkbox_download': True,
+ 'enable_send_mail': True,
+ 'display_mail_composer': False,
+ 'send_mail_readonly': False,
+ 'checkbox_send_mail': True,
+ 'mail_lang': 'en_US',
+ 'mail_partner_ids': [],
+ 'mail_subject': False,
+ 'mail_body': False,
+ 'mail_attachments_widget': False,
+ }
+ self.assertRecordValues(wizard, [wizard_values])
+
+ # Process.
+ results = wizard.action_send_and_print()
+ self.assertEqual(results['type'], 'ir.actions.act_url')
+ self.assertRecordValues(wizard, [{'mode': 'done'}])
+ self.assertTrue(invoice1.invoice_pdf_report_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice1._name),
+ ('res_id', '=', invoice1.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ self.assertTrue(invoice2.invoice_pdf_report_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice2._name),
+ ('res_id', '=', invoice2.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ self.assertFalse(invoice1.is_being_sent)
+ self.assertFalse(invoice2.is_being_sent)
+
+ # Mix already sent invoice with a new one.
+ invoice3 = self.init_invoice("out_invoice", partner=self.partner_b, amounts=[1000], post=True)
+ wizard = self.create_send_and_print(invoice1 + invoice2 + invoice3)
+ self.assertRecordValues(wizard, [wizard_values])
+ wizard.action_send_and_print(from_cron=True)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice1._name),
+ ('res_id', '=', invoice1.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice2._name),
+ ('res_id', '=', invoice2.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ self.assertTrue(invoice1.invoice_pdf_report_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice3._name),
+ ('res_id', '=', invoice3.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+
def test_invoice_multi_readonly_checkbox_warning_message(self):
invoice1 = self.init_invoice("out_invoice", partner=self.partner_a, amounts=[1000], post=True)
invoice2 = self.init_invoice("out_invoice", partner=self.partner_b, amounts=[1000], post=True)
diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml
index 6200727ca57..07bd679b952 100644
--- a/addons/account/views/account_move_views.xml
+++ b/addons/account/views/account_move_views.xml
@@ -1647,17 +1647,5 @@ if records:
-
- Download Electronic Invoices
-
-
- list
- code
-
- if records:
- action = records._action_download_electronic_invoice()
-
-
-
diff --git a/addons/account/wizard/account_move_send.py b/addons/account/wizard/account_move_send.py
index d1465131c68..36cbd5987e8 100644
--- a/addons/account/wizard/account_move_send.py
+++ b/addons/account/wizard/account_move_send.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from unittest.mock import patch
+from werkzeug.urls import url_encode
from odoo import _, api, fields, models, modules, tools, Command
from odoo.exceptions import UserError
@@ -102,6 +102,7 @@ class AccountMoveSend(models.Model):
return {
'move_ids': [Command.set(move.ids)],
'mail_template_id': self.mail_template_id.id,
+ 'checkbox_download': self.checkbox_download,
'checkbox_send_mail': self.checkbox_send_mail,
}
@@ -119,7 +120,7 @@ class AccountMoveSend(models.Model):
if move.invoice_pdf_report_id:
return []
- filename = move._get_invoice_pdf_report_filename()
+ filename = move._get_invoice_report_filename()
return [{
'id': f'placeholder_{filename}',
'name': filename,
@@ -181,7 +182,7 @@ class AccountMoveSend(models.Model):
@api.depends('move_ids')
def _compute_enable_download(self):
for wizard in self:
- wizard.enable_download = wizard.mode == 'invoice_single'
+ wizard.enable_download = wizard.mode in ('invoice_single', 'invoice_multi')
@api.depends('enable_download')
def _compute_checkbox_download(self):
@@ -291,7 +292,7 @@ class AccountMoveSend(models.Model):
:return: True if the PDF / electronic documents must be generated, False otherwise.
"""
self.ensure_one()
- return self.mode == 'invoice_single' and not invoice.invoice_pdf_report_id and invoice.state == 'posted'
+ return not invoice.invoice_pdf_report_id and invoice.state == 'posted'
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
""" Hook allowing to add some extra data for the invoice passed as parameter before the rendering of the pdf
@@ -317,7 +318,7 @@ class AccountMoveSend(models.Model):
invoice_data['pdf_attachment_values'] = {
'raw': content,
- 'name': invoice._get_invoice_pdf_report_filename(),
+ 'name': invoice._get_invoice_report_filename(),
'mimetype': 'application/pdf',
'res_model': invoice._name,
'res_id': invoice.id,
@@ -576,13 +577,21 @@ class AccountMoveSend(models.Model):
.create(invoice_data.pop('proforma_pdf_attachment_values'))
@api.model
- def _download(self, attachment_id):
- """ Download the PDF. """
- return {
- 'type': 'ir.actions.act_url',
- 'url': f'/web/content/{attachment_id}?download=true',
- 'close': True, # close the wizard
- }
+ def _download(self, attachment_ids):
+ """ Download the PDF or the zip of PDF if we are in 'multi' mode. """
+ if len(attachment_ids) == 1:
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': f'/web/content/{attachment_ids[0]}?download=true',
+ 'close': True, # close the wizard
+ }
+ else:
+ filename = self.move_ids._get_invoice_report_filename(extension='zip') if len(self.move_ids) == 1 else 'invoices.zip'
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': f"/account/export_zip_documents?{url_encode({'ids': attachment_ids, 'filename': filename})}",
+ 'close': True,
+ }
def action_send_and_print(self, from_cron=False, allow_fallback_pdf=False):
""" Create the documents and send them to the end customers.
@@ -595,45 +604,45 @@ class AccountMoveSend(models.Model):
self.ensure_one()
download = self.enable_download and self.checkbox_download
- generate_invoice_documents = self.mode == 'invoice_single' or (self.mode == 'invoice_multi' and from_cron)
- moves_data = {move: {} for move in self.move_ids}
+ moves_data = {move: {} for move in self.move_ids}
if len(moves_data) == 1:
moves_data[self.move_ids]['_form'] = self
else:
for move in self.move_ids:
moves_data[move]['_form'] = self.new(self._get_available_field_values_in_multi(move))
- if generate_invoice_documents:
- # Generate all invoice documents.
- self._generate_invoice_documents(moves_data, allow_fallback_pdf=allow_fallback_pdf)
-
- # Manage errors.
- errors = {move: move_data for move, move_data in moves_data.items() if move_data.get('error')}
- if errors:
- self._hook_if_errors(errors, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
-
- # Fallback in case of error.
- errors = {move: move_data for move, move_data in moves_data.items() if move_data.get('error')}
- if allow_fallback_pdf and errors:
- self._generate_invoice_fallback_documents(errors)
-
- # Send mail.
- success = {move: move_data for move, move_data in moves_data.items() if not move_data.get('error') and move.partner_id.email}
- if success:
- self._hook_if_success(success, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
-
- self.mode = 'done'
-
- if not from_cron:
+ if self.mode == 'invoice_multi' and not from_cron and not download:
self.env.ref('account.ir_cron_account_move_send')._trigger()
+ return {'type': 'ir.actions.act_window_close'}
+
+ # Generate all invoice documents.
+ self._generate_invoice_documents(moves_data, allow_fallback_pdf=allow_fallback_pdf)
+
+ # Manage errors.
+ errors = {move: move_data for move, move_data in moves_data.items() if move_data.get('error')}
+ if errors:
+ self._hook_if_errors(errors, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
+
+ # Fallback in case of error.
+ errors = {move: move_data for move, move_data in moves_data.items() if move_data.get('error')}
+ if allow_fallback_pdf and errors:
+ self._generate_invoice_fallback_documents(errors)
+
+ # Send mail.
+ success = {move: move_data for move, move_data in moves_data.items() if not move_data.get('error') and move.partner_id.email}
+ if success:
+ self._hook_if_success(success, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
+
+ self.mode = 'done'
if download:
- attachment = self.move_ids.invoice_pdf_report_id
- if not attachment and moves_data:
- attachment = list(moves_data.values())[0].get('proforma_pdf_attachment')
- if attachment:
- return self._download(attachment.id)
+ attachment_ids = []
+ for move in self.move_ids:
+ # _get_invoice_extra_attachments retrieves invoice PDF and other possible xml, etc.
+ attachment_ids += self._get_invoice_extra_attachments(move).ids or moves_data.get(move).get('proforma_pdf_attachment').ids
+ if attachment_ids:
+ return self._download(attachment_ids)
return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/account/wizard/account_move_send_views.xml b/addons/account/wizard/account_move_send_views.xml
index 39618377e69..c5b7565fc7a 100644
--- a/addons/account/wizard/account_move_send_views.xml
+++ b/addons/account/wizard/account_move_send_views.xml
@@ -104,7 +104,7 @@
- Send
+ Send & Print
code
diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py
index 0215cf1ae48..28b8337ae06 100644
--- a/addons/account_edi/models/account_move.py
+++ b/addons/account_edi/models/account_move.py
@@ -379,7 +379,3 @@ class AccountMove(models.Model):
move_result.setdefault('attachment_ids', []).extend(edi_attachments.get('attachment_ids', []))
move_result.setdefault('attachments', []).extend(edi_attachments.get('attachments', []))
return result
-
- def _get_edi_doc_attachments_to_export(self):
- # EXTENDS 'account'
- return super()._get_edi_doc_attachments_to_export() + self.edi_document_ids.attachment_id
diff --git a/addons/account_edi_ubl_cii/models/account_move.py b/addons/account_edi_ubl_cii/models/account_move.py
index 80562216889..457e3b2cabe 100644
--- a/addons/account_edi_ubl_cii/models/account_move.py
+++ b/addons/account_edi_ubl_cii/models/account_move.py
@@ -53,10 +53,6 @@ class AccountMove(models.Model):
return super()._get_edi_decoder(file_data, new=new)
- def _get_edi_doc_attachments_to_export(self):
- # EXTENDS 'account'
- return super()._get_edi_doc_attachments_to_export() + self.ubl_cii_xml_id
-
def _need_ubl_cii_xml(self):
self.ensure_one()
return not self.invoice_pdf_report_id \
diff --git a/addons/l10n_es_edi_facturae/models/account_move.py b/addons/l10n_es_edi_facturae/models/account_move.py
index 282d801f735..732dc6798df 100644
--- a/addons/l10n_es_edi_facturae/models/account_move.py
+++ b/addons/l10n_es_edi_facturae/models/account_move.py
@@ -379,10 +379,6 @@ class AccountMove(models.Model):
errors.append(_('No valid certificate found for this company, Facturae EDI file will not be signed.\n'))
return xml_content, errors
- def _get_edi_doc_attachments_to_export(self):
- # EXTENDS 'account'
- return super()._get_edi_doc_attachments_to_export() + self.l10n_es_edi_facturae_xml_id
-
# -------------------------------------------------------------------------
# IMPORT
# -------------------------------------------------------------------------