Skip to content

Commit c74a44e

Browse files
authored
Merge pull request #54282 from frappe/version-15-hotfix
2 parents 8aede87 + 8b3d65a commit c74a44e

29 files changed

Lines changed: 352 additions & 220 deletions

File tree

erpnext/accounts/doctype/payment_entry/payment_entry.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ frappe.ui.form.on("Payment Entry", {
839839
paid_amount: function (frm) {
840840
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
841841
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
842-
if (frm.doc.paid_amount) {
842+
if (!frm.doc.received_amount) {
843843
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
844844
frm.set_value("received_amount", frm.doc.paid_amount);
845845
} else if (company_currency == frm.doc.paid_to_account_currency) {
@@ -860,7 +860,7 @@ frappe.ui.form.on("Payment Entry", {
860860
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
861861
);
862862

863-
if (frm.doc.received_amount) {
863+
if (!frm.doc.paid_amount) {
864864
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
865865
frm.set_value("paid_amount", frm.doc.received_amount);
866866
if (frm.doc.target_exchange_rate) {

erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,6 @@
731731
"label": "Valuation Rate",
732732
"no_copy": 1,
733733
"options": "Company:company:default_currency",
734-
"precision": "6",
735734
"print_hide": 1,
736735
"read_only": 1
737736
},
@@ -984,7 +983,7 @@
984983
"idx": 1,
985984
"istable": 1,
986985
"links": [],
987-
"modified": "2025-10-14 13:01:54.441511",
986+
"modified": "2026-04-07 15:41:45.687554",
988987
"modified_by": "Administrator",
989988
"module": "Accounts",
990989
"name": "Purchase Invoice Item",

erpnext/accounts/doctype/sales_invoice/sales_invoice.js

Lines changed: 103 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
166166
);
167167
}
168168
}
169-
170-
// Show buttons only when pos view is active
171-
if (cint(doc.docstatus == 0) && cur_frm.page.current_view_name !== "pos" && !doc.is_return) {
172-
this.frm.cscript.sales_order_btn();
173-
this.frm.cscript.delivery_note_btn();
174-
this.frm.cscript.quotation_btn();
175-
}
169+
this.toggle_get_items();
176170

177171
this.set_default_print_format();
178172
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
@@ -258,6 +252,93 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
258252
}
259253
}
260254

255+
toggle_get_items() {
256+
const buttons = ["Sales Order", "Quotation", "Timesheet", "Delivery Note"];
257+
258+
buttons.forEach((label) => {
259+
this.frm.remove_custom_button(label, "Get Items From");
260+
});
261+
262+
if (cint(this.frm.doc.docstatus) !== 0 || this.frm.page.current_view_name === "pos") {
263+
return;
264+
}
265+
266+
if (!this.frm.doc.is_return) {
267+
this.frm.cscript.sales_order_btn();
268+
this.frm.cscript.quotation_btn();
269+
this.frm.cscript.timesheet_btn();
270+
}
271+
272+
this.frm.cscript.delivery_note_btn();
273+
}
274+
275+
timesheet_btn() {
276+
var me = this;
277+
278+
me.frm.add_custom_button(
279+
__("Timesheet"),
280+
function () {
281+
let d = new frappe.ui.Dialog({
282+
title: __("Fetch Timesheet"),
283+
fields: [
284+
{
285+
label: __("From"),
286+
fieldname: "from_time",
287+
fieldtype: "Date",
288+
reqd: 1,
289+
},
290+
{
291+
label: __("Item Code"),
292+
fieldname: "item_code",
293+
fieldtype: "Link",
294+
options: "Item",
295+
get_query: () => {
296+
return {
297+
query: "erpnext.controllers.queries.item_query",
298+
filters: {
299+
is_sales_item: 1,
300+
customer: me.frm.doc.customer,
301+
has_variants: 0,
302+
},
303+
};
304+
},
305+
},
306+
{
307+
fieldtype: "Column Break",
308+
fieldname: "col_break_1",
309+
},
310+
{
311+
label: __("To"),
312+
fieldname: "to_time",
313+
fieldtype: "Date",
314+
reqd: 1,
315+
},
316+
{
317+
label: __("Project"),
318+
fieldname: "project",
319+
fieldtype: "Link",
320+
options: "Project",
321+
default: me.frm.doc.project,
322+
},
323+
],
324+
primary_action: function () {
325+
const data = d.get_values();
326+
me.frm.events.add_timesheet_data(me.frm, {
327+
from_time: data.from_time,
328+
to_time: data.to_time,
329+
project: data.project,
330+
item_code: data.item_code,
331+
});
332+
d.hide();
333+
},
334+
primary_action_label: __("Get Timesheets"),
335+
});
336+
d.show();
337+
},
338+
__("Get Items From")
339+
);
340+
}
341+
261342
sales_order_btn() {
262343
var me = this;
263344
this.$sales_order_btn = this.frm.add_custom_button(
@@ -322,6 +403,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
322403
this.$delivery_note_btn = this.frm.add_custom_button(
323404
__("Delivery Note"),
324405
function () {
406+
if (!me.frm.doc.customer) {
407+
frappe.throw({
408+
title: __("Mandatory"),
409+
message: __("Please Select a Customer"),
410+
});
411+
}
325412
erpnext.utils.map_current_doc({
326413
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
327414
source_doctype: "Delivery Note",
@@ -334,7 +421,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
334421
var filters = {
335422
docstatus: 1,
336423
company: me.frm.doc.company,
337-
is_return: 0,
424+
is_return: me.frm.doc.is_return,
338425
};
339426
if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
340427
return {
@@ -594,6 +681,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
594681

595682
this.calculate_taxes_and_totals();
596683
}
684+
685+
apply_tds(frm) {
686+
this.frm.clear_table("tax_withholding_entries");
687+
}
688+
689+
is_return() {
690+
this.toggle_get_items();
691+
}
597692
};
598693

599694
// for backward compatibility: combine new and previous states
@@ -1039,71 +1134,6 @@ frappe.ui.form.on("Sales Invoice", {
10391134
},
10401135

10411136
refresh: function (frm) {
1042-
if (frm.doc.docstatus === 0 && !frm.doc.is_return) {
1043-
frm.add_custom_button(
1044-
__("Timesheet"),
1045-
function () {
1046-
let d = new frappe.ui.Dialog({
1047-
title: __("Fetch Timesheet"),
1048-
fields: [
1049-
{
1050-
label: __("From"),
1051-
fieldname: "from_time",
1052-
fieldtype: "Date",
1053-
reqd: 1,
1054-
},
1055-
{
1056-
label: __("Item Code"),
1057-
fieldname: "item_code",
1058-
fieldtype: "Link",
1059-
options: "Item",
1060-
get_query: () => {
1061-
return {
1062-
query: "erpnext.controllers.queries.item_query",
1063-
filters: {
1064-
is_sales_item: 1,
1065-
customer: frm.doc.customer,
1066-
has_variants: 0,
1067-
},
1068-
};
1069-
},
1070-
},
1071-
{
1072-
fieldtype: "Column Break",
1073-
fieldname: "col_break_1",
1074-
},
1075-
{
1076-
label: __("To"),
1077-
fieldname: "to_time",
1078-
fieldtype: "Date",
1079-
reqd: 1,
1080-
},
1081-
{
1082-
label: __("Project"),
1083-
fieldname: "project",
1084-
fieldtype: "Link",
1085-
options: "Project",
1086-
default: frm.doc.project,
1087-
},
1088-
],
1089-
primary_action: function () {
1090-
const data = d.get_values();
1091-
frm.events.add_timesheet_data(frm, {
1092-
from_time: data.from_time,
1093-
to_time: data.to_time,
1094-
project: data.project,
1095-
item_code: data.item_code,
1096-
});
1097-
d.hide();
1098-
},
1099-
primary_action_label: __("Get Timesheets"),
1100-
});
1101-
d.show();
1102-
},
1103-
__("Get Items From")
1104-
);
1105-
}
1106-
11071137
if (frm.doc.is_debit_note) {
11081138
frm.set_df_property("return_against", "label", __("Adjustment Against"));
11091139
}

erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2917,7 +2917,7 @@ def test_internal_transfer_gl_precision_issues(self):
29172917
si.submit()
29182918

29192919
# Check if adjustment entry is created
2920-
self.assertTrue(
2920+
self.assertFalse(
29212921
frappe.db.exists(
29222922
"GL Entry",
29232923
{

erpnext/assets/doctype/asset_movement/asset_movement.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ frappe.ui.form.on("Asset Movement", {
4141
});
4242
},
4343

44-
onload: (frm) => {
44+
refresh: (frm) => {
4545
frm.trigger("set_required_fields");
4646
},
4747

erpnext/buying/doctype/request_for_quotation/request_for_quotation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def create_user(self, rfq_supplier, link):
283283
}
284284
)
285285
user.save(ignore_permissions=True)
286-
update_password_link = user.reset_password()
286+
update_password_link = user._reset_password()
287287

288288
return user, update_password_link
289289

erpnext/controllers/queries.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -356,38 +356,43 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
356356

357357
@frappe.whitelist()
358358
@frappe.validate_and_sanitize_search_inputs
359-
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
360-
doctype = "Delivery Note"
359+
def get_delivery_notes_to_be_billed(
360+
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict, as_dict: bool = False
361+
):
362+
DeliveryNote = frappe.qb.DocType("Delivery Note")
363+
361364
fields = get_fields(doctype, ["name", "customer", "posting_date"])
362365

363-
return frappe.db.sql(
364-
"""
365-
select {fields}
366-
from `tabDelivery Note`
367-
where `tabDelivery Note`.`{key}` like {txt} and
368-
`tabDelivery Note`.docstatus = 1
369-
and status not in ('Stopped', 'Closed') {fcond}
370-
and (
371-
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
372-
or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
373-
or (
374-
`tabDelivery Note`.is_return = 1
375-
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
366+
original_dn = (
367+
frappe.qb.from_(DeliveryNote)
368+
.select(DeliveryNote.name)
369+
.where((DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0) & (DeliveryNote.per_billed > 0))
370+
)
371+
372+
query = (
373+
frappe.qb.from_(DeliveryNote)
374+
.select(*[DeliveryNote[f] for f in fields])
375+
.where(
376+
(DeliveryNote.docstatus == 1)
377+
& (DeliveryNote.status.notin(["Stopped", "Closed"]))
378+
& (DeliveryNote[searchfield].like(f"%{txt}%"))
379+
& (
380+
((DeliveryNote.is_return == 0) & (DeliveryNote.per_billed < 100))
381+
| ((DeliveryNote.grand_total == 0) & (DeliveryNote.per_billed < 100))
382+
| (
383+
(DeliveryNote.is_return == 1)
384+
& (DeliveryNote.per_billed < 100)
385+
& (DeliveryNote.return_against.isin(original_dn))
376386
)
377387
)
378-
{mcond} order by `tabDelivery Note`.`{key}` asc limit {page_len} offset {start}
379-
""".format(
380-
fields=", ".join([f"`tabDelivery Note`.{f}" for f in fields]),
381-
key=searchfield,
382-
fcond=get_filters_cond(doctype, filters, []),
383-
mcond=get_match_cond(doctype),
384-
start=start,
385-
page_len=page_len,
386-
txt="%(txt)s",
387-
),
388-
{"txt": ("%%%s%%" % txt)},
389-
as_dict=as_dict,
388+
)
390389
)
390+
if filters and isinstance(filters, dict):
391+
for key, value in filters.items():
392+
query = query.where(DeliveryNote[key] == value)
393+
394+
query = query.orderby(DeliveryNote[searchfield], order=Order.asc).limit(page_len).offset(start)
395+
return query.run(as_dict=as_dict)
391396

392397

393398
@frappe.whitelist()

erpnext/controllers/selling_controller.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,11 @@ def reset_incoming_rate():
616616
if allow_at_arms_length_price:
617617
continue
618618

619-
rate = flt(
620-
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
621-
d.precision("rate"),
622-
)
623-
if d.rate != rate:
619+
rate = flt(flt(d.incoming_rate) * flt(d.conversion_factor or 1.0))
620+
621+
if flt(d.rate, d.precision("incoming_rate")) != flt(
622+
rate, d.precision("incoming_rate")
623+
):
624624
d.rate = rate
625625
frappe.msgprint(
626626
_(

erpnext/controllers/taxes_and_totals.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,11 @@ def calculate_item_values(self):
186186
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
187187
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
188188
)
189+
190+
do_not_round_fields = ["valuation_rate", "incoming_rate"]
191+
189192
for item in self.doc.items:
190-
self.doc.round_floats_in(item)
193+
self.doc.round_floats_in(item, do_not_round_fields=do_not_round_fields)
191194

192195
if item.discount_percentage == 100:
193196
item.rate = 0.0

erpnext/crm/report/lost_opportunity/lost_opportunity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def get_join(filters):
117117
join = """JOIN `tabOpportunity Lost Reason Detail`
118118
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
119119
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
120-
`tabOpportunity Lost Reason Detail`.lost_reason = '{}'
121-
""".format(filters.get("lost_reason"))
120+
`tabOpportunity Lost Reason Detail`.lost_reason=%(lost_reason)s
121+
"""
122122

123123
return join

0 commit comments

Comments
 (0)