[REM] l10n_ch_qr_iban: merge module into l10n_ch

This module had been introduced in stable to add a field to store the QR-IBAN in case it differed from the regular account number. We now merge it into the original module and make it so that the only way to define a QR-IBAN is to populate this field (no more magic with account number).
This commit is contained in:
oco-odoo
2021-04-01 17:06:11 +00:00
parent ab179fb600
commit d9c97e8cd1
17 changed files with 111 additions and 194 deletions
+1
View File
@@ -3,6 +3,7 @@
from . import models
from . import report
from . import wizard
from odoo import api, SUPERUSER_ID
+10 -4
View File
@@ -9,7 +9,7 @@
'description': """
Swiss localization
==================
This module defines a chart of account for Switzerland (Swiss PME/KMU 2015), taxes and enables the generation of ISR when you print an invoice or send it by mail.
This module defines a chart of account for Switzerland (Swiss PME/KMU 2015), taxes and enables the generation of ISR and QR-bill when you print an invoice or send it by mail.
An ISR will be generated if you specify the information it needs :
- The bank account you expect to be paid on must be set, and have a valid postal reference.
@@ -18,11 +18,17 @@ An ISR will be generated if you specify the information it needs :
- You must have set the postal references of your bank.
- Your invoice must be in EUR or CHF (as ISRs do not accept other currencies)
The generation of the ISR is automatic if you meet the previous criteria.
A QR-bill will be generated if:
- The partner set on your invoice has a complete address (street, city, postal code and country) in Switzerland
- The option to generate the Swiss QR-code is selected on the invoice (done by default)
- A correct account number/QR IBAN is set on your bank journal
- (when using a QR-IBAN): the payment reference of the invoice is a QR-reference
The generation of the ISR and QR-bill is automatic if you meet the previous criteria.
Here is how it works:
- Printing the invoice will trigger the download of two files: the invoice, and its ISR
- Clicking the 'Send by mail' button will attach two files to your draft mail : the invoice, and the corresponding ISR.
- Printing the invoice will trigger the download of three files: the invoice, its ISR and its QR-bill
- Clicking the 'Send by mail' button will attach three files to your draft mail : the invoice, the ISR and the QR-bill.
""",
'version': '11.0',
'category': 'Accounting/Localizations/Account Charts',
+2 -4
View File
@@ -119,9 +119,7 @@ class AccountMove(models.Model):
(3) 1: control digit for identification number and reference
"""
for record in self:
has_qriban = record.partner_bank_id and record.partner_bank_id._is_qr_iban() or False
isr_subscription = record.l10n_ch_isr_subscription
if (has_qriban or isr_subscription) and record.name:
if (record.partner_bank_id.l10n_ch_qr_iban or record.l10n_ch_isr_subscription) and record.name:
id_number = record._get_isrb_id_number()
if id_number:
id_number = id_number.zfill(l10n_ch_ISR_ID_NUM_LENGTH)
@@ -246,7 +244,7 @@ class AccountMove(models.Model):
for inv in self:
if inv.move_type == 'in_invoice' and inv.company_id.account_fiscal_country_id.code == "CH":
partner_bank = inv.partner_bank_id
needs_isr_ref = partner_bank._is_qr_iban() or partner_bank._is_isr_issuer()
needs_isr_ref = partner_bank.l10n_ch_qr_iban or partner_bank._is_isr_issuer()
if needs_isr_ref and not inv._has_isr_ref():
inv.l10n_ch_isr_needs_fixing = True
continue
+67 -22
View File
@@ -4,9 +4,10 @@
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.tools.misc import mod10r
from odoo.exceptions import UserError
import werkzeug.urls
@@ -37,6 +38,30 @@ def _is_l10n_ch_isr_issuer(account_ref, currency_code):
return _is_l10n_ch_postal(account_ref)
return False
def validate_qr_iban(qr_iban):
# Check first if it's a valid IBAN.
validate_iban(qr_iban)
# We sanitize first so that _check_qr_iban_range() can extract correct IID from IBAN to validate it.
sanitized_qr_iban = sanitize_account_number(qr_iban)
if sanitized_qr_iban[:2] != 'CH':
raise ValidationError(_("QR-IBAN numbers are only available in Switzerland."))
# Now, check if it's valid QR-IBAN (based on its IID).
if not check_qr_iban_range(sanitized_qr_iban):
raise ValidationError(_("QR-IBAN '%s' is invalid.") % qr_iban)
return True
def check_qr_iban_range(iban):
if not iban or len(iban) < 9:
return False
iid_start_index = 4
iid_end_index = 8
iid = iban[iid_start_index : iid_end_index+1]
return re.match(r'\d+', iid) and 30000 <= int(iid) <= 31999 # Those values for iid are reserved for QR-IBANs only
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
@@ -49,6 +74,14 @@ class ResPartnerBank(models.Model):
"your own account. The client number is mostly 6 numbers without -, while the postal account number can "
"be e.g. 01-162-8")
l10n_ch_qr_iban = fields.Char(string='QR-IBAN',
compute='_compute_l10n_ch_qr_iban',
store=True,
readonly=False,
help="Put the QR-IBAN here for your own bank accounts. That way, you can "
"still use the main IBAN in the Account Number while you will see the "
"QR-IBAN for the barcode. ")
# fields to configure ISR payment slip generation
l10n_ch_isr_subscription_chf = fields.Char(string='CHF ISR Subscription Number', help='The subscription number provided by the bank or Postfinance to identify the bank, used to generate ISR in CHF. eg. 01-162-8')
l10n_ch_isr_subscription_eur = fields.Char(string='EUR ISR Subscription Number', help='The subscription number provided by the bank or Postfinance to identify the bank, used to generate ISR in EUR. eg. 03-162-5')
@@ -104,6 +137,33 @@ class ResPartnerBank(models.Model):
bank.sanitized_acc_number = bank.acc_number
super(ResPartnerBank, self - postal_banks)._compute_sanitized_acc_number()
@api.depends('acc_number')
def _compute_l10n_ch_qr_iban(self):
for record in self:
try:
validate_qr_iban(record.acc_number)
valid_qr_iban = True
except ValidationError:
valid_qr_iban = False
if valid_qr_iban:
record.l10n_ch_qr_iban = record.sanitized_acc_number
else:
record.l10n_ch_qr_iban = None
@api.model
def create(self, vals):
if vals.get('l10n_ch_qr_iban'):
validate_qr_iban(vals['l10n_ch_qr_iban'])
vals['l10n_ch_qr_iban'] = pretty_iban(normalize_iban(vals['l10n_ch_qr_iban']))
return super().create(vals)
def write(self, vals):
if vals.get('l10n_ch_qr_iban'):
validate_qr_iban(vals['l10n_ch_qr_iban'])
vals['l10n_ch_qr_iban'] = pretty_iban(normalize_iban(vals['l10n_ch_qr_iban']))
return super().write(vals)
@api.model
def _get_supported_account_types(self):
rslt = super(ResPartnerBank, self)._get_supported_account_types()
@@ -197,10 +257,13 @@ class ResPartnerBank(models.Model):
# just like ISR number for invoices)
reference_type = 'NON'
reference = ''
if self._is_qr_iban():
acc_number = self.sanitized_acc_number
if self.l10n_ch_qr_iban:
# _check_for_qr_code_errors ensures we can't have a QR-IBAN without a QR-reference here
reference_type = 'QRR'
reference = structured_communication
acc_number = sanitize_account_number(self.l10n_ch_qr_iban)
currency = currency or self.currency_id or self.company_id.currency_id
@@ -208,7 +271,7 @@ class ResPartnerBank(models.Model):
'SPC', # QR Type
'0200', # Version
'1', # Coding Type
self.sanitized_acc_number, # IBAN
acc_number, # IBAN / QR-IBAN
'K', # Creditor Address Type
(self.acc_holder_name or self.partner_id.name)[:70], # Creditor Name
creditor_addr_1, # Creditor Address Line 1
@@ -248,24 +311,6 @@ class ResPartnerBank(models.Model):
line_2 = partner.zip + ' ' + partner.city
return line_1[:70], line_2[:70]
def _check_qr_iban_range(self, iban):
if not iban or len(iban) < 9:
return False
iid_start_index = 4
iid_end_index = 8
iid = iban[iid_start_index : iid_end_index+1]
return re.match('\d+', iid) \
and 30000 <= int(iid) <= 31999 # Those values for iid are reserved for QR-IBANs only
def _is_qr_iban(self):
""" Tells whether or not this bank account has a QR-IBAN account number.
QR-IBANs are specific identifiers used in Switzerland as references in
QR-codes. They are formed like regular IBANs, but are actually something
different.
"""
return self.acc_type == 'iban' \
and self._check_qr_iban_range(self.sanitized_acc_number)
@api.model
def _is_qr_reference(self, reference):
""" Checks whether the given reference is a QR-reference, i.e. it is
@@ -300,7 +345,7 @@ class ResPartnerBank(models.Model):
if debtor_partner and not _partner_fields_set(debtor_partner):
return _("The partner the QR-code must have a complete postal address (street, zip, city and country).")
if self._is_qr_iban() and not self._is_qr_reference(structured_communication):
if self.l10n_ch_qr_iban and not self._is_qr_reference(structured_communication):
return _("When using a QR-IBAN as the destination account of a QR-code, the payment reference must be a QR-reference.")
return super()._check_for_qr_code_errors(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
+9 -5
View File
@@ -37,7 +37,9 @@
<div id="receipt_indication_zone" class="swissqr_column_left receipt_indication_zone">
<div class="swissqr_text">
<span class="title">Account / Payable to</span><br/>
<span class="content" t-field="o.partner_bank_id.acc_number"/><br/>
<span class="content" t-field="o.partner_bank_id.acc_number" t-if="not o.partner_bank_id.l10n_ch_qr_iban"/>
<span class="content" t-field="o.partner_bank_id.l10n_ch_qr_iban" t-if="o.partner_bank_id.l10n_ch_qr_iban"/>
<br/>
<span class="content" t-field="o.company_id.name"/><br/>
<span class="content" t-field="o.company_id.street"/><br/>
<span class="content" t-field="o.company_id.country_id.code"/>
@@ -45,7 +47,7 @@
<span class="content" t-field="o.company_id.city"/><br/>
</div>
<t t-if="o.partner_bank_id._is_qr_iban()">
<t t-if="o.partner_bank_id.l10n_ch_qr_iban">
<div class="swissqr_text">
<span class="title">Reference</span><br/>
<span class="content" t-esc="o.space_qrr_reference(o.payment_reference)"/><br/>
@@ -93,7 +95,9 @@
<div id="indications_zone" class="swissqr_column_right indication_zone">
<div class="swissqr_text">
<span class="title">Account / Payable to</span><br/>
<span class="content" t-field="o.partner_bank_id.acc_number"/><br/>
<span class="content" t-field="o.partner_bank_id.acc_number" t-if="not o.partner_bank_id.l10n_ch_qr_iban"/>
<span class="content" t-field="o.partner_bank_id.l10n_ch_qr_iban" t-if="o.partner_bank_id.l10n_ch_qr_iban"/>
<br/>
<span class="content" t-field="o.company_id.name"/><br/>
<span class="content" t-field="o.company_id.street"/><br/>
<span class="content" t-field="o.company_id.country_id.code"/>
@@ -101,14 +105,14 @@
<span class="content" t-field="o.company_id.city"/><br/>
</div>
<t t-if="o.partner_bank_id._is_qr_iban()">
<t t-if="o.partner_bank_id.l10n_ch_qr_iban">
<div class="swissqr_text">
<span class="title">Reference</span><br/>
<span class="content" t-esc="o.space_qrr_reference(o.payment_reference)"/><br/>
</div>
</t>
<t t-set="additional_info" t-value="(o.ref or o.name if o.partner_bank_id._is_qr_iban() else o.payment_reference or o.ref or o.name)"/>
<t t-set="additional_info" t-value="(o.ref or o.name if o.partner_bank_id.l10n_ch_qr_iban else o.payment_reference or o.ref or o.name)"/>
<t t-if="additional_info">
<div class="swissqr_text">
<span class="title">Additional information</span><br/>
+1 -1
View File
@@ -165,7 +165,7 @@ class TestSwissQR(AccountTestInvoicingCommon):
def test_swissQR_qriban(self):
# Now use a proper QR-IBAN, we are good to print a QR Bill
qriban_account = self.create_account(QR_IBAN)
self.assertTrue(qriban_account.acc_type, 'qr-iban')
self.assertTrue(qriban_account.l10n_ch_qr_iban)
self.invoice1.partner_bank_id = qriban_account
self.invoice1.action_post()
self.swissqr_generated(self.invoice1, ref_type="QRR")
+1
View File
@@ -7,6 +7,7 @@
<field name="inherit_id" ref="base.view_partner_bank_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='acc_number']" position="after">
<field name="l10n_ch_qr_iban" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
<label for="l10n_ch_postal" string="ISR Client Identification Number" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
<field name="l10n_ch_postal" nolabel="1" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
<field name="l10n_ch_postal" attrs="{'invisible': [('l10n_ch_show_subscription', '=', True)]}"/>
@@ -11,6 +11,7 @@
<field name="l10n_ch_isr_subscription_chf" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
<label for="l10n_ch_postal" string="ISR Client Identification Number" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
<field name="l10n_ch_postal" nolabel="1" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
<field name="l10n_ch_qr_iban" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
</field>
</field>
</record>
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import setup_wizards
+16
View File
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from odoo import api, models
class SwissSetupBarBankConfigWizard(models.TransientModel):
_inherit = 'account.setup.bank.manual.config'
@api.onchange('acc_number')
def _onchange_recompute_qr_iban(self):
# Needed because ORM doesn't properly call the compute in 'new' mode, due to inherits, and
# we want this field to be displayed in the wizard. We need to manually set acc_number
# on the inherits m2o before calling the compute function manually.
self.res_partner_bank_id.acc_number = self.acc_number
self.res_partner_bank_id._compute_l10n_ch_qr_iban()
self.l10n_ch_qr_iban = self.res_partner_bank_id.l10n_ch_qr_iban
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
-23
View File
@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Switzerland - QR-IBAN",
'icon': '/l10n_ch/static/description/icon.png',
'description': """
Swiss localization
==================
Added a QR-IBAN field on bank account.
If this field is empty but the bank account number itself is a valid QR-IBAN number, it will continue using it as QR-IBAN.
However, if the new QR-IBAN field is filled, the value will be used as the QR-IBAN.
This should help for reconciliation on the bank statements where the old IBAN code is still used.
""",
'version': '1.0',
'author': 'Odoo S.A',
'category': 'Localization',
'depends': ['l10n_ch'],
'data': [
'views/res_bank_views.xml',
'views/swissqr_report.xml',
],
'auto_install': True,
}
@@ -1,42 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_ch_qriban
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-02 11:41+0000\n"
"PO-Revision-Date: 2020-09-02 11:41+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_ch_qriban
#: model:ir.model,name:l10n_ch_qriban.model_res_partner_bank
msgid "Bank Accounts"
msgstr ""
#. module: l10n_ch_qriban
#: model:ir.model.fields,help:l10n_ch_qriban.field_account_setup_bank_manual_config__l10n_ch_qr_iban
#: model:ir.model.fields,help:l10n_ch_qriban.field_res_partner_bank__l10n_ch_qr_iban
msgid ""
"Put the QR-IBAN here for your own bank accounts. That way, you can still "
"use the main IBAN in the Account Number while you will see the QR-IBAN for "
"the barcode. "
msgstr ""
#. module: l10n_ch_qriban
#: model:ir.model.fields,field_description:l10n_ch_qriban.field_account_setup_bank_manual_config__l10n_ch_qr_iban
#: model:ir.model.fields,field_description:l10n_ch_qriban.field_res_partner_bank__l10n_ch_qr_iban
msgid "QR-IBAN"
msgstr ""
#. module: l10n_ch_qriban
#: code:addons/l10n_ch_qriban/models/res_bank.py:0
#, python-format
msgid "QR-IBAN '%s' is invalid."
msgstr ""
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import res_bank
-47
View File
@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.addons.base_iban.models.res_partner_bank import normalize_iban, pretty_iban, validate_iban
from odoo.addons.base.models.res_bank import sanitize_account_number
from odoo.exceptions import ValidationError
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
l10n_ch_qr_iban = fields.Char(string='QR-IBAN', help="Put the QR-IBAN here for your own bank accounts. That way, you can "
"still use the main IBAN in the Account Number while you will see the "
"QR-IBAN for the barcode. ")
def _validate_qr_iban(self, qr_iban):
# Check first if it's a valid IBAN.
validate_iban(qr_iban)
# We sanitize first so that _check_qr_iban_range() can extract correct IID from IBAN to validate it.
sanitized_qr_iban = sanitize_account_number(qr_iban)
# Now, check if it's valid QR-IBAN (based on its IID).
if not self._check_qr_iban_range(sanitized_qr_iban):
raise ValidationError(_("QR-IBAN '%s' is invalid.") % qr_iban)
return True
@api.model
def create(self, vals):
if vals.get('l10n_ch_qr_iban'):
self._validate_qr_iban(vals['l10n_ch_qr_iban'])
vals['l10n_ch_qr_iban'] = pretty_iban(normalize_iban(vals['l10n_ch_qr_iban']))
return super().create(vals)
def write(self, vals):
if vals.get('l10n_ch_qr_iban'):
self._validate_qr_iban(vals['l10n_ch_qr_iban'])
vals['l10n_ch_qr_iban'] = pretty_iban(normalize_iban(vals['l10n_ch_qr_iban']))
return super().write(vals)
def _is_qr_iban(self):
return super(ResPartnerBank, self)._is_qr_iban() or self.l10n_ch_qr_iban
def _l10n_ch_get_qr_vals(self, amount, currency, debtor_partner, free_communication, structured_communication):
qr_vals = super()._l10n_ch_get_qr_vals(amount, currency, debtor_partner, free_communication, structured_communication)
# If there is a QR IBAN we use it for the barcode instead of the account number
if self.l10n_ch_qr_iban:
qr_vals[3] = sanitize_account_number(self.l10n_ch_qr_iban)
return qr_vals
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_bank_form" model="ir.ui.view">
<field name="name">l10n_ch_qr.res.partner.bank.form</field>
<field name="model">res.partner.bank</field>
<field name="inherit_id" ref="l10n_ch.isr_partner_bank_form"/>
<field name="arch" type="xml">
<xpath expr="//label[@for='l10n_ch_postal']" position="before">
<field name="l10n_ch_qr_iban" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
</xpath>
</field>
</record>
<!-- Setup wizard view -->
<record id="setup_bank_account_wizard_qr_inherit" model="ir.ui.view">
<field name="name">account.setup.bank.manual.config.form.ch.qr.inherit</field>
<field name="model">account.setup.bank.manual.config</field>
<field name="inherit_id" ref="l10n_ch.setup_bank_account_wizard_inherit"/>
<field name="arch" type="xml">
<field name="l10n_ch_postal" position="after">
<field name="l10n_ch_qr_iban" attrs="{'invisible': [('l10n_ch_show_subscription', '=', False)]}"/>
</field>
</field>
</record>
</odoo>
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="l10n_ch_swissqr_qriban_template" inherit_id="l10n_ch.l10n_ch_swissqr_template">
<xpath expr="//div[@id='receipt_indication_zone']/div[hasclass('swissqr_text')]/span[@t-field='o.partner_bank_id.acc_number']" position="replace">
<span class="content" t-field="o.partner_bank_id.acc_number" t-if="not o.partner_bank_id.l10n_ch_qr_iban"/>
<span class="content" t-field="o.partner_bank_id.l10n_ch_qr_iban" t-if="o.partner_bank_id.l10n_ch_qr_iban"/>
</xpath>
<xpath expr="//div[@id='indications_zone']/div[hasclass('swissqr_text')]/span[@t-field='o.partner_bank_id.acc_number']" position="replace">
<span class="content" t-field="o.partner_bank_id.acc_number" t-if="not o.partner_bank_id.l10n_ch_qr_iban"/>
<span class="content" t-field="o.partner_bank_id.l10n_ch_qr_iban" t-if="o.partner_bank_id.l10n_ch_qr_iban"/>
</xpath>
</template>
</odoo>