diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py index 42fb4c35c24..81ec17034ef 100644 --- a/addons/l10n_ch/models/res_bank.py +++ b/addons/l10n_ch/models/res_bank.py @@ -307,6 +307,8 @@ class ResPartnerBank(models.Model): 'quiet': 1, 'mask': 'ch_cross', 'value': '\n'.join(self._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)), + # Swiss QR code requires Error Correction Level = 'M' by specification + 'barLevel': 'M', } return super()._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) diff --git a/addons/l10n_ch/tests/test_swissqr.py b/addons/l10n_ch/tests/test_swissqr.py index da268eb5c64..d2fa7aec2b5 100644 --- a/addons/l10n_ch/tests/test_swissqr.py +++ b/addons/l10n_ch/tests/test_swissqr.py @@ -147,6 +147,7 @@ class TestSwissQR(AccountTestInvoicingCommon): expected_params = { 'barcode_type': 'QR', + 'barLevel': 'M', 'width': 256, 'height': 256, 'quiet': 1, diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 2d4af05a743..1d3d1327a79 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1975,17 +1975,20 @@ class ReportController(http.Controller): #------------------------------------------------------ # Misc. route utils #------------------------------------------------------ - @http.route(['/report/barcode', '/report/barcode//'], type='http', auth="public") - def report_barcode(self, type, value, width=600, height=100, humanreadable=0, quiet=1, mask=None): + @http.route(['/report/barcode', '/report/barcode//'], type='http', auth="public") + def report_barcode(self, barcode_type, value, **kwargs): """Contoller able to render barcode images thanks to reportlab. - Samples: + Samples:: + - - :param type: Accepted types: 'Codabar', 'Code11', 'Code128', 'EAN13', 'EAN8', 'Extended39', - 'Extended93', 'FIM', 'I2of5', 'MSI', 'POSTNET', 'QR', 'Standard39', 'Standard93', - 'UPCA', 'USPS_4State' + :param barcode_type: Accepted types: 'Codabar', 'Code11', 'Code128', 'EAN13', 'EAN8', + 'Extended39', 'Extended93', 'FIM', 'I2of5', 'MSI', 'POSTNET', 'QR', 'Standard39', + 'Standard93', 'UPCA', 'USPS_4State' + :param width: Pixel width of the barcode + :param height: Pixel height of the barcode :param humanreadable: Accepted values: 0 (default) or 1. 1 will insert the readable value at the bottom of the output image :param quiet: Accepted values: 0 (default) or 1. 1 will display white @@ -1993,10 +1996,11 @@ class ReportController(http.Controller): :param mask: The mask code to be used when rendering this QR-code. Masks allow adding elements on top of the generated image, such as the Swiss cross in the center of QR-bill codes. + :param barLevel: QR code Error Correction Levels. Default is 'L'. + ref: https://hg.reportlab.com/hg-public/reportlab/file/830157489e00/src/reportlab/graphics/barcode/qr.py#l101 """ try: - barcode = request.env['ir.actions.report'].barcode(type, value, width=width, - height=height, humanreadable=humanreadable, quiet=quiet, mask=mask) + barcode = request.env['ir.actions.report'].barcode(barcode_type, value, **kwargs) except (ValueError, AttributeError): raise werkzeug.exceptions.HTTPException(description='Cannot convert into barcode.') diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index dedcca3c196..70bce3ac0be 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -533,7 +533,25 @@ class IrActionsReport(models.Model): return report_obj.with_context(context).sudo().search(conditions, limit=1) @api.model - def barcode(self, barcode_type, value, width=600, height=100, humanreadable=0, quiet=1, mask=None): + def barcode(self, barcode_type, value, **kwargs): + defaults = { + 'width': (600, int), + 'height': (100, int), + 'humanreadable': (False, lambda x: bool(int(x))), + 'quiet': (True, lambda x: bool(int(x))), + 'mask': (None, lambda x: x), + 'barBorder': (4, int), + # The QR code can have different layouts depending on the Error Correction Level + # See: https://en.wikipedia.org/wiki/QR_code#Error_correction + # Level 'L' – up to 7% damage (default) + # Level 'M' – up to 15% damage (i.e. required by l10n_ch QR bill) + # Level 'Q' – up to 25% damage + # Level 'H' – up to 30% damage + 'barLevel': ('L', lambda x: x in ('L', 'M', 'Q', 'H') and x or 'L'), + } + kwargs = {k: validator(kwargs.get(k, v)) for k, (v, validator) in defaults.items()} + kwargs['humanReadable'] = kwargs.pop('humanreadable') + if barcode_type == 'UPCA' and len(value) in (11, 12, 13): barcode_type = 'EAN13' if len(value) in (11, 12): @@ -544,34 +562,31 @@ class IrActionsReport(models.Model): elif barcode_type == 'DataMatrix' and not self.datamatrix_available(): # fallback to avoid stacktrack because reportlab won't recognize the type and error message isn't useful/will be blocking barcode_type = 'Code128' - try: - width, height, humanreadable, quiet = int(width), int(height), bool(int(humanreadable)), bool(int(quiet)) + elif barcode_type == 'QR': # for `QR` type, `quiet` is not supported. And is simply ignored. # But we can use `barBorder` to get a similar behaviour. - bar_border = 4 - if barcode_type == 'QR' and quiet: - bar_border = 0 + if kwargs['quiet']: + kwargs['barBorder'] = 0 - barcode = createBarcodeDrawing( - barcode_type, value=value, format='png', width=width, height=height, - humanReadable=humanreadable, quiet=quiet, barBorder=bar_border - ) + try: + barcode = createBarcodeDrawing(barcode_type, value=value, format='png', **kwargs) # If a mask is asked and it is available, call its function to # post-process the generated QR-code image - if mask: + if kwargs['mask']: available_masks = self.get_available_barcode_masks() - mask_to_apply = available_masks.get(mask) + mask_to_apply = available_masks.get(kwargs['mask']) if mask_to_apply: - mask_to_apply(width, height, barcode) + mask_to_apply(kwargs['width'], kwargs['height'], barcode) return barcode.asString('png') except (ValueError, AttributeError): if barcode_type == 'Code128': raise ValueError("Cannot convert into barcode.") + elif barcode_type == 'QR': + raise ValueError("Cannot convert into QR code.") else: - return self.barcode('Code128', value, width=width, height=height, - humanreadable=humanreadable, quiet=quiet) + return self.barcode('Code128', value, **kwargs) @api.model def get_available_barcode_masks(self):