From 38fd69043403625a65f733fbe47772d6bc2ef425 Mon Sep 17 00:00:00 2001 From: "Claire Bretton (clbr)" Date: Tue, 3 Oct 2023 11:33:49 +0200 Subject: [PATCH] [IMP] account{_edi}{_ubl_cii},l10n_es_edi_facturae: Send and Print mass download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add the "Download" option back into the Send & Print in list view 2. If the "Download" option is ticked, the Send & Print is run synchronously. 3. "Download" returnsĀ a zip containing all documents that are generated (or previously generated) 4. Remove completely "Download Electronic Invoice" 5. When downloading a single invoice that has multiple attachments (pdf, xml, ...) we now download a zip with all files instead of only the pdf. Task-id: 3525932 Part-of: odoo/odoo#137382 --- addons/account/controllers/__init__.py | 2 +- ...{download_edi_docs.py => download_docs.py} | 23 ++--- addons/account/models/account_move.py | 30 +----- .../account/tests/test_account_move_send.py | 73 ++++++++++++++- addons/account/views/account_move_views.xml | 12 --- addons/account/wizard/account_move_send.py | 91 ++++++++++--------- .../wizard/account_move_send_views.xml | 2 +- addons/account_edi/models/account_move.py | 4 - .../models/account_move.py | 4 - .../models/account_move.py | 4 - 10 files changed, 136 insertions(+), 109 deletions(-) rename addons/account/controllers/{download_edi_docs.py => download_docs.py} (57%) 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 # -------------------------------------------------------------------------