diff --git a/addons/account/controllers/download_docs.py b/addons/account/controllers/download_docs.py index 67038111377..749ea8227e0 100644 --- a/addons/account/controllers/download_docs.py +++ b/addons/account/controllers/download_docs.py @@ -1,12 +1,19 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import io -import zipfile from odoo import http, _ from odoo.http import request, content_disposition +def _get_zip_headers(content, filename): + return [ + ('Content-Type', 'zip'), + ('X-Content-Type-Options', 'nosniff'), + ('Content-Length', len(content)), + ('Content-Disposition', content_disposition(filename)), + ] + + class AccountDocumentDownloadController(http.Controller): @http.route('/account/export_zip_documents', type='http', auth='user') def export_zip_documents(self, **args): @@ -16,18 +23,6 @@ class AccountDocumentDownloadController(http.Controller): attachments = request.env['ir.attachment'].browse(ids) attachments.check_access_rights('read') attachments.check_access_rule('read') - - # Create zip file - buffer = io.BytesIO() - with zipfile.ZipFile(buffer, 'w', compression=zipfile.ZIP_DEFLATED) as zipfile_obj: - for attachment in attachments: - zipfile_obj.writestr(attachment.display_name, attachment.raw) - content = buffer.getvalue() - - headers = [ - ('Content-Type', 'zip'), - ('X-Content-Type-Options', 'nosniff'), - ('Content-Length', len(content)), - ('Content-Disposition', content_disposition(filename)), - ] + content = attachments._build_zip_from_attachments() + headers = _get_zip_headers(content, filename) return request.make_response(content, headers) diff --git a/addons/account/controllers/portal.py b/addons/account/controllers/portal.py index 0bc04fb9076..9ef44cad8dc 100644 --- a/addons/account/controllers/portal.py +++ b/addons/account/controllers/portal.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from collections import OrderedDict + from odoo import http, _ from odoo.osv import expression from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager +from odoo.addons.account.controllers.download_docs import _get_zip_headers from odoo.exceptions import AccessError, MissingError -from collections import OrderedDict from odoo.http import request @@ -121,7 +123,20 @@ class PortalAccount(CustomerPortal): except (AccessError, MissingError): return request.redirect('/my') - if report_type in ('html', 'pdf', 'text'): + if report_type == 'pdf' and download: + # Send & Print wizard with only the 'download' checkbox to get the official attachment(s) + template = request.env.ref(invoice_sudo._get_mail_template()) + attachment_ids = invoice_sudo._generate_pdf_and_send_invoice(template, checkbox_send_mail=False, checkbox_download=True) + attachments = request.env['ir.attachment'].browse(attachment_ids) + if len(attachments) > 1: + filename = invoice_sudo._get_invoice_report_filename(extension='zip') + zip_content = attachments.sudo()._build_zip_from_attachments() + headers = _get_zip_headers(zip_content, filename) + return request.make_response(zip_content, headers) + headers = self._get_http_headers(invoice_sudo, report_type, attachments.raw, download) + return request.make_response(attachments.raw, list(headers.items())) + + elif report_type in ('html', 'pdf', 'text'): return self._show_report(model=invoice_sudo, report_type=report_type, report_ref='account.account_invoices', download=download) values = self._invoice_get_page_view_values(invoice_sudo, access_token, **kw) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 9f5d9e3564b..d0ac5700c06 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -4294,15 +4294,16 @@ class AccountMove(models.Model): return rslt - def _get_pdf_and_send_invoice_vals(self, template): + def _get_pdf_and_send_invoice_vals(self, template, **kwargs): return { 'mail_template_id': template.id, 'move_ids': self.ids, 'checkbox_send_mail': True, 'checkbox_download': False, + **kwargs, } - def _generate_pdf_and_send_invoice(self, template, from_cron=True, allow_fallback_pdf=True): + def _generate_pdf_and_send_invoice(self, template, from_cron=True, allow_fallback_pdf=True, **kwargs): """ Generate the pdf for the current invoices and send them by mail using the send & print wizard. :param from_cron: Flag indicating if the method is called from a cron. In that case, we avoid raising any @@ -4310,11 +4311,11 @@ class AccountMove(models.Model): :param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a proforma PDF report instead. """ - composer_vals = self._get_pdf_and_send_invoice_vals(template) + composer_vals = self._get_pdf_and_send_invoice_vals(template, **kwargs) composer = self.env['account.move.send'].create(composer_vals) # from_cron=True to log errors in chatter instead of raise - composer.action_send_and_print(from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf) + return composer.action_send_and_print(from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf) def get_invoice_pdf_report_attachment(self): if len(self) < 2 and self.invoice_pdf_report_id: diff --git a/addons/account/models/ir_attachment.py b/addons/account/models/ir_attachment.py index b0701fce0ff..201deb20131 100644 --- a/addons/account/models/ir_attachment.py +++ b/addons/account/models/ir_attachment.py @@ -10,6 +10,7 @@ except ImportError: from PyPDF2.utils import PdfReadError import io import logging +import zipfile _logger = logging.getLogger(__name__) @@ -17,6 +18,14 @@ _logger = logging.getLogger(__name__) class IrAttachment(models.Model): _inherit = 'ir.attachment' + def _build_zip_from_attachments(self): + """ Return the zip bytes content resulting from compressing the attachments in `self`""" + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, 'w', compression=zipfile.ZIP_DEFLATED) as zipfile_obj: + for attachment in self: + zipfile_obj.writestr(attachment.display_name, attachment.raw) + return buffer.getvalue() + # ------------------------------------------------------------------------- # EDI # ------------------------------------------------------------------------- diff --git a/addons/account/wizard/account_move_send.py b/addons/account/wizard/account_move_send.py index facf14c1d04..4aca179eedd 100644 --- a/addons/account/wizard/account_move_send.py +++ b/addons/account/wizard/account_move_send.py @@ -646,7 +646,10 @@ class AccountMoveSend(models.Model): # _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) + if from_cron: + return attachment_ids + else: + return self._download(attachment_ids) return {'type': 'ir.actions.act_window_close'} diff --git a/addons/portal/controllers/portal.py b/addons/portal/controllers/portal.py index 77254a0ccbe..b4a147bacb4 100644 --- a/addons/portal/controllers/portal.py +++ b/addons/portal/controllers/portal.py @@ -489,14 +489,18 @@ class CustomerPortal(Controller): method_name = '_render_qweb_%s' % (report_type) report = getattr(ReportAction, method_name)(report_ref, list(model.ids), data={'report_type': report_type})[0] - reporthttpheaders = [ - ('Content-Type', 'application/pdf' if report_type == 'pdf' else 'text/html'), - ('Content-Length', len(report)), - ] + headers = self._get_http_headers(model, report_type, report, download) + return request.make_response(report, headers=list(headers.items())) + + def _get_http_headers(self, model, report_type, report, download): + headers = { + 'Content-Type': 'application/pdf' if report_type == 'pdf' else 'text/html', + 'Content-Length': len(report), + } if report_type == 'pdf' and download: filename = "%s.pdf" % (re.sub('\W+', '-', model._get_report_base_filename())) - reporthttpheaders.append(('Content-Disposition', content_disposition(filename))) - return request.make_response(report, headers=reporthttpheaders) + headers['Content-Disposition'] = content_disposition(filename) + return headers def get_error(e, path=''): """ Recursively dereferences `path` (a period-separated sequence of dict diff --git a/addons/snailmail_account/models/account_move.py b/addons/snailmail_account/models/account_move.py index 5e623fade6d..5f3fe326d94 100644 --- a/addons/snailmail_account/models/account_move.py +++ b/addons/snailmail_account/models/account_move.py @@ -7,8 +7,8 @@ from odoo import models class AccountMove(models.Model): _inherit = "account.move" - def _get_pdf_and_send_invoice_vals(self, template): + def _get_pdf_and_send_invoice_vals(self, template, **kwargs): # EXTENDS account - vals = super()._get_pdf_and_send_invoice_vals(template) + vals = super()._get_pdf_and_send_invoice_vals(template, **kwargs) vals['checkbox_send_by_post'] = False return vals