Register 'spayd' as a proper qr_code_method on res.partner.bank (same extension point account_qr_code_sepa uses for EPC/'sct_qr'), sequence=6 so it's tried before EPC (sequence 20). Eligibility now requires CZK/EUR currency AND a Czech customer country - previously the report template replaced EPC for *any* customer as long as the invoice currency was CZK or EUR, which would incorrectly show a Czech SPAYD code to e.g. a German customer being invoiced in EUR. account.move._generate_qr_code() already tries all registered methods in priority order and the existing div#qrcode block in account.report_invoice_document already renders whatever comes back, so this needs no report template override at all. - Move get_cz_qr_code_base64()/cz_to_iban() from account_move.py into res_partner_bank.py (as _get_qr_code_generation_params/_spayd_to_iban); fix bank_code not being zero-padded to 4 digits, which could throw off the mod-97 checksum for short local bank codes. Verified the checksum against the standard 19-2000145399/0800 -> CZ6508000000192000145399 reference example and general IBAN mod-97 validity. - Remove views/report_invoice.xml (nothing left to override) and the now unnecessary qrcode Python dependency (Odoo's own barcode report action renders the image; this module only supplies the SPAYD string). - Update README FAQ to match the new eligibility rule. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
3.7 KiB
Python
84 lines
3.7 KiB
Python
from odoo import api, models, _
|
|
|
|
|
|
class ResPartnerBank(models.Model):
|
|
_inherit = 'res.partner.bank'
|
|
|
|
@api.model
|
|
def _get_available_qr_methods(self):
|
|
rslt = super()._get_available_qr_methods()
|
|
# Nižšia sequence ako natívny EPC/SCT QR (sequence 20 v
|
|
# account_qr_code_sepa) - SPAYD sa uprednostní, keď je eligible.
|
|
rslt.append(('spayd', _("SPAYD (Czech QR Platba)"), 6))
|
|
return rslt
|
|
|
|
def _get_error_messages_for_qr(self, qr_method, debtor_partner, currency):
|
|
if qr_method == 'spayd':
|
|
if currency.name not in ('CZK', 'EUR'):
|
|
return _("SPAYD podporuje len menu CZK alebo EUR.")
|
|
if not self._spayd_to_iban():
|
|
return _("Bankový účet nemá platné číslo účtu ani IBAN pre SPAYD.")
|
|
if debtor_partner and debtor_partner.country_id and debtor_partner.country_id.code != 'CZ':
|
|
return _("SPAYD je určený pre zákazníkov z Českej republiky.")
|
|
return None
|
|
return super()._get_error_messages_for_qr(qr_method, debtor_partner, currency)
|
|
|
|
def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
|
|
if qr_method == 'spayd':
|
|
return None
|
|
return super()._check_for_qr_code_errors(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
|
|
|
|
def _get_qr_code_generation_params(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
|
|
if qr_method == 'spayd':
|
|
iban = self._spayd_to_iban()
|
|
vs_source = structured_communication or free_communication or ''
|
|
vs = ''.join(filter(str.isdigit, vs_source))[-10:]
|
|
spayd = f"SPD*1.0*ACC:{iban}*AM:{amount:.2f}*CC:{currency.name}*X-VS:{vs}"
|
|
return {
|
|
'barcode_type': 'QR',
|
|
'quiet': 0,
|
|
'width': 128,
|
|
'height': 128,
|
|
'humanreadable': 0,
|
|
'value': spayd,
|
|
}
|
|
return super()._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
|
|
|
|
def _spayd_to_iban(self):
|
|
"""Vráti IBAN pre tento bankový účet, vhodný pre SPAYD QR (ACC pole).
|
|
|
|
Podporuje český lokálny formát ('123-456/0800' alebo '456/0800') aj
|
|
priamo zadaný IBAN (akejkoľvek krajiny).
|
|
"""
|
|
self.ensure_one()
|
|
acc_number = self.acc_number
|
|
if not acc_number:
|
|
return ''
|
|
|
|
clean_acc = acc_number.replace(' ', '').upper()
|
|
|
|
# Už IBAN (ľubovoľná krajina): 2 písmená + 2-číslicový kontrolný súčet.
|
|
if len(clean_acc) >= 4 and clean_acc[:2].isalpha() and clean_acc[2:4].isdigit():
|
|
return clean_acc
|
|
|
|
try:
|
|
parts = clean_acc.split('/')
|
|
if len(parts) != 2:
|
|
return clean_acc # nie je český formát ani IBAN - vrátime pôvodné
|
|
|
|
bank_code = parts[1].zfill(4)
|
|
account_parts = parts[0].split('-')
|
|
if len(account_parts) == 2:
|
|
prefix = account_parts[0].zfill(6)
|
|
main = account_parts[1].zfill(10)
|
|
else:
|
|
prefix = '000000'
|
|
main = account_parts[0].zfill(10)
|
|
|
|
# ISO 7064 mod 97-10: BBAN + krajina ako čísla (C=12, Z=35) + '00'.
|
|
check_string = f"{bank_code}{prefix}{main}123500"
|
|
checksum = 98 - (int(check_string) % 97)
|
|
return f"CZ{str(checksum).zfill(2)}{bank_code}{prefix}{main}"
|
|
except (ValueError, IndexError):
|
|
return clean_acc
|