[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:
@@ -3,4 +3,4 @@
|
||||
|
||||
from . import portal
|
||||
from . import terms
|
||||
from . import download_edi_docs
|
||||
from . import download_docs
|
||||
|
||||
+10
-13
@@ -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)
|
||||
@@ -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. """
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 & 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
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user