diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 3c679fa215a..20ce7ca9a4c 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -21,11 +21,39 @@ class BankAccount(Document): def validate(self): self.validate_company() + self.validate_iban() def validate_company(self): if self.is_company_account and not self.company: frappe.throw(_("Company is manadatory for company account")) + def validate_iban(self): + ''' + Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN + ''' + # IBAN field is optional + if not self.iban: + return + + def encode_char(c): + # Position in the alphabet (A=1, B=2, ...) plus nine + return str(9 + ord(c) - 64) + + # remove whitespaces, upper case to get the right number from ord() + iban = ''.join(self.iban.split(' ')).upper() + + # Move country code and checksum from the start to the end + flipped = iban[4:] + iban[:4] + + # Encode characters as numbers + encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped] + + to_check = int(''.join(encoded)) + + if to_check % 97 != 1: + frappe.throw(_('IBAN is not valid')) + + @frappe.whitelist() def make_bank_account(doctype, docname): doc = frappe.new_doc("Bank Account") diff --git a/erpnext/accounts/doctype/bank_account/test_bank_account.py b/erpnext/accounts/doctype/bank_account/test_bank_account.py index 43a3298ec4a..f3bb086fa96 100644 --- a/erpnext/accounts/doctype/bank_account/test_bank_account.py +++ b/erpnext/accounts/doctype/bank_account/test_bank_account.py @@ -4,9 +4,46 @@ from __future__ import unicode_literals import frappe +from frappe import _ +from frappe import ValidationError import unittest # test_records = frappe.get_test_records('Bank Account') class TestBankAccount(unittest.TestCase): - pass + + def test_validate_iban(self): + valid_ibans = [ + 'GB82 WEST 1234 5698 7654 32', + 'DE91 1000 0000 0123 4567 89', + 'FR76 3000 6000 0112 3456 7890 189' + ] + + invalid_ibans = [ + # wrong checksum (3rd place) + 'GB72 WEST 1234 5698 7654 32', + 'DE81 1000 0000 0123 4567 89', + 'FR66 3000 6000 0112 3456 7890 189' + ] + + bank_account = frappe.get_doc({'doctype':'Bank Account'}) + + try: + bank_account.validate_iban() + except AttributeError: + msg = _('BankAccount.validate_iban() failed for empty IBAN') + self.fail(msg=msg) + + for iban in valid_ibans: + bank_account.iban = iban + try: + bank_account.validate_iban() + except ValidationError: + msg = _('BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)) + self.fail(msg=msg) + + for not_iban in invalid_ibans: + bank_account.iban = not_iban + msg = _('BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)) + with self.assertRaises(ValidationError, msg=msg): + bank_account.validate_iban()