From 7cc4b6cfe8828ea551378c39b24745d7b55fa989 Mon Sep 17 00:00:00 2001 From: Yolann Sabaux Date: Thu, 8 Jun 2023 15:15:56 +0000 Subject: [PATCH] [FIX] l10n_ch_sale: create correct payment reference for swiss loca Steps to reproduce: - Install website_sale, contact, l10n_ch - make sure your company is in Switzerland and has all the address information - in settings, activate QR - in Accounting/Journals/Bank:add a bank account (iban: CH4431999123000889012; qr-iban: CH11 3000 5228 1308 3501 F) - install and enable the provider "Wire Transfer" - From the Website, create an order and make sure the customer set an Invoicing address in Switzerland (address + Country) - Validate the order - In Website/unpaid orders: select your order and Confirm the order - Create and confirm the invoice - Print the invoice Issue: User Error is raised Cause: With Swiss QR code you need to have a valid payment reference. That is, an ISR reference such as in https://github.com/odoo/odoo/blob/c6631df1c5b0b6d4c2268a826ca150edd0ca653e/addons/l10n_ch/models/account_invoice.py#L90 But when you create an order from the website, it creates an automatic reference "SO0001" which, when converted to an invoice, stays the same. Since the reference is prepoluted, the `_compute_l10n_ch_isr_number` will not be triggered and therefore will raise an error when trying to print the invoice. Solution: Check if the Customer Invoice Journal uses the swiss reference model, then we know that the swiss loca is installed and can call the correct function to compute the reference/ Note: In the test we check that `payment_custom` is installed. It is because the `_set_pending` method checks taht the payment_provider.code is "custom"="wire_transfer" in which case the sale order reference is computed https://github.com/odoo/odoo/blob/b4ed9537895ecee48ed146add4257af0aebeb3f4/addons/sale/models/payment_transaction.py#L54-L56 opw-3334534 closes odoo/odoo#127338 X-original-commit: dfa4844e57b67d16b181e1e58813cdbbc7efa956 Signed-off-by: Yolann Sabaux (yosa) Signed-off-by: Olivier Colson (oco) Co-authored-by: flvr-odoo --- addons/account/models/account_journal.py | 8 +++++ addons/l10n_ch/models/account_invoice.py | 18 ++++++----- addons/l10n_ch/models/account_journal.py | 13 ++++++++ addons/l10n_ch/tests/test_swissqr.py | 38 +++++++++++++++++++++++ addons/sale/models/payment_transaction.py | 9 ++++-- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index 70f7d98f303..485b707b85f 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -905,3 +905,11 @@ class AccountJournal(models.Model): """ Check if the payment method is available on this journal. """ self.ensure_one() return self.filtered_domain(self.env['account.payment.method']._get_payment_method_domain(payment_method_code)) + + def _process_reference_for_sale_order(self, order_reference): + ''' + returns the order reference to be used for the payment. + Hook to be overriden: see l10n_ch for an example. + ''' + self.ensure_one() + return order_reference diff --git a/addons/l10n_ch/models/account_invoice.py b/addons/l10n_ch/models/account_invoice.py index dc532b1b732..bf2fe51645b 100644 --- a/addons/l10n_ch/models/account_invoice.py +++ b/addons/l10n_ch/models/account_invoice.py @@ -30,16 +30,20 @@ class AccountMove(models.Model): self.ensure_one() if self.partner_bank_id.l10n_ch_qr_iban and self.l10n_ch_is_qr_valid and self.name: invoice_ref = re.sub(r'[^\d]', '', self.name) - # keep only the last digits if it exceed boundaries - ref_payload_len = L10N_CH_QRR_NUMBER_LENGTH - 1 - extra = len(invoice_ref) - ref_payload_len - if extra > 0: - invoice_ref = invoice_ref[extra:] - internal_ref = invoice_ref.zfill(ref_payload_len) - return mod10r(internal_ref) + return self._compute_qrr_number(invoice_ref) else: return False + @api.model + def _compute_qrr_number(self, invoice_ref): + # keep only the last digits if it exceed boundaries + ref_payload_len = L10N_CH_QRR_NUMBER_LENGTH - 1 + extra = len(invoice_ref) - ref_payload_len + if extra > 0: + invoice_ref = invoice_ref[extra:] + internal_ref = invoice_ref.zfill(ref_payload_len) + return mod10r(internal_ref) + def _get_invoice_reference_ch_invoice(self): """ This sets QRR reference number which is generated based on customer's `Bank Account` and set it as `Payment Reference` of the invoice when invoice's journal is using Switzerland's communication standard diff --git a/addons/l10n_ch/models/account_journal.py b/addons/l10n_ch/models/account_journal.py index 2fa30c15f0d..e17fadceb14 100644 --- a/addons/l10n_ch/models/account_journal.py +++ b/addons/l10n_ch/models/account_journal.py @@ -15,3 +15,16 @@ class AccountJournal(models.Model): invoice_reference_model = fields.Selection(selection_add=[ ('ch', 'Switzerland') ], ondelete={'ch': lambda recs: recs.write({'invoice_reference_model': 'odoo'})}) + + def _process_reference_for_sale_order(self, order_reference): + ''' + Returns the order reference to be used for the payment, respecting the QRR standard. + ''' + self.ensure_one() + if self.invoice_reference_model == 'ch': + # converting the sale order name into a unique number. Letters are converted to their base10 value + invoice_ref = "".join([a if a.isdigit() else str(ord(a)) for a in order_reference]) + # id_number = self.company_id.bank_ids.l10n_ch_postal or '' + order_reference = self.env['account.move']._compute_qrr_number(invoice_ref) + return order_reference + return super()._process_reference_for_sale_order(order_reference) diff --git a/addons/l10n_ch/tests/test_swissqr.py b/addons/l10n_ch/tests/test_swissqr.py index 3a57d36410b..f49da319b85 100644 --- a/addons/l10n_ch/tests/test_swissqr.py +++ b/addons/l10n_ch/tests/test_swissqr.py @@ -5,6 +5,7 @@ import time from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.exceptions import UserError from odoo.tests import tagged +from odoo.tools.misc import mod10r CH_IBAN = 'CH15 3881 5158 3845 3843 7' QR_IBAN = 'CH21 3080 8001 2345 6782 7' @@ -180,3 +181,40 @@ class TestSwissQR(AccountTestInvoicingCommon): self.invoice1.partner_bank_id = qriban_account self.invoice1.action_post() self.swissqr_generated(self.invoice1, ref_type="QRR") + + def test_swiss_order_reference_qrr_for_qr_code(self): + """ + Test that the order reference is correctly generated for QR-Code + We summon the skipTest if Sale is not installed (instead of creating a whole module for one test) + """ + if 'sale.order' not in self.env: + self.skipTest('`sale` is not installed') + + payment_custom = self.env['ir.module.module']._get('payment_custom') + if payment_custom.state != 'installed': + self.skipTest("payment_custom module is not installed") + + provider = self.env['payment.provider'].create({ + 'name': 'Test', + 'code': 'custom', + }) + invoice_journal = self.env['account.journal'].search( + [('type', '=', 'sale'), ('company_id', '=', self.env.company.id)], limit=1) + invoice_journal.write({'invoice_reference_model': 'ch'}) + order = self.env['sale.order'].create({ + 'name': "S00001", + 'partner_id': self.env['res.partner'].search([("name", '=', 'Partner')])[0].id, + 'order_line': [ + (0, 0, {'product_id': self.product_a.id, 'price_unit': 100}), + ], + }) + payment_transaction = self.env['payment.transaction'].create({ + 'provider_id': provider.id, + 'sale_order_ids': [order.id], + 'partner_id': self.env['res.partner'].search([("name", '=', 'Partner')])[0].id, + 'amount': 100, + 'currency_id': self.env.company.currency_id.id, + }) + payment_transaction._set_pending() + + self.assertEqual(order.reference, mod10r(order.reference[:-1])) diff --git a/addons/sale/models/payment_transaction.py b/addons/sale/models/payment_transaction.py index 221556cc541..93ac1caf059 100644 --- a/addons/sale/models/payment_transaction.py +++ b/addons/sale/models/payment_transaction.py @@ -17,11 +17,16 @@ class PaymentTransaction(models.Model): def _compute_sale_order_reference(self, order): self.ensure_one() if self.provider_id.so_reference_type == 'so_name': - return order.name + order_reference = order.name else: # self.provider_id.so_reference_type == 'partner' identification_number = order.partner_id.id - return '%s/%s' % ('CUST', str(identification_number % 97).rjust(2, '0')) + order_reference = '%s/%s' % ('CUST', str(identification_number % 97).rjust(2, '0')) + + invoice_journal = self.env['account.journal'].search([('type', '=', 'sale'), ('company_id', '=', self.env.company.id)], limit=1) + order_reference = invoice_journal._process_reference_for_sale_order(order_reference) + + return order_reference @api.depends('sale_order_ids') def _compute_sale_order_ids_nbr(self):