[IMP][l10n_ch] account: allow for QR bills to be printed in batch

The only option to print a QR Invoice was to go on the invoice page and to click on the Print QR Button.

This PR allows for a user to print multiple QR codes, selected from the Invoices view.
The download occurs normally if all invoices are valid and QR-printable.
A wizard opens when the whole invoice selection isn't valid. It allows to download the invoices as a single pdf or to see a list of the ones that could not be printed in the QR format.
The wizard's text depends on the number of QR-valid, ISR-valid and classic (non QR, non ISR) invoices that the user tried to print.

All invoices for which a print is asked will be printed with a QR bill if it is possible. If not, this will check if printing in the ISR format is possible. Lastly, if none is possible, the classic invoice will be printed.
The behaviour allowed for the removal of the QR PRINT and ISR PRINT buttons on the single invoice view, for a behaviour closer to the original guidelines.

Bills can't be QR printed.

Corrected QR-Bill CSS.

task-2726507

closes odoo/odoo#82341

Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
Camille Spiritus
2022-05-10 16:23:44 +02:00
parent 1a71354b41
commit e720cfe901
20 changed files with 360 additions and 166 deletions
+2 -2
View File
@@ -151,7 +151,7 @@ class ResPartnerBank(models.Model):
return [(code, name) for (code, name, sequence) in all_available]
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency):
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency, raises_error=True):
""" Tells whether or not the criteria to apply QR-generation
method qr_method are met for a payment on this account, in the
given currency, by debtor_partner. This does not impeach generation errors,
@@ -167,4 +167,4 @@ class ResPartnerBank(models.Model):
Returns None if no error was found, or a string describing the first error encountered
so that it can be reported to the user.
"""
return None
return None
@@ -38,7 +38,7 @@ class ResPartnerBank(models.Model):
}
return super()._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency):
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency, raises_error=True):
if qr_method == 'sct_qr':
# Some countries share the same IBAN country code
+2
View File
@@ -36,6 +36,7 @@ Here is how it works:
'depends': ['account', 'l10n_multilang', 'base_iban', 'l10n_din5008'],
'data': [
'security/ir.model.access.csv',
'data/l10n_ch_chart_data.xml',
'data/account.account.template.csv',
'data/l10n_ch_chart_post_data.xml',
@@ -50,6 +51,7 @@ Here is how it works:
'views/account_invoice_view.xml',
'views/res_config_settings_views.xml',
'views/setup_wizard_views.xml',
'views/qr_invoice_wizard_view.xml'
],
'demo': [
-1
View File
@@ -4,7 +4,6 @@
from . import res_config_settings
from . import account_invoice
from . import account_journal
from . import mail_template
from . import res_bank
from . import res_company
from . import account_bank_statement
+47 -36
View File
@@ -1,18 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_split_str
from odoo.tools.misc import mod10r
l10n_ch_ISR_NUMBER_LENGTH = 27
l10n_ch_ISR_ID_NUM_LENGTH = 6
class AccountMove(models.Model):
# NOTE
# The ISR system is kept and taken into account up to September 2022.
# After that, the transition to the QR system will be completed and the ISR system won't exist anymore.
# This means that Odoo v16 shouldn't support the ISR system and all the references to it should be cleaned up by then.
# In the versions leading to that change,
# although the functions related to the ISR are still taken into account and still exist,
# the QR billing is always preferred.
_inherit = 'account.move'
l10n_ch_isr_subscription = fields.Char(compute='_compute_l10n_ch_isr_subscription', help='ISR subscription number identifying your company or your bank to generate ISR.')
@@ -29,6 +35,14 @@ class AccountMove(models.Model):
l10n_ch_currency_name = fields.Char(related='currency_id.name', readonly=True, string="Currency Name", help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.
l10n_ch_isr_needs_fixing = fields.Boolean(compute="_compute_l10n_ch_isr_needs_fixing", help="Used to show a warning banner when the vendor bill needs a correct ISR payment reference. ")
l10n_ch_is_qr_valid = fields.Boolean(compute='_compute_l10n_ch_qr_is_valid', help="Determines whether an invoice can be printed as a QR or not")
@api.depends('partner_id', 'currency_id')
def _compute_l10n_ch_qr_is_valid(self):
for move in self:
move.l10n_ch_is_qr_valid = move.move_type == 'out_invoice' \
and move.partner_bank_id._eligible_for_qr_code('ch_qr', move.partner_id, move.currency_id, raises_error=False)
@api.depends('partner_bank_id.l10n_ch_isr_subscription_eur', 'partner_bank_id.l10n_ch_isr_subscription_chf')
def _compute_l10n_ch_isr_subscription(self):
""" Computes the ISR subscription identifying your company or the bank that allows to generate ISR. And formats it accordingly"""
@@ -278,42 +292,11 @@ class AccountMove(models.Model):
"""
return float_split_str(self.amount_residual, 2)
def isr_print(self):
""" Triggered by the 'Print ISR' button.
"""
self.ensure_one()
if self.l10n_ch_isr_valid:
self.l10n_ch_isr_sent = True
return self.env.ref('l10n_ch.l10n_ch_isr_report').report_action(self)
else:
raise ValidationError(_("""You cannot generate an ISR yet.\n
For this, you need to :\n
- set a valid postal account number (or an IBAN referencing one) for your company\n
- define its bank\n
- associate this bank with a postal reference for the currency used in this invoice\n
- fill the 'bank account' field of the invoice with the postal to be used to receive the related payment. A default account will be automatically set for all invoices created after you defined a postal account for your company."""))
def print_ch_qr_bill(self):
""" Triggered by the 'Print QR-bill' button.
"""
self.ensure_one()
if not self.partner_bank_id:
raise UserError(_("QR-Bill can not be generated on paid invoices. If the invoice is not fully paid, please make sure Recipient Bank field is not empty and try again."))
if not self.partner_bank_id._eligible_for_qr_code('ch_qr', self.partner_id, self.currency_id):
raise UserError(_("Cannot generate the QR-bill. Please check you have configured the address of your company and debtor. If you are using a QR-IBAN, also check the invoice's payment reference is a QR reference."))
self.l10n_ch_isr_sent = True
return self.env.ref('l10n_ch.l10n_ch_qr_report').report_action(self)
def action_invoice_sent(self):
# OVERRIDE
rslt = super(AccountMove, self).action_invoice_sent()
if self.l10n_ch_isr_valid:
if self.l10n_ch_isr_valid or self.l10n_ch_is_qr_valid:
rslt['context']['l10n_ch_mark_isr_as_sent'] = True
return rslt
@api.returns('mail.message', lambda value: value.id)
@@ -346,5 +329,33 @@ class AccountMove(models.Model):
while i > 0:
spaced_qrr_ref = qrr_ref[max(i-5, 0) : i] + ' ' + spaced_qrr_ref
i -= 5
return spaced_qrr_ref
def l10n_ch_action_print_qr(self):
'''
Checks that all invoices can be printed in the QR format.
If so, launches the printing action.
Else, triggers the l10n_ch wizard that will display the informations.
'''
if any(x.move_type != 'out_invoice' for x in self):
raise UserError(_("Only customers invoices can be QR-printed."))
if False in self.mapped('l10n_ch_is_qr_valid'):
return {
'name': (_("Some invoices could not be printed in the QR format")),
'type': 'ir.actions.act_window',
'res_model': 'l10n_ch.qr_invoice.wizard',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'context': {'active_ids': self.ids},
}
return self.env.ref('account.account_invoices').report_action(self)
def _l10n_ch_dispatch_invoices_to_print(self):
qr_invs = self.filtered('l10n_ch_is_qr_valid')
isr_invs = self.filtered('l10n_ch_isr_valid')
return {
'qr': qr_invs,
'isr': isr_invs,
'classic': self - qr_invs - isr_invs,
}
+41 -3
View File
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import io
from odoo import api, models
from odoo.tools.pdf import OdooPdfFileReader, OdooPdfFileWriter
from pathlib import Path
from reportlab.graphics.shapes import Image as ReportLabImage
from reportlab.graphics.shapes import Image as ReportLabImage
from reportlab.lib.units import mm
CH_QR_CROSS_SIZE_RATIO = 0.1522 # Ratio between the side length of the Swiss QR-code cross image and the QR-code's
@@ -26,3 +26,41 @@ class IrActionsReport(models.Model):
cross_path = Path(__file__).absolute().parent / CH_QR_CROSS_FILE
qr_cross = ReportLabImage((width/2 - cross_width/2) / mm, (height/2 - cross_height/2) / mm, cross_width / mm, cross_height / mm, cross_path.as_posix())
barcode_drawing.add(qr_cross)
def _render_qweb_pdf_prepare_streams(self, data, res_ids=None):
# OVERRIDE
res = super()._render_qweb_pdf_prepare_streams(data, res_ids)
if res_ids and self.report_name in ('account.report_invoice_with_payments', 'account.report_invoice'):
invoices = self.env[self.model].browse(res_ids)
# Determine which invoices need a QR/ISR.
qr_inv_ids = []
isr_inv_ids = []
for invoice in invoices:
if invoice.company_id.country_code != 'CH':
continue
if invoice.l10n_ch_is_qr_valid:
qr_inv_ids.append(invoice.id)
elif invoice.l10n_ch_isr_valid:
isr_inv_ids.append(invoice.id)
# Render the additional reports.
streams_to_append = {}
if qr_inv_ids:
qr_res = self.env.ref('l10n_ch.l10n_ch_qr_report')._render_qweb_pdf_prepare_streams(data, res_ids=qr_inv_ids)
for invoice_id, stream in qr_res.items():
streams_to_append[invoice_id] = stream
if isr_inv_ids:
isr_res = self.env.ref('l10n_ch.l10n_ch_isr_report')._render_qweb_pdf_prepare_streams(data, res_ids=qr_inv_ids)
for invoice_id, stream in isr_res.items():
streams_to_append[invoice_id] = stream
# Add to results
for invoice_id, additional_stream in streams_to_append.items():
invoice_stream = res[invoice_id]['stream']
writer = OdooPdfFileWriter()
writer.appendPagesFromReader(OdooPdfFileReader(invoice_stream, strict=False))
writer.appendPagesFromReader(OdooPdfFileReader(additional_stream['stream'], strict=False))
new_pdf_stream = io.BytesIO()
writer.write(new_pdf_stream)
res[invoice_id]['stream'] = new_pdf_stream
invoice_stream.close()
additional_stream['stream'].close()
return res
-54
View File
@@ -1,54 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import api, models
class MailTemplate(models.Model):
_inherit = 'mail.template'
def generate_email(self, res_ids, fields):
""" Method overridden in order to add an attachment containing the ISR
to the draft message when opening the 'send by mail' wizard on an invoice.
This attachment generation will only occur if all the required data are
present on the invoice. Otherwise, no ISR attachment will be created, and
the mail will only contain the invoice (as defined in the mother method).
"""
result = super(MailTemplate, self).generate_email(res_ids, fields)
if self.model != 'account.move':
return result
multi_mode = True
if isinstance(res_ids, int):
res_ids = [res_ids]
multi_mode = False
if self.model == 'account.move':
for record in self.env[self.model].browse(res_ids):
inv_print_name = self._render_field('report_name', record.ids, compute_lang=True)[record.id]
new_attachments = []
if record.l10n_ch_isr_valid:
# We add an attachment containing the ISR
isr_report_name = 'ISR-' + inv_print_name + '.pdf'
isr_pdf = self.env.ref('l10n_ch.l10n_ch_isr_report')._render_qweb_pdf(record.ids)[0]
isr_pdf = base64.b64encode(isr_pdf)
new_attachments.append((isr_report_name, isr_pdf))
if record.partner_bank_id._eligible_for_qr_code('ch_qr', record.partner_id, record.currency_id):
# We add an attachment containing the QR-bill
qr_report_name = 'QR-bill-' + inv_print_name + '.pdf'
qr_pdf = self.env.ref('l10n_ch.l10n_ch_qr_report')._render_qweb_pdf(record.ids)[0]
qr_pdf = base64.b64encode(qr_pdf)
new_attachments.append((qr_report_name, qr_pdf))
record_dict = multi_mode and result[record.id] or result
attachments_list = record_dict.get('attachments', False)
if attachments_list:
attachments_list.extend(new_attachments)
else:
record_dict['attachments'] = new_attachments
return result
+17 -10
View File
@@ -6,10 +6,9 @@ import re
from odoo import api, fields, models, _
from odoo.addons.base.models.res_bank import sanitize_account_number
from odoo.addons.base_iban.models.res_partner_bank import normalize_iban, pretty_iban, validate_iban
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
from odoo.tools.misc import mod10r
ISR_SUBSCRIPTION_CODE = {'CHF': '01', 'EUR': '03'}
CLEARING = "09000"
_re_postal = re.compile('^[0-9]{2}-[0-9]{1,6}-[0-9]$')
@@ -332,15 +331,23 @@ class ResPartnerBank(models.Model):
and re.match('\d+$', reference) \
and reference == mod10r(reference[:-1])
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency):
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency, raises_error=True):
if qr_method == 'ch_qr':
return self.acc_type == 'iban' and \
self.partner_id.country_id.code == 'CH' and \
(not debtor_partner or debtor_partner.country_id.code == 'CH') \
and currency.name in ('EUR', 'CHF')
return super()._eligible_for_qr_code(qr_method, debtor_partner, currency)
error_messages = [_("The QR code could not be generated for the following reason(s):")]
if self.acc_type != 'iban':
error_messages.append(_("The account type isn't QR-IBAN."))
if self.partner_id.country_id.code != 'CH':
error_messages.append(_("Your company isn't located in Switzerland."))
if not debtor_partner or debtor_partner.country_id.code != 'CH':
error_messages.append(_("The debtor partner's address isn't located in Switzerland."))
if currency.id not in (self.env.ref('base.EUR').id, self.env.ref('base.CHF').id):
error_messages.append(_("The currency isn't EUR nor CHF. \r\n"))
if len(error_messages) != 1:
if raises_error:
raise UserError(' '.join(error_messages))
return False
return True
return super()._eligible_for_qr_code(qr_method, debtor_partner, currency, raises_error)
def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
def _partner_fields_set(partner):
+1 -2
View File
@@ -1,5 +1,4 @@
# -*- coding:utf-8 -*-
from odoo import api, models
class ReportSwissQR(models.AbstractModel):
@@ -19,4 +18,4 @@ class ReportSwissQR(models.AbstractModel):
'doc_model': 'account.move',
'docs': docs,
'qr_code_urls': qr_code_urls,
}
}
-1
View File
@@ -10,7 +10,6 @@
<field name="report_file">l10n_ch.qr_report_main</field>
<field name="print_report_name">'QR-bill-%s' % object.name</field>
<field name="paperformat_id" ref="l10n_ch.paperformat_euro_no_margin"/>
<field name="attachment">'QR-bill-' + object.name + '.pdf'</field>
</record>
<template id="l10n_ch_swissqr_template">
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_l10n_ch_qr_invoice_wizard,l10n_ch.qr_invoice.wizard,model_l10n_ch_qr_invoice_wizard,account.group_account_invoice,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_l10n_ch_qr_invoice_wizard l10n_ch.qr_invoice.wizard model_l10n_ch_qr_invoice_wizard account.group_account_invoice 1 1 1 0
@@ -12,6 +12,10 @@ $l10n-ch-qr-ratio: 1.25;
body.l10n_ch_qr, body[data-oe-report="l10n_ch.l10n_ch_qr_report"] {
padding: 0;
span{
padding-left: 10mm;
white-space: nowrap;
}
/* Disable custom bakground */
.o_report_layout_striped {
@@ -26,6 +30,10 @@ body.l10n_ch_qr, body[data-oe-report="l10n_ch.l10n_ch_qr_report"] {
height: 7mm * $l10n-ch-qr-ratio;
padding: 15px;
padding-top: 150px;
h1{
padding-left:20mm;
white-space:nowrap;
}
}
.swissqr_content_v2 {
+1
View File
@@ -4,6 +4,7 @@
from . import test_ch_qr_code
from . import test_swissqr
from . import test_l10n_ch_isr_print
from . import test_l10n_ch_qr_print
from . import test_vendor_bill_isr
from . import test_onchange_l10n_ch_postal
from . import test_gen_isr_reference
@@ -14,7 +14,7 @@ class ISRTest(AccountTestInvoicingCommon):
def print_isr(self, invoice):
try:
invoice.isr_print()
invoice.action_invoice_sent()
return True
except ValidationError:
return False
@@ -69,4 +69,7 @@ class ISRTest(AccountTestInvoicingCommon):
'invoice_line_ids': [(0, 0, {'product_id': self.product_a.id})],
})
invoice_eur.action_post()
self.assertFalse(self.print_isr(invoice_eur))
#a normal invoice will still get printed
self.assertTrue(self.print_isr(invoice_eur))
# However, a isr bill can't be printed with those infos
self.assertFalse(invoice_eur.l10n_ch_isr_valid)
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.exceptions import UserError
from odoo.tests import tagged
_logger = logging.getLogger(__name__)
@tagged('post_install_l10n', 'post_install', '-at_install')
class QRPrintTest(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ch.l10nch_chart_template'):
super().setUpClass(chart_template_ref=chart_template_ref)
# the partner must be located in Switzerland.
cls.partner = cls.env['res.partner'].create({
'name': 'Bobby',
'country_id': cls.env.ref('base.ch').id,
})
# The bank account must be QR-compatible
cls.qr_bank_account = cls.env['res.partner.bank'].create({
'acc_number': "CH4431999123000889012",
'partner_id': cls.env.company.partner_id.id,
'l10n_ch_isr_subscription_chf': '01-39139-1',
})
cls.correct_invoice_chf = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner.id,
'partner_bank_id': cls.qr_bank_account.id,
'currency_id': cls.env.ref('base.CHF').id,
'invoice_date': '2019-01-01',
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id})],
})
cls.correct_invoice_eur = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner.id,
'partner_bank_id': cls.qr_bank_account.id,
'currency_id': cls.env.ref('base.EUR').id,
'invoice_date': '2019-01-01',
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id})],
})
cls.wrong_partner_invoice = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner_a.id,
'partner_bank_id': cls.qr_bank_account.id,
'currency_id': cls.env.ref('base.EUR').id,
'invoice_date': '2019-01-01',
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id})],
})
def print_qr_bill(self, invoice):
try:
invoice.action_invoice_sent()
return True
except UserError as e:
_logger.warning(e.name)
return False
def test_print_qr(self):
self.correct_invoice_chf.action_post()
self.assertTrue(self.print_qr_bill(self.correct_invoice_chf))
#The QR can also be printed if the currency is EUR
self.env.ref('base.EUR').active = True
self.correct_invoice_eur.action_post()
self.assertTrue(self.print_qr_bill(self.correct_invoice_eur))
#A normal invoice will be printed if the partner is not from Switzerland
self.wrong_partner_invoice.action_post()
self.assertTrue(self.print_qr_bill(self.wrong_partner_invoice))
#However, a qr bill can't be printed with those infos
self.assertFalse(self.wrong_partner_invoice.l10n_ch_is_qr_valid)
+4 -2
View File
@@ -3,6 +3,7 @@
import time
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.exceptions import UserError
from odoo.tests import tagged
CH_IBAN = 'CH15 3881 5158 3845 3843 7'
@@ -164,8 +165,9 @@ class TestSwissQR(AccountTestInvoicingCommon):
def test_swissQR_missing_bank(self):
# Let us test the generation of a SwissQR for an invoice, first by showing an
# QR is included in the invoice is only generated when Odoo has all the data it needs.
self.invoice1.action_post()
self.swissqr_not_generated(self.invoice1)
with self.assertRaises(UserError), self.cr.savepoint():
self.invoice1.action_post()
self.swissqr_not_generated(self.invoice1)
def test_swissQR_iban(self):
# Now we add an account for payment to our invoice
+13 -52
View File
@@ -9,58 +9,7 @@
<xpath expr="//field[@name='is_move_sent']" position="after">
<field name="l10n_ch_isr_sent" invisible="1"/>
<field name="l10n_ch_currency_name" invisible="1" readonly="1"/>
</xpath>
<xpath expr="//button[@id='account_invoice_payment_btn']" position="before">
<button id="l10n_ch_btn_isr_print_highlight"
name="isr_print"
string="Print ISR"
type="object"
attrs="{'invisible':['|', '|', '|', '|',
('move_type', 'not in', ('out_invoice', 'out_refund')),
('l10n_ch_isr_sent', '=', True),
('state', '!=', 'posted'),
('payment_state', 'not in', ('not_paid', 'partial')),
('l10n_ch_currency_name', 'not in', ['EUR', 'CHF'])]}"
class="oe_highlight"
groups="base.group_user"
/>
</xpath>
<xpath expr="//button[@id='account_invoice_payment_btn']" position="before">
<button id="btn_isr_print_normal"
name="isr_print"
string="Print ISR"
type="object"
attrs="{'invisible':['|', '|', '|', '|',
('move_type', 'not in', ('out_invoice', 'out_refund')),
('l10n_ch_isr_sent', '=', False),
('state', '!=', 'posted'),
('payment_state', 'not in', ('not_paid', 'partial')),
('l10n_ch_currency_name', 'not in', ['EUR', 'CHF'])]}"
groups="base.group_user"
/>
<button
name="print_ch_qr_bill"
string="Print QR-bill"
type="object"
attrs="{'invisible':['|', ('state', '!=', 'posted'),
'|', ('l10n_ch_isr_sent', '=', True),
'|', ('move_type', '!=', 'out_invoice'),
('l10n_ch_currency_name', 'not in', ['EUR', 'CHF'])]}"
groups="base.group_user"
class="oe_highlight"
/>
<button
name="print_ch_qr_bill"
string="Print QR-bill"
type="object"
attrs="{'invisible':['|', ('state', '!=', 'posted'),
'|', ('l10n_ch_isr_sent', '=', False),
'|', ('move_type', '!=', 'out_invoice'),
('l10n_ch_currency_name', 'not in', ['EUR', 'CHF'])]}"
groups="base.group_user"
/>
<field name="l10n_ch_is_qr_valid" invisible="1"/>
</xpath>
<header position="after">
<field name="l10n_ch_isr_needs_fixing" invisible="1"/>
@@ -90,5 +39,17 @@
<field name="res_model">account.move</field>
<field name="search_view_id" ref="isr_invoice_search_view"/>
</record>
<record id="l10n_ch_qr_server_action" model="ir.actions.server">
<field name="name">Print QR Invoices</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
action = records.l10n_ch_action_print_qr()
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="l10n_ch_qr_invoice_wizard_form" model="ir.ui.view">
<field name="name">l10n_ch.qr_invoice.wizard.form</field>
<field name="model">l10n_ch.qr_invoice.wizard</field>
<field name="arch" type="xml">
<form string="QR printing encountered a problem">
<field name='nb_qr_inv' invisible="1"/>
<field name="nb_classic_inv" invisible="1"/>
<field name="nb_isr_inv" invisible="1"/>
<p>
<field name="qr_inv_text"/>
<field name="isr_inv_text"/>
<field name="classic_inv_text"/>
</p>
<p>
To be able to print all invoices in the QR format, you might need to : <br/>
- check the account is a valid QR-IBAN<br/>
- or check your company and the partners are located in Switzerland.<br/>
Press Check Invalid Invoices to see a list of the invoices that were printed without an ISR or a QR.
</p>
<footer>
<button name="print_all_invoices" string="Print All" type="object" class="btn-primary"/>
<button name="action_view_faulty_invoices" string="Check invalid invoices" type="object"/>
</footer>
</form>
</field>
</record>
<record id="l10n_ch_qr_invoice_wizard" model="ir.actions.act_window">
<field name="name">Qr Batch error Wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">l10n_ch.qr_invoice.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="l10n_ch_qr_invoice_wizard_form"/>
<field name="target">new</field>
</record>
</odoo>
+1
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import setup_wizards
from . import qr_invoice_wizard
+100
View File
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class QrInvoiceWizard(models.TransientModel):
'''
Wizard :
When multiple invoices are selected to be printed in the QR-Iban format,
this wizard will appear if one or more invoice(s) could not be QR-printed (wrong format...)
The user will then be able to print the invoices (in the format available, priority : QR --> ISR --> normal)
or to see a list of the non-QR/ISR invoices.
The non-QR/ISR invoices will have a note logged in their chatter, detailing the reason of the failure.
'''
_name = 'l10n_ch.qr_invoice.wizard'
_description = 'Handles problems occurring while creating multiple QR-invoices at once'
nb_qr_inv = fields.Integer(readonly=True)
nb_isr_inv = fields.Integer(readonly=True)
nb_classic_inv = fields.Integer(readonly=True)
qr_inv_text = fields.Text(readonly=True)
isr_inv_text = fields.Text(readonly=True)
classic_inv_text = fields.Text(readonly=True)
@api.model
def default_get(self, fields):
# Extends 'base'.
def determine_invoices_text(nb_inv, inv_format):
'''
Creates a sentence explaining nb_inv invoices could be printed in the inv_format format.
'''
if nb_inv == 0:
return _("No invoice could be printed in the %s format.", inv_format)
if nb_inv == 1:
return _("One invoice could be printed in the %s format.", inv_format)
return _("%s invoices could be printed in the %s format.", nb_inv, inv_format)
if not self._context.get('active_ids'):
raise UserError(_("No invoice was found to be printed."))
invoices = self.env['account.move'].browse(self._context['active_ids'])
companies = invoices.company_id
if len(companies) != 1 or companies[0].country_code != 'CH':
raise UserError(_("All selected invoices must belong to the same Switzerland company"))
results = super().default_get(fields)
dispatched_invoices = invoices._l10n_ch_dispatch_invoices_to_print()
results.update({
'nb_qr_inv': len(dispatched_invoices['qr']),
'nb_isr_inv': len(dispatched_invoices['isr']),
'nb_classic_inv': len(dispatched_invoices['classic']),
'qr_inv_text': determine_invoices_text(nb_inv=len(dispatched_invoices['qr']), inv_format="QR"),
'isr_inv_text': determine_invoices_text(nb_inv=len(dispatched_invoices['isr']), inv_format="ISR"),
'classic_inv_text': determine_invoices_text(nb_inv=len(dispatched_invoices['classic']),
inv_format="classic"),
})
return results
def print_all_invoices(self):
'''
Triggered by the Print All button
'''
all_invoices_ids = self.env.context.get('inv_ids')
return self.env.ref('account.account_invoices').report_action(all_invoices_ids)
def action_view_faulty_invoices(self):
'''
Open a list view of all the invoices that could not be printed in the QR nor the ISR format.
'''
# Prints the error stopping the invoice from being QR-printed in the invoice's chatter.
invoices = self.env['account.move'].browse(self._context['active_ids'])
dispatched_invoices = invoices._l10n_ch_dispatch_invoices_to_print()
faulty_invoices = dispatched_invoices['classic']
# Log a message inside the chatter explaining why the invoice is faulty.
for inv in faulty_invoices:
try:
# The error potentially raised in the following function helps create the wizard's message.
inv.partner_bank_id._eligible_for_qr_code('ch_qr', inv.partner_id, inv.currency_id, raises_error=True)
except UserError as e:
inv.message_post(body=e.name, message_type="comment")
action_vals = {
'name': _("Invalid Invoices"),
'type': 'ir.actions.act_window',
'res_model': 'account.move',
'context': {'create': False},
}
if len(faulty_invoices) == 1:
action_vals.update({
'view_mode': 'form',
'res_id': faulty_invoices.id,
})
else:
action_vals.update({
'view_mode': 'tree',
'domain': [('id', 'in', faulty_invoices.ids)],
})
return action_vals