[IMP] account{_edi}{_ubl_cii},l10n_es_edi_facturae: Send and Print mass download

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
This commit is contained in:
Claire Bretton (clbr)
2023-10-09 13:25:32 +00:00
parent fa64a23f1c
commit 38fd690434
10 changed files with 136 additions and 109 deletions
+1 -1
View File
@@ -3,4 +3,4 @@
from . import portal
from . import terms
from . import download_edi_docs
from . import download_docs
@@ -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)
+3 -27
View File
@@ -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. """
+71 -2
View File
@@ -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)
@@ -1647,17 +1647,5 @@ if records:
</field>
</record>
<record id="model_account_download_electronic_invoice" model="ir.actions.server">
<field name="name">Download Electronic Invoices</field>
<field name="model_id" ref="model_account_move"/>
<field name="binding_model_id" ref="model_account_move"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
action = records._action_download_electronic_invoice()
</field>
</record>
</data>
</odoo>
+50 -41
View File
@@ -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'}
@@ -104,7 +104,7 @@
</record>
<record id="invoice_send" model="ir.actions.server">
<field name="name">Send</field>
<field name="name">Send &amp; Print</field>
<field name="state">code</field>
<field name="model_id" ref="model_account_move"/>
<field name="binding_model_id" ref="model_account_move"/>
@@ -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
@@ -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 \
@@ -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
# -------------------------------------------------------------------------