Skip to content

Commit 72bb3e2

Browse files
authored
Merge pull request #52730 from frappe/version-15-hotfix
chore: release v15
2 parents b340d7c + 128c2bf commit 72bb3e2

29 files changed

Lines changed: 751 additions & 226 deletions

File tree

erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
{
2828
"default": "0",
29-
"depends_on": "eval:parent.doctype == 'Sales Invoice'",
29+
"depends_on": "eval: [\"POS Invoice\", \"Sales Invoice\"].includes(parent.doctype)",
3030
"fieldname": "amount",
3131
"fieldtype": "Currency",
3232
"in_list_view": 1,
@@ -85,7 +85,7 @@
8585
],
8686
"istable": 1,
8787
"links": [],
88-
"modified": "2024-01-23 16:20:06.436979",
88+
"modified": "2026-02-16 20:46:34.592604",
8989
"modified_by": "Administrator",
9090
"module": "Accounts",
9191
"name": "Sales Invoice Payment",
@@ -95,4 +95,4 @@
9595
"sort_field": "modified",
9696
"sort_order": "DESC",
9797
"states": []
98-
}
98+
}

erpnext/accounts/general_ledger.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -804,12 +804,19 @@ def validate_against_pcv(is_opening, posting_date, company):
804804
title=_("Invalid Opening Entry"),
805805
)
806806

807-
last_pcv_date = frappe.db.get_value(
808-
"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(period_end_date)"
809-
)
807+
# Local import so you don't have to touch file-level imports
808+
from frappe.query_builder.functions import Max
809+
810+
pcv = frappe.qb.DocType("Period Closing Voucher")
811+
812+
last_pcv_date = (
813+
frappe.qb.from_(pcv)
814+
.select(Max(pcv.period_end_date))
815+
.where((pcv.docstatus == 1) & (pcv.company == company))
816+
).run(pluck=True)[0]
810817

811818
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
812-
message = _("Books have been closed till the period ending on {0}").format(formatdate(last_pcv_date))
819+
message = _("Books have been closed till the period ending on {0}.").format(formatdate(last_pcv_date))
813820
message += "</br >"
814821
message += _("You cannot create/amend any accounting entries till this date.")
815822
frappe.throw(message, title=_("Period Closed"))

erpnext/accounts/report/profitability_analysis/profitability_analysis.py

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44

55
import frappe
6-
from frappe import _
6+
from frappe import _, qb
7+
from frappe.query_builder import Criterion
78
from frappe.utils import cstr, flt
89

910
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
@@ -33,11 +34,19 @@ def execute(filters=None):
3334

3435
def get_accounts_data(based_on, company):
3536
if based_on == "Cost Center":
36-
return frappe.db.sql(
37-
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
38-
from `tabCost Center` where company=%s order by name""",
39-
company,
40-
as_dict=True,
37+
cc = qb.DocType("Cost Center")
38+
return (
39+
qb.from_(cc)
40+
.select(
41+
cc.name,
42+
cc.parent_cost_center.as_("parent_account"),
43+
cc.cost_center_name.as_("account_name"),
44+
cc.lft,
45+
cc.rgt,
46+
)
47+
.where(cc.company.eq(company))
48+
.orderby(cc.name)
49+
.run(as_dict=True)
4150
)
4251
elif based_on == "Project":
4352
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
@@ -206,27 +215,38 @@ def set_gl_entries_by_account(
206215
company, from_date, to_date, based_on, gl_entries_by_account, ignore_closing_entries=False
207216
):
208217
"""Returns a dict like { "account": [gl entries], ... }"""
209-
additional_conditions = []
218+
gl = qb.DocType("GL Entry")
219+
acc = qb.DocType("Account")
220+
221+
conditions = []
222+
conditions.append(gl.company.eq(company))
223+
conditions.append(gl[based_on].notnull())
224+
conditions.append(gl.is_cancelled.eq(0))
225+
226+
if from_date and to_date:
227+
conditions.append(gl.posting_date.between(from_date, to_date))
228+
elif from_date and not to_date:
229+
conditions.append(gl.posting_date.gte(from_date))
230+
elif not from_date and to_date:
231+
conditions.append(gl.posting_date.lte(to_date))
210232

211233
if ignore_closing_entries:
212-
additional_conditions.append("and voucher_type !='Period Closing Voucher'")
213-
214-
if from_date:
215-
additional_conditions.append("and posting_date >= %(from_date)s")
216-
217-
gl_entries = frappe.db.sql(
218-
"""select posting_date, {based_on} as based_on, debit, credit,
219-
is_opening, (select root_type from `tabAccount` where name = account) as type
220-
from `tabGL Entry` where company=%(company)s
221-
{additional_conditions}
222-
and posting_date <= %(to_date)s
223-
and {based_on} is not null
224-
and is_cancelled = 0
225-
order by {based_on}, posting_date""".format(
226-
additional_conditions="\n".join(additional_conditions), based_on=based_on
227-
),
228-
{"company": company, "from_date": from_date, "to_date": to_date},
229-
as_dict=True,
234+
conditions.append(gl.voucher_type.ne("Period Closing Voucher"))
235+
236+
root_subquery = qb.from_(acc).select(acc.root_type).where(acc.name.eq(gl.account))
237+
gl_entries = (
238+
qb.from_(gl)
239+
.select(
240+
gl.posting_date,
241+
gl[based_on].as_("based_on"),
242+
gl.debit,
243+
gl.credit,
244+
gl.is_opening,
245+
root_subquery.as_("type"),
246+
)
247+
.where(Criterion.all(conditions))
248+
.orderby(gl[based_on], gl.posting_date)
249+
.run(as_dict=True)
230250
)
231251

232252
for entry in gl_entries:

erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
5151
entries = {}
5252
for name, details in gle_map.items():
5353
for entry in details:
54-
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
54+
tax_amount, total_amount, grand_total, base_total, base_tax_withholding_net_total = 0, 0, 0, 0, 0
5555
tax_withholding_category, rate = None, None
5656
bill_no, bill_date = "", ""
5757
party = entry.party or entry.against
@@ -83,6 +83,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
8383
# back calculate total amount from rate and tax_amount
8484
base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0])
8585
total_amount = grand_total = base_total
86+
base_tax_withholding_net_total = total_amount
8687

8788
else:
8889
if tax_amount and rate:
@@ -93,12 +94,16 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
9394

9495
grand_total = values[1]
9596
base_total = values[2]
97+
base_tax_withholding_net_total = total_amount
9698

9799
if voucher_type == "Purchase Invoice":
100+
base_tax_withholding_net_total = values[0]
98101
bill_no = values[3]
99102
bill_date = values[4]
103+
100104
else:
101105
total_amount += entry.credit
106+
base_tax_withholding_net_total = total_amount
102107

103108
if tax_amount:
104109
if party_map.get(party, {}).get("party_type") == "Supplier":
@@ -125,6 +130,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
125130
"rate": rate,
126131
"total_amount": total_amount,
127132
"grand_total": grand_total,
133+
"base_tax_withholding_net_total": base_tax_withholding_net_total,
128134
"base_total": base_total,
129135
"tax_amount": tax_amount,
130136
"transaction_date": posting_date,
@@ -252,14 +258,14 @@ def get_columns(filters):
252258
"width": 60,
253259
},
254260
{
255-
"label": _("Total Amount"),
256-
"fieldname": "total_amount",
261+
"label": _("Tax Withholding Net Total"),
262+
"fieldname": "base_tax_withholding_net_total",
257263
"fieldtype": "Float",
258-
"width": 120,
264+
"width": 150,
259265
},
260266
{
261-
"label": _("Base Total"),
262-
"fieldname": "base_total",
267+
"label": _("Taxable Amount"),
268+
"fieldname": "total_amount",
263269
"fieldtype": "Float",
264270
"width": 120,
265271
},
@@ -270,10 +276,16 @@ def get_columns(filters):
270276
"width": 120,
271277
},
272278
{
273-
"label": _("Grand Total"),
279+
"label": _("Grand Total (Company Currency)"),
280+
"fieldname": "base_total",
281+
"fieldtype": "Float",
282+
"width": 150,
283+
},
284+
{
285+
"label": _("Grand Total (Transaction Currency)"),
274286
"fieldname": "grand_total",
275287
"fieldtype": "Float",
276-
"width": 120,
288+
"width": 170,
277289
},
278290
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
279291
{

erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ def test_tax_withholding_for_customers(self):
3535
result = execute(filters)[1]
3636
expected_values = [
3737
# Check for JV totals using back calculation logic
38-
[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
39-
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
40-
[si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
38+
[jv.name, "TCS", 0.075, -10000.0, -10000.0, -7.5, -10000.0],
39+
[pe.name, "TCS", 0.075, 706.67, 2550.0, 0.53, 2550.53],
40+
[si.name, "TCS", 0.075, 693.33, 1000.0, 0.52, 1000.52],
4141
]
4242
self.check_expected_values(result, expected_values)
4343

@@ -55,8 +55,8 @@ def test_single_account_for_multiple_categories(self):
5555
frappe._dict(company="_Test Company", party_type="Supplier", from_date=today(), to_date=today())
5656
)[1]
5757
expected_values = [
58-
[inv_1.name, "TDS - 1", 10, 5000, 500, 5500],
59-
[inv_2.name, "TDS - 2", 20, 5000, 1000, 6000],
58+
[inv_1.name, "TDS - 1", 10, 5000, 5000, 500, 5500],
59+
[inv_2.name, "TDS - 2", 20, 5000, 5000, 1000, 6000],
6060
]
6161
self.check_expected_values(result, expected_values)
6262

@@ -107,8 +107,8 @@ def test_date_filters_in_multiple_tax_withholding_rules(self):
107107
)[1]
108108

109109
expected_values = [
110-
[inv_1.name, "TDS - 3", 10.0, 5000, 500, 4500],
111-
[inv_2.name, "TDS - 3", 20.0, 5000, 1000, 4000],
110+
[inv_1.name, "TDS - 3", 10.0, 5000, 5000, 500, 4500],
111+
[inv_2.name, "TDS - 3", 20.0, 5000, 5000, 1000, 4000],
112112
]
113113
self.check_expected_values(result, expected_values)
114114

@@ -120,6 +120,7 @@ def check_expected_values(self, result, expected_values):
120120
voucher.ref_no,
121121
voucher.section_code,
122122
voucher.rate,
123+
voucher.base_tax_withholding_net_total,
123124
voucher.base_total,
124125
voucher.tax_amount,
125126
voucher.grand_total,

erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def get_columns(filters):
128128
"width": 120,
129129
},
130130
{
131-
"label": _("Total Amount"),
131+
"label": _("Total Taxable Amount"),
132132
"fieldname": "total_amount",
133133
"fieldtype": "Float",
134134
"width": 120,

erpnext/controllers/accounts_controller.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,12 +2580,12 @@ def set_payment_schedule(self):
25802580

25812581
def get_order_details(self):
25822582
if self.doctype == "Sales Invoice":
2583-
po_or_so = self.get("items")[0].get("sales_order")
2583+
po_or_so = self.get("items") and self.get("items")[0].get("sales_order")
25842584
po_or_so_doctype = "Sales Order"
25852585
po_or_so_doctype_name = "sales_order"
25862586

25872587
else:
2588-
po_or_so = self.get("items")[0].get("purchase_order")
2588+
po_or_so = self.get("items") and self.get("items")[0].get("purchase_order")
25892589
po_or_so_doctype = "Purchase Order"
25902590
po_or_so_doctype_name = "purchase_order"
25912591

@@ -4002,6 +4002,12 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40024002
flt(d.get("conversion_factor"), conv_fac_precision) or conversion_factor
40034003
)
40044004

4005+
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
4006+
child_item.total_weight = flt(
4007+
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
4008+
child_item.precision("total_weight"),
4009+
)
4010+
40054011
if d.get("delivery_date") and parent_doctype == "Sales Order":
40064012
child_item.delivery_date = d.get("delivery_date")
40074013

@@ -4054,6 +4060,7 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40544060

40554061
parent.set_payment_schedule()
40564062
if parent_doctype == "Purchase Order":
4063+
parent.set_tax_withholding()
40574064
parent.validate_minimum_order_qty()
40584065
parent.validate_budget()
40594066
if parent.is_against_so():

erpnext/controllers/buying_controller.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,9 @@ def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_vouche
626626
or self.is_return
627627
or (self.is_internal_transfer() and self.docstatus == 2)
628628
else self.get_package_for_target_warehouse(
629-
d, type_of_transaction=type_of_transaction
629+
d,
630+
type_of_transaction=type_of_transaction,
631+
via_landed_cost_voucher=via_landed_cost_voucher,
630632
)
631633
),
632634
},
@@ -714,7 +716,22 @@ def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_vouche
714716
via_landed_cost_voucher=via_landed_cost_voucher,
715717
)
716718

717-
def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str:
719+
def get_package_for_target_warehouse(
720+
self, item, warehouse=None, type_of_transaction=None, via_landed_cost_voucher=None
721+
) -> str:
722+
if via_landed_cost_voucher and item.get("warehouse"):
723+
if sabb := frappe.db.get_value(
724+
"Serial and Batch Bundle",
725+
{
726+
"voucher_detail_no": item.name,
727+
"warehouse": item.get("warehouse"),
728+
"docstatus": 1,
729+
"is_cancelled": 0,
730+
},
731+
"name",
732+
):
733+
return sabb
734+
718735
if not item.serial_and_batch_bundle:
719736
return ""
720737

erpnext/controllers/stock_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,7 @@ def get_gl_entries_for_preview(doctype, docname, fields):
15911591

15921592
def get_columns(raw_columns, fields):
15931593
return [
1594-
{"name": d.get("label"), "editable": False, "width": 110}
1594+
{"name": d.get("label"), "editable": False, "width": 110, "fieldtype": d.get("fieldtype")}
15951595
for d in raw_columns
15961596
if not d.get("hidden") and d.get("fieldname") in fields
15971597
]

0 commit comments

Comments
 (0)