Merge pull request #50182 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
Diptanil Saha
2025-10-21 18:13:27 +05:30
committed by GitHub
16 changed files with 79 additions and 98 deletions

View File

@@ -132,7 +132,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "IBAN",
"length": 30
"length": 34,
"options": "IBAN"
},
{
"fieldname": "column_break_12",
@@ -208,6 +209,7 @@
"label": "Disabled"
}
],
"grid_page_length": 50,
"links": [
{
"group": "Transactions",
@@ -250,7 +252,7 @@
"link_fieldname": "default_bank_account"
}
],
"modified": "2024-10-30 09:41:14.113414",
"modified": "2025-08-29 12:32:01.081687",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",
@@ -282,6 +284,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "bank,account",
"sort_field": "modified",
"sort_order": "DESC",

View File

@@ -9,7 +9,8 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact,
)
from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
from frappe.utils import comma_and, get_link_to_form, validate_iban
from frappe.utils.deprecations import deprecated
class BankAccount(Document):
@@ -52,7 +53,6 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
self.validate_account()
self.update_default_bank_account()
@@ -72,34 +72,10 @@ class BankAccount(Document):
if self.is_company_account and not self.company:
frappe.throw(_("Company is manadatory for company account"))
@deprecated
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]
try:
to_check = int("".join(encoded))
except ValueError:
frappe.throw(_("IBAN is not valid"))
if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid"))
"""Kept for backward compatibility, will be removed in v16."""
validate_iban(self.iban, throw=True)
def update_default_bank_account(self):
if self.is_default and not self.disabled:

View File

@@ -3,45 +3,8 @@
import unittest
import frappe
from frappe import ValidationError
# test_records = frappe.get_test_records('Bank Account')
class TestBankAccount(unittest.TestCase):
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 = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()
pass

View File

@@ -146,6 +146,7 @@
"fieldname": "iban",
"fieldtype": "Data",
"label": "IBAN",
"options": "IBAN",
"read_only": 1
},
{
@@ -216,7 +217,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2025-09-25 23:44:12.473583",
"modified": "2025-09-26 00:38:17.584694",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Guarantee",

View File

@@ -223,7 +223,8 @@
{
"fieldname": "bank_party_iban",
"fieldtype": "Data",
"label": "Party IBAN (Bank Statement)"
"label": "Party IBAN (Bank Statement)",
"options": "IBAN"
},
{
"fieldname": "bank_party_account_number",
@@ -238,7 +239,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-09-26 17:06:29.207673",
"modified": "2025-10-14 11:53:45.908169",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -272,7 +272,8 @@
"label": "Advance Voucher Type",
"no_copy": 1,
"options": "DocType",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "advance_voucher_no",
@@ -280,13 +281,14 @@
"label": "Advance Voucher No",
"no_copy": 1,
"options": "advance_voucher_type",
"read_only": 1
"read_only": 1,
"search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-09-29 13:01:48.916517",
"modified": "2025-10-20 17:46:47.344089",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -226,7 +226,8 @@
"fetch_from": "bank_account.iban",
"fieldname": "iban",
"fieldtype": "Read Only",
"label": "IBAN"
"label": "IBAN",
"options": "IBAN"
},
{
"fetch_from": "bank_account.branch_code",
@@ -443,11 +444,12 @@
"label": "Phone Number"
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-12-27 21:29:10.361894",
"modified": "2025-08-29 11:52:48.555415",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -482,8 +484,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_preview_popup": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -644,9 +644,8 @@ class PurchaseOrder(BuyingController):
if not self.is_against_so():
return
for item in removed_items:
prev_ordered_qty = (
prev_ordered_qty = flt(
frappe.get_cached_value("Sales Order Item", item.get("sales_order_item"), "ordered_qty")
or 0.0
)
frappe.db.set_value(

View File

@@ -3073,9 +3073,7 @@ def set_balance_in_account_currency(
_("Account: {0} with currency: {1} can not be selected").format(gl_dict.account, account_currency)
)
gl_dict["account_currency"] = (
company_currency if account_currency == company_currency else account_currency
)
gl_dict["account_currency"] = account_currency
# set debit/credit in account currency if not provided
if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):

View File

@@ -1552,7 +1552,9 @@ def get_stock_ledger_preview(doc, filters):
if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note"):
doc.docstatus = 1
doc.make_bundle_using_old_serial_batch_fields()
doc.update_stock_ledger()
columns = get_sl_columns(filters)
sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name, fields)

View File

@@ -17,7 +17,17 @@ erpnext.buying = {
this.setup_queries(doc, cdt, cdn);
super.onload();
this.frm.set_query('shipping_rule', function() {
if (["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype)) {
this.frm.set_query("supplier", function () {
return {
filters: {
is_transporter: 0,
},
};
});
}
this.frm.set_query("shipping_rule", function () {
return {
filters: {
"shipping_rule_type": "Buying"

View File

@@ -394,6 +394,10 @@ erpnext.PointOfSale.Payment = class {
const payments = doc.payments;
const currency = doc.currency;
if (!this.$payment_modes.is(":visible")) {
return;
}
this.$payment_modes.html(
`${payments
.map((p, i) => {
@@ -612,6 +616,10 @@ erpnext.PointOfSale.Payment = class {
const currency = doc.currency;
const label = __("Change Amount");
if (!this.$totals.is(":visible")) {
return;
}
this.$totals.html(
`<div class="col">
<div class="total-label">${__("Grand Total")}</div>

View File

@@ -763,13 +763,11 @@ def update_company_current_month_sales(company):
def update_company_monthly_sales(company):
"""Cache past year monthly sales of every company based on sales invoices"""
import json
from frappe.utils.goal import get_monthly_results
filter_str = f"company = {frappe.db.escape(company)} and status != 'Draft' and docstatus=1"
filter_dict = {"company": company, "status": ["!=", "Draft"], "docstatus": 1}
month_to_value_dict = get_monthly_results(
"Sales Invoice", "base_grand_total", "posting_date", filter_str, "sum"
"Sales Invoice", "base_grand_total", "posting_date", filter_dict, "sum"
)
frappe.db.set_value("Company", company, "sales_monthly_history", json.dumps(month_to_value_dict))

View File

@@ -814,7 +814,8 @@
"depends_on": "eval:doc.salary_mode == 'Bank'",
"fieldname": "iban",
"fieldtype": "Data",
"label": "IBAN"
"label": "IBAN",
"options": "IBAN"
}
],
"icon": "fa fa-user",
@@ -822,7 +823,7 @@
"image_field": "image",
"is_tree": 1,
"links": [],
"modified": "2025-07-04 08:29:34.347269",
"modified": "2025-08-29 11:52:12.819878",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
@@ -864,6 +865,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "employee_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
@@ -871,4 +873,4 @@
"states": [],
"title_field": "employee_name",
"track_changes": 1
}
}

View File

@@ -896,7 +896,11 @@ class update_entries_after:
if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0:
sle.stock_value_difference = (
get_stock_value_difference(
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
sle.item_code,
sle.warehouse,
sle.posting_date,
sle.posting_time,
voucher_detail_no=sle.voucher_detail_no,
)
* -1
)
@@ -2353,7 +2357,9 @@ def is_internal_transfer(sle):
return True
def get_stock_value_difference(item_code, warehouse, posting_date, posting_time, voucher_no=None):
def get_stock_value_difference(
item_code, warehouse, posting_date, posting_time, voucher_no=None, voucher_detail_no=None
):
table = frappe.qb.DocType("Stock Ledger Entry")
posting_datetime = get_combine_datetime(posting_date, posting_time)
@@ -2368,7 +2374,10 @@ def get_stock_value_difference(item_code, warehouse, posting_date, posting_time,
)
)
if voucher_no:
if voucher_detail_no:
query = query.where(table.voucher_detail_no != voucher_detail_no)
elif voucher_no:
query = query.where(table.voucher_no != voucher_no)
difference_amount = query.run()

View File

@@ -484,10 +484,16 @@ def get_documents_with_active_service_level_agreement():
def set_documents_with_active_service_level_agreement():
active = frozenset(
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
)
frappe.cache.set_value("doctypes_with_active_sla", active)
try:
active = frozenset(
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
)
frappe.cache.set_value("doctypes_with_active_sla", active)
except (frappe.DoesNotExistError, frappe.db.TableMissingError):
# This happens during install / uninstall when wildcard hook for SLA intercepts some doc action.
# In both cases, the error can be safely ignored.
active = frozenset()
return active