Skip to content

Commit 6e1a808

Browse files
mihir-kandoimergify[bot]
authored andcommitted
fix: standalone sales invoice return should not fallback to item master for valuation rate
(cherry picked from commit a85a0ae)
1 parent 407bf7e commit 6e1a808

5 files changed

Lines changed: 46 additions & 40 deletions

File tree

erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@
843843
"fieldtype": "Currency",
844844
"label": "Incoming Rate (Costing)",
845845
"no_copy": 1,
846+
"non_negative": 1,
846847
"options": "Company:company:default_currency",
847848
"print_hide": 1
848849
},
@@ -1009,7 +1010,7 @@
10091010
"idx": 1,
10101011
"istable": 1,
10111012
"links": [],
1012-
"modified": "2026-02-15 21:08:57.341638",
1013+
"modified": "2026-02-23 14:37:14.853941",
10131014
"modified_by": "Administrator",
10141015
"module": "Accounts",
10151016
"name": "Sales Invoice Item",

erpnext/accounts/report/gross_profit/test_gross_profit.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ def test_standalone_cr_notes(self):
444444
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
445445
)
446446
sinv.is_return = 1
447+
sinv.items[0].allow_zero_valuation_rate = 1
447448
sinv = sinv.save().submit()
448449

449450
filters = frappe._dict(

erpnext/controllers/selling_controller.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,34 @@ def update_reserved_qty(self):
498498
sales_order.update_reserved_qty(so_item_rows)
499499

500500
def set_incoming_rate(self):
501+
def reset_incoming_rate():
502+
old_item = next(
503+
(
504+
item
505+
for item in (old_doc.get("items") + (old_doc.get("packed_items") or []))
506+
if item.name == d.name
507+
),
508+
None,
509+
)
510+
if old_item:
511+
old_qty = flt(old_item.get("stock_qty") or old_item.get("actual_qty") or old_item.get("qty"))
512+
if (
513+
old_item.item_code != d.item_code
514+
or old_item.warehouse != d.warehouse
515+
or old_qty != qty
516+
or old_item.serial_no != d.serial_no
517+
or get_serial_nos(old_item.serial_and_batch_bundle)
518+
!= get_serial_nos(d.serial_and_batch_bundle)
519+
or old_item.batch_no != d.batch_no
520+
or get_batch_nos(old_item.serial_and_batch_bundle)
521+
!= get_batch_nos(d.serial_and_batch_bundle)
522+
):
523+
d.incoming_rate = 0
524+
501525
if self.doctype not in ("Delivery Note", "Sales Invoice"):
502526
return
503527

504-
from erpnext.stock.serial_batch_bundle import get_batch_nos
528+
from erpnext.stock.serial_batch_bundle import get_batch_nos, get_serial_nos
505529

506530
allow_at_arms_length_price = frappe.get_cached_value(
507531
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
@@ -510,6 +534,8 @@ def set_incoming_rate(self):
510534
"Selling Settings", "set_zero_rate_for_expired_batch"
511535
)
512536

537+
is_standalone = self.is_return and not self.return_against
538+
513539
old_doc = self.get_doc_before_save()
514540
items = self.get("items") + (self.get("packed_items") or [])
515541
for d in items:
@@ -541,27 +567,7 @@ def set_incoming_rate(self):
541567
qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty"))
542568

543569
if old_doc:
544-
old_item = next(
545-
(
546-
item
547-
for item in (old_doc.get("items") + (old_doc.get("packed_items") or []))
548-
if item.name == d.name
549-
),
550-
None,
551-
)
552-
if old_item:
553-
old_qty = flt(
554-
old_item.get("stock_qty") or old_item.get("actual_qty") or old_item.get("qty")
555-
)
556-
if (
557-
old_item.item_code != d.item_code
558-
or old_item.warehouse != d.warehouse
559-
or old_qty != qty
560-
or old_item.batch_no != d.batch_no
561-
or get_batch_nos(old_item.serial_and_batch_bundle)
562-
!= get_batch_nos(d.serial_and_batch_bundle)
563-
):
564-
d.incoming_rate = 0
570+
reset_incoming_rate()
565571

566572
if (
567573
not d.incoming_rate
@@ -583,11 +589,12 @@ def set_incoming_rate(self):
583589
"voucher_type": self.doctype,
584590
"voucher_no": self.name,
585591
"voucher_detail_no": d.name,
586-
"allow_zero_valuation": d.get("allow_zero_valuation"),
592+
"allow_zero_valuation": d.get("allow_zero_valuation_rate"),
587593
"batch_no": d.batch_no,
588594
"serial_no": d.serial_no,
589595
},
590-
raise_error_if_no_rate=False,
596+
raise_error_if_no_rate=is_standalone,
597+
fallbacks=not is_standalone,
591598
)
592599

593600
if (

erpnext/stock/stock_ledger.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,7 @@ def get_valuation_rate(
19101910
allow_zero_rate=False,
19111911
currency=None,
19121912
company=None,
1913+
fallbacks=True,
19131914
raise_error_if_no_rate=True,
19141915
batch_no=None,
19151916
serial_and_batch_bundle=None,
@@ -1970,23 +1971,20 @@ def get_valuation_rate(
19701971
):
19711972
return flt(last_valuation_rate[0][0])
19721973

1973-
# If negative stock allowed, and item delivered without any incoming entry,
1974-
# system does not found any SLE, then take valuation rate from Item
1975-
valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate")
1976-
1977-
if not valuation_rate:
1978-
# try Item Standard rate
1979-
valuation_rate = frappe.db.get_value("Item", item_code, "standard_rate")
1980-
1981-
if not valuation_rate:
1982-
# try in price list
1983-
valuation_rate = frappe.db.get_value(
1974+
if fallbacks:
1975+
# If negative stock allowed, and item delivered without any incoming entry,
1976+
# system does not found any SLE, then take valuation rate from Item
1977+
if rate := (
1978+
frappe.db.get_value("Item", item_code, "valuation_rate")
1979+
or frappe.db.get_value("Item", item_code, "standard_rate")
1980+
or frappe.db.get_value(
19841981
"Item Price", dict(item_code=item_code, buying=1, currency=currency), "price_list_rate"
19851982
)
1983+
):
1984+
return flt(rate)
19861985

19871986
if (
19881987
not allow_zero_rate
1989-
and not valuation_rate
19901988
and raise_error_if_no_rate
19911989
and cint(erpnext.is_perpetual_inventory_enabled(company))
19921990
):
@@ -2016,8 +2014,6 @@ def get_valuation_rate(
20162014

20172015
frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
20182016

2019-
return valuation_rate
2020-
20212017

20222018
def update_qty_in_future_sle(args, allow_negative_stock=False):
20232019
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""

erpnext/stock/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def _create_bin(item_code, warehouse):
237237

238238

239239
@frappe.whitelist()
240-
def get_incoming_rate(args, raise_error_if_no_rate=True):
240+
def get_incoming_rate(args, raise_error_if_no_rate=True, fallbacks: bool = True):
241241
"""Get Incoming Rate based on valuation method"""
242242
from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate
243243

@@ -325,6 +325,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
325325
args.get("allow_zero_valuation"),
326326
currency=erpnext.get_company_currency(args.get("company")),
327327
company=args.get("company"),
328+
fallbacks=fallbacks,
328329
raise_error_if_no_rate=raise_error_if_no_rate,
329330
)
330331

0 commit comments

Comments
 (0)