Skip to content

Commit bd11146

Browse files
authored
Merge pull request #47996 from frappe/version-15-hotfix
chore: release v15
2 parents 63d165c + 33f1d7a commit bd11146

File tree

41 files changed

+774
-135
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+774
-135
lines changed

erpnext/accounts/doctype/accounts_settings/accounts_settings.json

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
"show_taxes_as_table_in_print",
3939
"column_break_12",
4040
"show_payment_schedule_in_print",
41+
"item_price_settings_section",
42+
"maintain_same_internal_transaction_rate",
43+
"column_break_feyo",
44+
"maintain_same_rate_action",
45+
"role_to_override_stop_action",
4146
"currency_exchange_section",
4247
"allow_stale",
4348
"column_break_yuug",
@@ -540,13 +545,6 @@
540545
"fieldname": "column_break_xrnd",
541546
"fieldtype": "Column Break"
542547
},
543-
{
544-
"default": "0",
545-
"description": "If enabled, Sales Invoice will be generated instead of POS Invoice in POS Transactions for real-time update of G/L and Stock Ledger.",
546-
"fieldname": "use_sales_invoice_in_pos",
547-
"fieldtype": "Check",
548-
"label": "Use Sales Invoice"
549-
},
550548
{
551549
"default": "Buffered Cursor",
552550
"fieldname": "receivable_payable_fetch_method",
@@ -563,6 +561,37 @@
563561
"fieldname": "legacy_section",
564562
"fieldtype": "Section Break",
565563
"label": "Legacy Fields"
564+
},
565+
{
566+
"default": "0",
567+
"fieldname": "maintain_same_internal_transaction_rate",
568+
"fieldtype": "Check",
569+
"label": "Maintain Same Rate Throughout Internal Transaction"
570+
},
571+
{
572+
"default": "Stop",
573+
"depends_on": "maintain_same_internal_transaction_rate",
574+
"fieldname": "maintain_same_rate_action",
575+
"fieldtype": "Select",
576+
"label": "Action if Same Rate is Not Maintained Throughout Internal Transaction",
577+
"mandatory_depends_on": "maintain_same_internal_transaction_rate",
578+
"options": "Stop\nWarn"
579+
},
580+
{
581+
"depends_on": "eval: doc.maintain_same_internal_transaction_rate && doc.maintain_same_rate_action == 'Stop'",
582+
"fieldname": "role_to_override_stop_action",
583+
"fieldtype": "Link",
584+
"label": "Role Allowed to Override Stop Action",
585+
"options": "Role"
586+
},
587+
{
588+
"fieldname": "item_price_settings_section",
589+
"fieldtype": "Section Break",
590+
"label": "Item Price Settings"
591+
},
592+
{
593+
"fieldname": "column_break_feyo",
594+
"fieldtype": "Column Break"
566595
}
567596
],
568597
"icon": "icon-cog",
@@ -599,4 +628,4 @@
599628
"sort_order": "ASC",
600629
"states": [],
601630
"track_changes": 1
602-
}
631+
}

erpnext/accounts/doctype/accounts_settings/accounts_settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class AccountsSettings(Document):
5050
general_ledger_remarks_length: DF.Int
5151
ignore_account_closing_balance: DF.Check
5252
ignore_is_opening_check_for_reporting: DF.Check
53+
maintain_same_internal_transaction_rate: DF.Check
54+
maintain_same_rate_action: DF.Literal["Stop", "Warn"]
5355
make_payment_via_journal_entry: DF.Check
5456
merge_similar_account_heads: DF.Check
5557
over_billing_allowance: DF.Currency
@@ -58,6 +60,7 @@ class AccountsSettings(Document):
5860
receivable_payable_remarks_length: DF.Int
5961
reconciliation_queue_size: DF.Int
6062
role_allowed_to_over_bill: DF.Link | None
63+
role_to_override_stop_action: DF.Link | None
6164
round_row_wise_tax: DF.Check
6265
show_balance_in_coa: DF.Check
6366
show_inclusive_tax_in_print: DF.Check

erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def get_import_status(docname):
277277

278278
@frappe.whitelist()
279279
def get_import_logs(docname: str):
280-
frappe.has_permission("Bank Statement Import")
280+
frappe.has_permission("Bank Statement Import", throw=True)
281281

282282
return frappe.get_all(
283283
"Data Import Log",

erpnext/accounts/doctype/gl_entry/gl_entry.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from frappe.model.document import Document
88
from frappe.model.meta import get_field_precision
99
from frappe.model.naming import set_name_from_naming_options
10-
from frappe.utils import flt, fmt_money, now
10+
from frappe.utils import create_batch, flt, fmt_money, now
1111

1212
import erpnext
1313
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -451,12 +451,15 @@ def rename_gle_sle_docs():
451451
def rename_temporarily_named_docs(doctype):
452452
"""Rename temporarily named docs using autoname options"""
453453
docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation", limit=50000)
454-
for doc in docs_to_rename:
455-
oldname = doc.name
456-
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
457-
newname = doc.name
458-
frappe.db.sql(
459-
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
460-
(newname, now(), oldname),
461-
auto_commit=True,
462-
)
454+
autoname = frappe.get_meta(doctype).autoname
455+
456+
for batch in create_batch(docs_to_rename, 100):
457+
for doc in batch:
458+
oldname = doc.name
459+
set_name_from_naming_options(autoname, doc)
460+
newname = doc.name
461+
frappe.db.sql(
462+
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
463+
(newname, now(), oldname),
464+
)
465+
frappe.db.commit()

erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2806,6 +2806,17 @@ def test_pr_pi_over_billing(self):
28062806
# Test 4 - Since this PI is overbilled by 130% and only 120% is allowed, it will fail
28072807
self.assertRaises(frappe.ValidationError, pi.submit)
28082808

2809+
def test_discount_percentage_not_set_when_amount_is_manually_set(self):
2810+
pi = make_purchase_invoice(do_not_save=True)
2811+
discount_amount = 7
2812+
pi.discount_amount = discount_amount
2813+
pi.save()
2814+
self.assertEqual(pi.additional_discount_percentage, None)
2815+
pi.set_posting_time = 1
2816+
pi.posting_date = add_days(today(), -1)
2817+
pi.save()
2818+
self.assertEqual(pi.discount_amount, discount_amount)
2819+
28092820

28102821
def set_advance_flag(company, flag, default_account):
28112822
frappe.db.set_value(

erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ def setUp(self):
6464
)
6565
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
6666

67+
@change_settings(
68+
"Accounts Settings",
69+
{"maintain_same_internal_transaction_rate": 1, "maintain_same_rate_action": "Stop"},
70+
)
71+
def test_invalid_rate_without_override(self):
72+
from frappe import ValidationError
73+
74+
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice
75+
76+
si = create_sales_invoice(
77+
customer="_Test Internal Customer 3", company="_Test Company", is_internal_customer=1, rate=100
78+
)
79+
pi = make_inter_company_purchase_invoice(si.name)
80+
pi.items[0].rate = 120
81+
82+
with self.assertRaises(ValidationError) as e:
83+
pi.insert()
84+
pi.submit()
85+
self.assertIn("Rate must be same", str(e.exception))
86+
6787
def tearDown(self):
6888
frappe.db.rollback()
6989

@@ -4441,6 +4461,7 @@ def create_sales_invoice(**args):
44414461
si.conversion_rate = args.conversion_rate or 1
44424462
si.naming_series = args.naming_series or "T-SINV-"
44434463
si.cost_center = args.parent_cost_center
4464+
si.is_internal_customer = args.is_internal_customer or 0
44444465

44454466
bundle_id = None
44464467
if si.update_stock and (args.get("batch_no") or args.get("serial_no")):
@@ -4643,6 +4664,12 @@ def create_internal_parties():
46434664
allowed_to_interact_with="_Test Company with perpetual inventory",
46444665
)
46454666

4667+
create_internal_supplier(
4668+
supplier_name="_Test Internal Supplier 3",
4669+
represents_company="_Test Company",
4670+
allowed_to_interact_with="_Test Company",
4671+
)
4672+
46464673

46474674
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
46484675
if not frappe.db.exists("Supplier", supplier_name):

erpnext/accounts/report/accounts_receivable/accounts_receivable.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ <h5 class="text-center">
185185

186186
{% if(!filters.show_future_payments) { %}
187187
<td>
188-
{% if(!(filters.party)) { %}
188+
{% if(!filters.party?.length) { %}
189189
{%= data[i]["party"] %}
190190
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
191191
<br> {%= data[i]["customer_name"] %}
@@ -258,7 +258,7 @@ <h5 class="text-center">
258258
{% if(data[i]["party"]|| "&nbsp;") { %}
259259
{% if(!data[i]["is_total_row"]) { %}
260260
<td>
261-
{% if(!(filters.party)) { %}
261+
{% if(!filters.party?.length) { %}
262262
{%= data[i]["party"] %}
263263
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
264264
<br> {%= data[i]["customer_name"] %}

erpnext/accounts/report/accounts_receivable/accounts_receivable.py

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import frappe
88
from frappe import _, qb, query_builder, scrub
9+
from frappe.desk.reportview import build_match_conditions
910
from frappe.query_builder import Criterion
1011
from frappe.query_builder.functions import Date, Substring, Sum
1112
from frappe.utils import cint, cstr, flt, getdate, nowdate
@@ -126,7 +127,7 @@ def get_data(self):
126127
self.build_data()
127128

128129
def fetch_ple_in_buffered_cursor(self):
129-
query, param = self.ple_query.walk()
130+
query, param = self.ple_query
130131
self.ple_entries = frappe.db.sql(query, param, as_dict=True)
131132

132133
for ple in self.ple_entries:
@@ -140,7 +141,7 @@ def fetch_ple_in_buffered_cursor(self):
140141

141142
def fetch_ple_in_unbuffered_cursor(self):
142143
self.ple_entries = []
143-
query, param = self.ple_query.walk()
144+
query, param = self.ple_query
144145
with frappe.db.unbuffered_cursor():
145146
for ple in frappe.db.sql(query, param, as_dict=True, as_iterator=True):
146147
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
@@ -449,16 +450,14 @@ def get_invoice_details(self):
449450
self.invoice_details = frappe._dict()
450451
if self.account_type == "Receivable":
451452
# nosemgrep
452-
si_list = frappe.db.sql(
453-
"""
454-
select name, due_date, po_no
455-
from `tabSales Invoice`
456-
where posting_date <= %s
457-
and company = %s
458-
and docstatus = 1
459-
""",
460-
(self.filters.report_date, self.filters.company),
461-
as_dict=1,
453+
si_list = frappe.get_list(
454+
"Sales Invoice",
455+
filters={
456+
"posting_date": ("<=", self.filters.report_date),
457+
"company": self.filters.company,
458+
"docstatus": 1,
459+
},
460+
fields=["name", "due_date", "po_no"],
462461
)
463462
for d in si_list:
464463
self.invoice_details.setdefault(d.name, d)
@@ -481,33 +480,29 @@ def get_invoice_details(self):
481480

482481
if self.account_type == "Payable":
483482
# nosemgrep
484-
for pi in frappe.db.sql(
485-
"""
486-
select name, due_date, bill_no, bill_date
487-
from `tabPurchase Invoice`
488-
where
489-
posting_date <= %s
490-
and company = %s
491-
and docstatus = 1
492-
""",
493-
(self.filters.report_date, self.filters.company),
494-
as_dict=1,
495-
):
483+
invoices = frappe.get_list(
484+
"Purchase Invoice",
485+
filters={
486+
"posting_date": ("<=", self.filters.report_date),
487+
"company": self.filters.company,
488+
"docstatus": 1,
489+
},
490+
fields=["name", "due_date", "bill_no", "bill_date"],
491+
)
492+
493+
for pi in invoices:
496494
self.invoice_details.setdefault(pi.name, pi)
497495

498496
# Invoices booked via Journal Entries
499497
# nosemgrep
500-
journal_entries = frappe.db.sql(
501-
"""
502-
select name, due_date, bill_no, bill_date
503-
from `tabJournal Entry`
504-
where
505-
posting_date <= %s
506-
and company = %s
507-
and docstatus = 1
508-
""",
509-
(self.filters.report_date, self.filters.company),
510-
as_dict=1,
498+
journal_entries = frappe.get_list(
499+
"Journal Entry",
500+
filters={
501+
"posting_date": ("<=", self.filters.report_date),
502+
"company": self.filters.company,
503+
"docstatus": 1,
504+
},
505+
fields=["name", "due_date", "bill_no", "bill_date"],
511506
)
512507

513508
for je in journal_entries:
@@ -856,12 +851,18 @@ def prepare_ple_query(self):
856851
else:
857852
query = query.select(ple.remarks)
858853

854+
query, param = query.walk()
855+
856+
match_conditions = build_match_conditions("Payment Ledger Entry")
857+
if match_conditions:
858+
query += " AND " + match_conditions
859+
859860
if self.filters.get("group_by_party"):
860-
query = query.orderby(self.ple.party, self.ple.posting_date)
861+
query += f" ORDER BY `{self.ple.party.name}`, `{self.ple.posting_date.name}`"
861862
else:
862-
query = query.orderby(self.ple.posting_date, self.ple.party)
863+
query += f" ORDER BY `{self.ple.posting_date.name}`, `{self.ple.party.name}`"
863864

864-
self.ple_query = query
865+
self.ple_query = (query, param)
865866

866867
def get_sales_invoices_or_customers_based_on_sales_person(self):
867868
if self.filters.get("sales_person"):

erpnext/accounts/report/calculated_discount_mismatch/__init__.py

Whitespace-only changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
2+
// For license information, please see license.txt
3+
4+
// frappe.query_reports["Calculated Discount Mismatch"] = {
5+
// filters: [
6+
// {
7+
// "fieldname": "my_filter",
8+
// "label": __("My Filter"),
9+
// "fieldtype": "Data",
10+
// "reqd": 1,
11+
// },
12+
// ],
13+
// };
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"add_total_row": 0,
3+
"add_translate_data": 0,
4+
"columns": [],
5+
"creation": "2025-06-06 17:09:50.681090",
6+
"disabled": 0,
7+
"docstatus": 0,
8+
"doctype": "Report",
9+
"filters": [],
10+
"idx": 0,
11+
"is_standard": "Yes",
12+
"letter_head": "",
13+
"letterhead": null,
14+
"modified": "2025-06-06 18:09:18.221911",
15+
"modified_by": "Administrator",
16+
"module": "Accounts",
17+
"name": "Calculated Discount Mismatch",
18+
"owner": "Administrator",
19+
"prepared_report": 0,
20+
"ref_doctype": "Version",
21+
"report_name": "Calculated Discount Mismatch",
22+
"report_type": "Script Report",
23+
"roles": [
24+
{
25+
"role": "System Manager"
26+
},
27+
{
28+
"role": "Administrator"
29+
},
30+
{
31+
"role": "Accounts Manager"
32+
},
33+
{
34+
"role": "Accounts User"
35+
}
36+
],
37+
"timeout": 0
38+
}

0 commit comments

Comments
 (0)