Skip to content

Commit 44b935b

Browse files
authored
Merge pull request #52925 from frappe/version-16-hotfix
chore: release v16
2 parents 9a0b54c + 16e29d8 commit 44b935b

201 files changed

Lines changed: 5648 additions & 1407 deletions

File tree

Some content is hidden

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

erpnext/accounts/doctype/accounts_settings/accounts_settings.json

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"enable_common_party_accounting",
2121
"allow_multi_currency_invoices_against_single_party_account",
2222
"confirm_before_resetting_posting_date",
23+
"analytics_section",
24+
"enable_accounting_dimensions",
25+
"column_break_vtnr",
26+
"enable_discounts_and_margin",
2327
"journals_section",
2428
"merge_similar_account_heads",
2529
"deferred_accounting_settings_section",
@@ -51,12 +55,16 @@
5155
"allow_pegged_currencies_exchange_rates",
5256
"column_break_yuug",
5357
"stale_days",
58+
"payments_tab",
5459
"section_break_jpd0",
5560
"auto_reconcile_payments",
5661
"auto_reconciliation_job_trigger",
5762
"reconciliation_queue_size",
5863
"column_break_resa",
5964
"exchange_gain_loss_posting_date",
65+
"payment_options_section",
66+
"enable_loyalty_point_program",
67+
"column_break_ctam",
6068
"invoicing_settings_tab",
6169
"accounts_transactions_settings_section",
6270
"over_billing_allowance",
@@ -281,7 +289,7 @@
281289
},
282290
{
283291
"default": "0",
284-
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
292+
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\" rel=\"noopener noreferrer\">Common Party</a>",
285293
"fieldname": "enable_common_party_accounting",
286294
"fieldtype": "Check",
287295
"label": "Enable Common Party Accounting"
@@ -637,6 +645,49 @@
637645
"fieldname": "budget_section",
638646
"fieldtype": "Section Break",
639647
"label": "Budget"
648+
},
649+
{
650+
"fieldname": "analytics_section",
651+
"fieldtype": "Section Break",
652+
"label": "Analytical Accounting"
653+
},
654+
{
655+
"fieldname": "column_break_vtnr",
656+
"fieldtype": "Column Break"
657+
},
658+
{
659+
"default": "0",
660+
"description": "Apply discounts and margins on products",
661+
"fieldname": "enable_discounts_and_margin",
662+
"fieldtype": "Check",
663+
"label": "Enable Discounts and Margin"
664+
},
665+
{
666+
"fieldname": "payments_tab",
667+
"fieldtype": "Tab Break",
668+
"label": "Payments"
669+
},
670+
{
671+
"fieldname": "payment_options_section",
672+
"fieldtype": "Section Break",
673+
"label": "Payment Options"
674+
},
675+
{
676+
"default": "0",
677+
"fieldname": "enable_loyalty_point_program",
678+
"fieldtype": "Check",
679+
"label": "Enable Loyalty Point Program"
680+
},
681+
{
682+
"fieldname": "column_break_ctam",
683+
"fieldtype": "Column Break"
684+
},
685+
{
686+
"default": "0",
687+
"description": "Enable cost center, projects and other custom accounting dimensions",
688+
"fieldname": "enable_accounting_dimensions",
689+
"fieldtype": "Check",
690+
"label": "Enable Accounting Dimensions"
640691
}
641692
],
642693
"grid_page_length": 50,
@@ -646,7 +697,7 @@
646697
"index_web_pages_for_search": 1,
647698
"issingle": 1,
648699
"links": [],
649-
"modified": "2026-01-11 18:30:45.968531",
700+
"modified": "2026-02-04 17:15:38.609327",
650701
"modified_by": "Administrator",
651702
"module": "Accounts",
652703
"name": "Accounts Settings",

erpnext/accounts/doctype/accounts_settings/accounts_settings.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@
1212

1313
from erpnext.accounts.utils import sync_auto_reconcile_config
1414

15+
SELLING_DOCTYPES = [
16+
"Sales Invoice",
17+
"Sales Order",
18+
"Delivery Note",
19+
"Quotation",
20+
"Sales Invoice Item",
21+
"Sales Order Item",
22+
"Delivery Note Item",
23+
"Quotation Item",
24+
"POS Invoice",
25+
"POS Invoice Item",
26+
]
27+
28+
BUYING_DOCTYPES = [
29+
"Purchase Invoice",
30+
"Purchase Order",
31+
"Purchase Receipt",
32+
"Purchase Invoice Item",
33+
"Purchase Order Item",
34+
"Purchase Receipt Item",
35+
]
36+
1537

1638
class AccountsSettings(Document):
1739
# begin: auto-generated types
@@ -43,9 +65,12 @@ class AccountsSettings(Document):
4365
default_ageing_range: DF.Data | None
4466
delete_linked_ledger_entries: DF.Check
4567
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
68+
enable_accounting_dimensions: DF.Check
4669
enable_common_party_accounting: DF.Check
70+
enable_discounts_and_margin: DF.Check
4771
enable_fuzzy_matching: DF.Check
4872
enable_immutable_ledger: DF.Check
73+
enable_loyalty_point_program: DF.Check
4974
enable_party_matching: DF.Check
5075
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
5176
fetch_valuation_rate_for_internal_transaction: DF.Check
@@ -98,6 +123,18 @@ def validate(self):
98123
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
99124
self.enable_payment_schedule_in_print()
100125

126+
if old_doc.enable_accounting_dimensions != self.enable_accounting_dimensions:
127+
toggle_accounting_dimension_sections(not self.enable_accounting_dimensions)
128+
clear_cache = True
129+
130+
if old_doc.enable_discounts_and_margin != self.enable_discounts_and_margin:
131+
toggle_sales_discount_section(not self.enable_discounts_and_margin)
132+
clear_cache = True
133+
134+
if old_doc.enable_loyalty_point_program != self.enable_loyalty_point_program:
135+
toggle_loyalty_point_program_section(not self.enable_loyalty_point_program)
136+
clear_cache = True
137+
101138
if clear_cache:
102139
frappe.clear_cache()
103140

@@ -154,3 +191,36 @@ def drop_ar_sql_procedures(self):
154191

155192
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
156193
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
194+
195+
196+
def toggle_accounting_dimension_sections(hide):
197+
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")
198+
for doctype in accounting_dimension_doctypes:
199+
create_property_setter_for_hiding_field(doctype, "accounting_dimensions_section", hide)
200+
201+
202+
def toggle_sales_discount_section(hide):
203+
for doctype in SELLING_DOCTYPES + BUYING_DOCTYPES:
204+
meta = frappe.get_meta(doctype)
205+
if meta.has_field("additional_discount_section"):
206+
create_property_setter_for_hiding_field(doctype, "additional_discount_section", hide)
207+
if meta.has_field("discount_and_margin"):
208+
create_property_setter_for_hiding_field(doctype, "discount_and_margin", hide)
209+
210+
211+
def toggle_loyalty_point_program_section(hide):
212+
for doctype in SELLING_DOCTYPES:
213+
meta = frappe.get_meta(doctype)
214+
if meta.has_field("loyalty_points_redemption"):
215+
create_property_setter_for_hiding_field(doctype, "loyalty_points_redemption", hide)
216+
217+
218+
def create_property_setter_for_hiding_field(doctype, field_name, hide):
219+
make_property_setter(
220+
doctype,
221+
field_name,
222+
"hidden",
223+
hide,
224+
"Check",
225+
validate_fields_for_doctype=False,
226+
)

erpnext/accounts/doctype/bank_transaction/bank_transaction.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ def before_update_after_submit(self):
139139
self.set_status()
140140

141141
def on_cancel(self):
142+
self.ignore_linked_doctypes = ["GL Entry"]
143+
142144
for payment_entry in self.payment_entries:
143145
self.delink_payment_entry(payment_entry)
144146

@@ -373,11 +375,12 @@ def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries
373375
("unallocated_amount", "bank_account"),
374376
as_dict=True,
375377
)
378+
bt_bank_account = frappe.db.get_value("Bank Account", bt.bank_account, "account")
376379

377-
if bt.bank_account != gl_bank_account:
380+
if bt_bank_account != gl_bank_account:
378381
frappe.throw(
379382
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
380-
bt.bank_account, payment_entry.payment_entry, gl_bank_account
383+
bt_bank_account, payment_entry.payment_entry, gl_bank_account
381384
)
382385
)
383386

erpnext/accounts/doctype/financial_report_template/financial_report_engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from frappe.query_builder import Case
1616
from frappe.query_builder.functions import Sum
1717
from frappe.utils import cstr, date_diff, flt, getdate
18-
from pypika.terms import LiteralValue
18+
from pypika.terms import Bracket, LiteralValue
1919

2020
from erpnext import get_company_currency
2121
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -732,7 +732,7 @@ def _execute_with_permissions(self, query, doctype):
732732
user_conditions = build_match_conditions(doctype)
733733

734734
if user_conditions:
735-
query = query.where(LiteralValue(user_conditions))
735+
query = query.where(Bracket(LiteralValue(user_conditions)))
736736

737737
return query.run(as_dict=True)
738738

erpnext/accounts/doctype/fiscal_year/fiscal_year.py

Lines changed: 32 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import frappe
66
from dateutil.relativedelta import relativedelta
7-
from frappe import _
7+
from frappe import _, cint
88
from frappe.model.document import Document
99
from frappe.utils import add_days, add_years, cstr, getdate
1010

@@ -33,24 +33,6 @@ def validate(self):
3333
self.validate_dates()
3434
self.validate_overlap()
3535

36-
if not self.is_new():
37-
year_start_end_dates = frappe.db.sql(
38-
"""select year_start_date, year_end_date
39-
from `tabFiscal Year` where name=%s""",
40-
(self.name),
41-
)
42-
43-
if year_start_end_dates:
44-
if (
45-
getdate(self.year_start_date) != year_start_end_dates[0][0]
46-
or getdate(self.year_end_date) != year_start_end_dates[0][1]
47-
):
48-
frappe.throw(
49-
_(
50-
"Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."
51-
)
52-
)
53-
5436
def validate_dates(self):
5537
self.validate_from_to_dates("year_start_date", "year_end_date")
5638
if self.is_short_year:
@@ -66,28 +48,20 @@ def validate_dates(self):
6648
frappe.exceptions.InvalidDates,
6749
)
6850

69-
def on_update(self):
70-
check_duplicate_fiscal_year(self)
71-
frappe.cache().delete_value("fiscal_years")
51+
def validate_overlap(self):
52+
fy = frappe.qb.DocType("Fiscal Year")
7253

73-
def on_trash(self):
74-
frappe.cache().delete_value("fiscal_years")
54+
name = self.name or self.year
7555

76-
def validate_overlap(self):
77-
existing_fiscal_years = frappe.db.sql(
78-
"""select name from `tabFiscal Year`
79-
where (
80-
(%(year_start_date)s between year_start_date and year_end_date)
81-
or (%(year_end_date)s between year_start_date and year_end_date)
82-
or (year_start_date between %(year_start_date)s and %(year_end_date)s)
83-
or (year_end_date between %(year_start_date)s and %(year_end_date)s)
84-
) and name!=%(name)s""",
85-
{
86-
"year_start_date": self.year_start_date,
87-
"year_end_date": self.year_end_date,
88-
"name": self.name or "No Name",
89-
},
90-
as_dict=True,
56+
existing_fiscal_years = (
57+
frappe.qb.from_(fy)
58+
.select(fy.name)
59+
.where(
60+
(fy.year_start_date <= self.year_end_date)
61+
& (fy.year_end_date >= self.year_start_date)
62+
& (fy.name != name)
63+
)
64+
.run(as_dict=True)
9165
)
9266

9367
if existing_fiscal_years:
@@ -110,44 +84,41 @@ def validate_overlap(self):
11084
frappe.throw(
11185
_(
11286
"Year start date or end date is overlapping with {0}. To avoid please set company"
113-
).format(existing.name),
87+
).format(frappe.get_desk_link("Fiscal Year", existing.name, open_in_new_tab=True)),
11488
frappe.NameError,
11589
)
11690

11791

118-
@frappe.whitelist()
119-
def check_duplicate_fiscal_year(doc):
120-
year_start_end_dates = frappe.db.sql(
121-
"""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""",
122-
(doc.name),
92+
def auto_create_fiscal_year():
93+
fy = frappe.qb.DocType("Fiscal Year")
94+
95+
# Skipped auto-creating Short Year, as it has very rare use case.
96+
# Reference: https://www.irs.gov/businesses/small-businesses-self-employed/tax-years (US)
97+
follow_up_date = add_days(getdate(), days=3)
98+
fiscal_year = (
99+
frappe.qb.from_(fy)
100+
.select(fy.name)
101+
.where((fy.year_end_date == follow_up_date) & (fy.is_short_year == 0))
102+
.run()
123103
)
124-
for fiscal_year, ysd, yed in year_start_end_dates:
125-
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
126-
not frappe.in_test
127-
):
128-
frappe.throw(
129-
_(
130-
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
131-
).format(fiscal_year)
132-
)
133104

134-
135-
@frappe.whitelist()
136-
def auto_create_fiscal_year():
137-
for d in frappe.db.sql(
138-
"""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""
139-
):
105+
for d in fiscal_year:
140106
try:
141107
current_fy = frappe.get_doc("Fiscal Year", d[0])
142108

143-
new_fy = frappe.copy_doc(current_fy, ignore_no_copy=False)
109+
new_fy = frappe.new_doc("Fiscal Year")
110+
new_fy.disabled = cint(current_fy.disabled)
144111

145112
new_fy.year_start_date = add_days(current_fy.year_end_date, 1)
146113
new_fy.year_end_date = add_years(current_fy.year_end_date, 1)
147114

148115
start_year = cstr(new_fy.year_start_date.year)
149116
end_year = cstr(new_fy.year_end_date.year)
150117
new_fy.year = start_year if start_year == end_year else (start_year + "-" + end_year)
118+
119+
for row in current_fy.companies:
120+
new_fy.append("companies", {"company": row.company})
121+
151122
new_fy.auto_created = 1
152123

153124
new_fy.insert(ignore_permissions=True)

0 commit comments

Comments
 (0)