mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-19 06:45:11 +00:00
fix: standalone sales invoice return should not fallback to item master for valuation rate
(cherry picked from commit a85a0aef52)
This commit is contained in:
@@ -843,6 +843,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Incoming Rate (Costing)",
|
"label": "Incoming Rate (Costing)",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"non_negative": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
@@ -1009,7 +1010,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-02-15 21:08:57.341638",
|
"modified": "2026-02-23 14:37:14.853941",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@@ -444,6 +444,7 @@ class TestGrossProfit(IntegrationTestCase):
|
|||||||
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||||
)
|
)
|
||||||
sinv.is_return = 1
|
sinv.is_return = 1
|
||||||
|
sinv.items[0].allow_zero_valuation_rate = 1
|
||||||
sinv = sinv.save().submit()
|
sinv = sinv.save().submit()
|
||||||
|
|
||||||
filters = frappe._dict(
|
filters = frappe._dict(
|
||||||
|
|||||||
@@ -498,10 +498,34 @@ class SellingController(StockController):
|
|||||||
sales_order.update_reserved_qty(so_item_rows)
|
sales_order.update_reserved_qty(so_item_rows)
|
||||||
|
|
||||||
def set_incoming_rate(self):
|
def set_incoming_rate(self):
|
||||||
|
def reset_incoming_rate():
|
||||||
|
old_item = next(
|
||||||
|
(
|
||||||
|
item
|
||||||
|
for item in (old_doc.get("items") + (old_doc.get("packed_items") or []))
|
||||||
|
if item.name == d.name
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if old_item:
|
||||||
|
old_qty = flt(old_item.get("stock_qty") or old_item.get("actual_qty") or old_item.get("qty"))
|
||||||
|
if (
|
||||||
|
old_item.item_code != d.item_code
|
||||||
|
or old_item.warehouse != d.warehouse
|
||||||
|
or old_qty != qty
|
||||||
|
or old_item.serial_no != d.serial_no
|
||||||
|
or get_serial_nos(old_item.serial_and_batch_bundle)
|
||||||
|
!= get_serial_nos(d.serial_and_batch_bundle)
|
||||||
|
or old_item.batch_no != d.batch_no
|
||||||
|
or get_batch_nos(old_item.serial_and_batch_bundle)
|
||||||
|
!= get_batch_nos(d.serial_and_batch_bundle)
|
||||||
|
):
|
||||||
|
d.incoming_rate = 0
|
||||||
|
|
||||||
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
||||||
return
|
return
|
||||||
|
|
||||||
from erpnext.stock.serial_batch_bundle import get_batch_nos
|
from erpnext.stock.serial_batch_bundle import get_batch_nos, get_serial_nos
|
||||||
|
|
||||||
allow_at_arms_length_price = frappe.get_cached_value(
|
allow_at_arms_length_price = frappe.get_cached_value(
|
||||||
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
|
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
|
||||||
@@ -510,6 +534,8 @@ class SellingController(StockController):
|
|||||||
"Selling Settings", "set_zero_rate_for_expired_batch"
|
"Selling Settings", "set_zero_rate_for_expired_batch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_standalone = self.is_return and not self.return_against
|
||||||
|
|
||||||
old_doc = self.get_doc_before_save()
|
old_doc = self.get_doc_before_save()
|
||||||
items = self.get("items") + (self.get("packed_items") or [])
|
items = self.get("items") + (self.get("packed_items") or [])
|
||||||
for d in items:
|
for d in items:
|
||||||
@@ -541,27 +567,7 @@ class SellingController(StockController):
|
|||||||
qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty"))
|
qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty"))
|
||||||
|
|
||||||
if old_doc:
|
if old_doc:
|
||||||
old_item = next(
|
reset_incoming_rate()
|
||||||
(
|
|
||||||
item
|
|
||||||
for item in (old_doc.get("items") + (old_doc.get("packed_items") or []))
|
|
||||||
if item.name == d.name
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if old_item:
|
|
||||||
old_qty = flt(
|
|
||||||
old_item.get("stock_qty") or old_item.get("actual_qty") or old_item.get("qty")
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
old_item.item_code != d.item_code
|
|
||||||
or old_item.warehouse != d.warehouse
|
|
||||||
or old_qty != qty
|
|
||||||
or old_item.batch_no != d.batch_no
|
|
||||||
or get_batch_nos(old_item.serial_and_batch_bundle)
|
|
||||||
!= get_batch_nos(d.serial_and_batch_bundle)
|
|
||||||
):
|
|
||||||
d.incoming_rate = 0
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not d.incoming_rate
|
not d.incoming_rate
|
||||||
@@ -583,11 +589,12 @@ class SellingController(StockController):
|
|||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": d.name,
|
"voucher_detail_no": d.name,
|
||||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
"allow_zero_valuation": d.get("allow_zero_valuation_rate"),
|
||||||
"batch_no": d.batch_no,
|
"batch_no": d.batch_no,
|
||||||
"serial_no": d.serial_no,
|
"serial_no": d.serial_no,
|
||||||
},
|
},
|
||||||
raise_error_if_no_rate=False,
|
raise_error_if_no_rate=is_standalone,
|
||||||
|
fallbacks=not is_standalone,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1910,6 +1910,7 @@ def get_valuation_rate(
|
|||||||
allow_zero_rate=False,
|
allow_zero_rate=False,
|
||||||
currency=None,
|
currency=None,
|
||||||
company=None,
|
company=None,
|
||||||
|
fallbacks=True,
|
||||||
raise_error_if_no_rate=True,
|
raise_error_if_no_rate=True,
|
||||||
batch_no=None,
|
batch_no=None,
|
||||||
serial_and_batch_bundle=None,
|
serial_and_batch_bundle=None,
|
||||||
@@ -1970,23 +1971,20 @@ def get_valuation_rate(
|
|||||||
):
|
):
|
||||||
return flt(last_valuation_rate[0][0])
|
return flt(last_valuation_rate[0][0])
|
||||||
|
|
||||||
# If negative stock allowed, and item delivered without any incoming entry,
|
if fallbacks:
|
||||||
# system does not found any SLE, then take valuation rate from Item
|
# If negative stock allowed, and item delivered without any incoming entry,
|
||||||
valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate")
|
# system does not found any SLE, then take valuation rate from Item
|
||||||
|
if rate := (
|
||||||
if not valuation_rate:
|
frappe.db.get_value("Item", item_code, "valuation_rate")
|
||||||
# try Item Standard rate
|
or frappe.db.get_value("Item", item_code, "standard_rate")
|
||||||
valuation_rate = frappe.db.get_value("Item", item_code, "standard_rate")
|
or frappe.db.get_value(
|
||||||
|
|
||||||
if not valuation_rate:
|
|
||||||
# try in price list
|
|
||||||
valuation_rate = frappe.db.get_value(
|
|
||||||
"Item Price", dict(item_code=item_code, buying=1, currency=currency), "price_list_rate"
|
"Item Price", dict(item_code=item_code, buying=1, currency=currency), "price_list_rate"
|
||||||
)
|
)
|
||||||
|
):
|
||||||
|
return flt(rate)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not allow_zero_rate
|
not allow_zero_rate
|
||||||
and not valuation_rate
|
|
||||||
and raise_error_if_no_rate
|
and raise_error_if_no_rate
|
||||||
and cint(erpnext.is_perpetual_inventory_enabled(company))
|
and cint(erpnext.is_perpetual_inventory_enabled(company))
|
||||||
):
|
):
|
||||||
@@ -2016,8 +2014,6 @@ def get_valuation_rate(
|
|||||||
|
|
||||||
frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
|
frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
|
||||||
|
|
||||||
return valuation_rate
|
|
||||||
|
|
||||||
|
|
||||||
def update_qty_in_future_sle(args, allow_negative_stock=False):
|
def update_qty_in_future_sle(args, allow_negative_stock=False):
|
||||||
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
|
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ def _create_bin(item_code, warehouse):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_incoming_rate(args, raise_error_if_no_rate=True):
|
def get_incoming_rate(args, raise_error_if_no_rate=True, fallbacks: bool = True):
|
||||||
"""Get Incoming Rate based on valuation method"""
|
"""Get Incoming Rate based on valuation method"""
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate
|
from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate
|
||||||
|
|
||||||
@@ -325,6 +325,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
|||||||
args.get("allow_zero_valuation"),
|
args.get("allow_zero_valuation"),
|
||||||
currency=erpnext.get_company_currency(args.get("company")),
|
currency=erpnext.get_company_currency(args.get("company")),
|
||||||
company=args.get("company"),
|
company=args.get("company"),
|
||||||
|
fallbacks=fallbacks,
|
||||||
raise_error_if_no_rate=raise_error_if_no_rate,
|
raise_error_if_no_rate=raise_error_if_no_rate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user