Merge pull request #33841 from barredterra/iban-fixes
feat: format IBAN in read-only fields
This commit is contained in:
commit
a2e5621683
6 changed files with 76 additions and 1 deletions
|
|
@ -993,6 +993,7 @@ class BaseDocument:
|
|||
from frappe.utils import (
|
||||
split_emails,
|
||||
validate_email_address,
|
||||
validate_iban,
|
||||
validate_name,
|
||||
validate_phone_number,
|
||||
validate_phone_number_with_country_code,
|
||||
|
|
@ -1031,6 +1032,9 @@ class BaseDocument:
|
|||
if data_field_options == "URL":
|
||||
validate_url(data, throw=True)
|
||||
|
||||
if data_field_options == "IBAN":
|
||||
validate_iban(data, throw=True)
|
||||
|
||||
def _validate_constants(self):
|
||||
if frappe.flags.in_import or self.is_new() or self.flags.ignore_validate_constants:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
|
|||
}
|
||||
format_for_input(val) {
|
||||
if (this.df.options == "IBAN" && val) {
|
||||
return val.replaceAll(" ", "").replace(/(.{4})(?=.)/g, "$1 ");
|
||||
return frappe.utils.get_formatted_iban(val);
|
||||
}
|
||||
return val == null ? "" : val;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ frappe.form.formatters = {
|
|||
if (!value) return;
|
||||
return `<a href="${value}" title="Open Link" target="_blank">${value}</a>`;
|
||||
}
|
||||
if (df && df.options == "IBAN") {
|
||||
if (!value) return;
|
||||
return frappe.utils.get_formatted_iban(value);
|
||||
}
|
||||
value = value == null ? "" : value;
|
||||
|
||||
return frappe.form.formatters._apply_custom_formatter(value, df);
|
||||
|
|
|
|||
|
|
@ -1134,6 +1134,14 @@ Object.assign(frappe.utils, {
|
|||
return duration;
|
||||
},
|
||||
|
||||
get_formatted_iban(value) {
|
||||
if (!value || ["BI", "SV", "EG", "LY"].some((country) => value.startsWith(country))) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.replaceAll(" ", "").replace(/(.{4})(?=.)/g, "$1 ");
|
||||
},
|
||||
|
||||
seconds_to_duration(seconds, duration_options) {
|
||||
const round = seconds > 0 ? Math.floor : Math.ceil;
|
||||
const total_duration = {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ from frappe.utils import (
|
|||
get_site_info,
|
||||
get_sites,
|
||||
get_url,
|
||||
is_valid_iban,
|
||||
money_in_words,
|
||||
parse_and_map_trackers_from_url,
|
||||
parse_timedelta,
|
||||
|
|
@ -479,6 +480,26 @@ class TestValidationUtils(IntegrationTestCase):
|
|||
for name in invalid_names:
|
||||
self.assertRaises(frappe.InvalidNameError, validate_name, name, True)
|
||||
|
||||
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",
|
||||
]
|
||||
|
||||
for iban in valid_ibans:
|
||||
self.assertTrue(is_valid_iban(iban))
|
||||
|
||||
for not_iban in invalid_ibans:
|
||||
self.assertFalse(is_valid_iban(not_iban))
|
||||
|
||||
|
||||
class TestImage(IntegrationTestCase):
|
||||
def test_strip_exif_data(self):
|
||||
|
|
|
|||
|
|
@ -261,6 +261,44 @@ def validate_url(
|
|||
return is_valid
|
||||
|
||||
|
||||
def validate_iban(iban: str, throw: bool = False) -> bool:
|
||||
from frappe import _
|
||||
|
||||
valid = is_valid_iban(iban)
|
||||
if not valid and throw:
|
||||
frappe.throw(frappe._("'{0}' is not a valid IBAN").format(frappe.bold(iban)))
|
||||
|
||||
return valid
|
||||
|
||||
|
||||
def is_valid_iban(iban: str) -> bool:
|
||||
"""
|
||||
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
||||
"""
|
||||
if not iban:
|
||||
return False
|
||||
|
||||
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 = iban.replace(" ", "").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]
|
||||
|
||||
try:
|
||||
to_check = int("".join(encoded))
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
return to_check % 97 == 1
|
||||
|
||||
|
||||
def random_string(length: int) -> str:
|
||||
"""generate a random string"""
|
||||
import string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue