Compare commits

..

4 Commits

Author SHA1 Message Date
MochaMind
8fd0175b06 chore: update POT file (#59201) 2026-09-20 21:44:23 +02:00
Afsal Syed
db6e089109 fix(stock): validate warehouse account belongs to selected company (#59191) 2026-09-19 13:06:06 +05:30
Khushi Rawat
a2481e9390 Merge pull request #59181 from aerele/fix/accounts-reports-revaluation-filter-labels
fix(accounts): clarify revaluation journal filters
2026-09-18 17:53:02 +05:30
pandiyan
dc4b390a9b fix(accounts): clarify revaluation journal filters 2026-09-18 17:26:10 +05:30
18 changed files with 1913 additions and 2067 deletions

View File

@@ -156,7 +156,7 @@ frappe.query_reports["Accounts Payable"] = {
},
{
fieldname: "for_revaluation_journals",
label: __("Revaluation Journals"),
label: __("Include Revaluation Journals"),
fieldtype: "Check",
},
{

View File

@@ -113,7 +113,7 @@ frappe.query_reports["Accounts Payable Summary"] = {
},
{
fieldname: "for_revaluation_journals",
label: __("Revaluation Journals"),
label: __("Include Revaluation Journals"),
fieldtype: "Check",
},
{

View File

@@ -183,7 +183,7 @@ frappe.query_reports["Accounts Receivable"] = {
},
{
fieldname: "for_revaluation_journals",
label: __("Revaluation Journals"),
label: __("Include Revaluation Journals"),
fieldtype: "Check",
},
{

View File

@@ -141,7 +141,7 @@ frappe.query_reports["Accounts Receivable Summary"] = {
},
{
fieldname: "for_revaluation_journals",
label: __("Revaluation Journals"),
label: __("Include Revaluation Journals"),
fieldtype: "Check",
},
],

View File

@@ -23,11 +23,7 @@ from erpnext.stock.get_item_details import (
get_conversion_factor,
get_item_defaults,
)
<<<<<<< HEAD
from erpnext.stock.utils import _get_incoming_rate
=======
from erpnext.stock.utils import get_incoming_rate, is_serial_no_wise_valuation_disabled
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
class QtyMismatchError(ValidationError):
@@ -821,11 +817,9 @@ class BuyingController(SubcontractingController):
)
if self.is_return:
outgoing_rate = 0.0
if not is_serial_no_wise_valuation_disabled(d.item_code):
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
sle.update(
{

File diff suppressed because it is too large Load Diff

View File

@@ -523,4 +523,3 @@ erpnext.patches.v16_0.set_supplier_quotation_order_status
erpnext.patches.v16_0.recalculate_holiday_list_totals
erpnext.patches.v16_0.recalculate_returned_delivery_note_billing_status
erpnext.patches.v16_0.rename_component_cost_valuation_type
erpnext.patches.v16_0.enable_serial_no_wise_valuation

View File

@@ -1,11 +0,0 @@
import frappe
def execute():
item = frappe.qb.DocType("Item")
(
frappe.qb.update(item)
.set(item.use_serial_no_wise_valuation, 1)
.where((item.has_serial_no == 1) & (item.use_serial_no_wise_valuation == 0))
).run()

View File

@@ -3,15 +3,10 @@ import json
from collections import defaultdict
import frappe
<<<<<<< HEAD
from frappe.query_builder.functions import Sum
from frappe.utils import flt
=======
from frappe.query_builder.functions import Locate, Sum
from frappe.utils import flt, nowtime
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
from pypika import Order
from pypika.functions import Coalesce, Concat
from pypika.functions import Coalesce
from erpnext.deprecation_dumpster import deprecated
@@ -69,39 +64,36 @@ class DeprecatedSerialNoValuation:
incoming_values += self.serial_no_incoming_rate[serial_no]
continue
for sle in self.get_last_inward_sle_for_serial_no(serial_no, posting_datetime):
table = frappe.qb.DocType("Stock Ledger Entry")
stock_ledgers = (
frappe.qb.from_(table)
.select(table.incoming_rate, table.actual_qty, table.stock_value_difference)
.where(
(
(table.serial_no == serial_no)
| (table.serial_no.like(serial_no + "\n%"))
| (table.serial_no.like("%\n" + serial_no))
| (table.serial_no.like("%\n" + serial_no + "\n%"))
)
& (table.item_code == self.sle.item_code)
& (table.company == self.sle.company)
& (table.warehouse == self.sle.warehouse)
& (table.serial_and_batch_bundle.isnull())
& (table.actual_qty > 0)
& (table.is_cancelled == 0)
& table.posting_datetime
<= posting_datetime
)
.orderby(table.posting_datetime, order=Order.desc)
.limit(1)
).run(as_dict=1)
for sle in stock_ledgers:
self.serial_no_incoming_rate[serial_no] += flt(sle.incoming_rate)
incoming_values += self.serial_no_incoming_rate[serial_no]
return incoming_values
def get_last_inward_sle_for_serial_no(self, serial_no, posting_datetime):
table = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(table)
.select(table.incoming_rate, table.actual_qty, table.stock_value_difference)
.where(
(table.item_code == self.sle.item_code)
& (table.company == self.sle.company)
& (table.warehouse == self.sle.warehouse)
& (table.posting_datetime <= posting_datetime)
& (table.is_cancelled == 0)
& (table.actual_qty > 0)
& (table.serial_and_batch_bundle.isnull())
& (Locate(serial_no, table.serial_no) > 0)
& (Locate(f"\n{serial_no}\n", Concat("\n", table.serial_no, "\n")) > 0)
)
.orderby(table.posting_datetime, order=Order.desc)
.orderby(table.creation, order=Order.desc)
.limit(1)
)
if frappe.db.db_type == "mariadb":
query = query.force_index("item_code_warehouse_posting_datetime_creation_index")
return query.run(as_dict=1)
class DeprecatedBatchNoValuation:
@deprecated(

View File

@@ -98,7 +98,6 @@
"column_break_37",
"has_serial_no",
"serial_no_series",
"use_serial_no_wise_valuation",
"variants_section",
"variant_of",
"variant_based_on",
@@ -387,12 +386,7 @@
"fieldname": "valuation_method",
"fieldtype": "Select",
"label": "Valuation Method",
<<<<<<< HEAD
"options": "\nFIFO\nMoving Average\nLIFO\nStandard Cost"
=======
"options": "\nFIFO\nMoving Average\nLIFO",
"description": "Serialized items are valued at Moving Average once stock transactions exist and Serial No Wise Valuation is disabled."
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
},
{
"depends_on": "is_stock_item",
@@ -534,15 +528,6 @@
"label": "Serial Number Series",
"show_description_on_click": 1
},
{
"default": "1",
"depends_on": "eval:doc.is_stock_item && doc.has_serial_no",
"description": "Value every outward movement at each Serial No's own incoming rate. If unchecked, the item is valued at Moving Average once stock transactions exist. Cannot be enabled once Serial Nos exist for this item.",
"fieldname": "use_serial_no_wise_valuation",
"fieldtype": "Check",
"label": "Use Serial No Wise Valuation",
"show_description_on_click": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "attributes",
@@ -1132,11 +1117,7 @@
"image_field": "image",
"links": [],
"make_attachments_public": 1,
<<<<<<< HEAD
"modified": "2026-09-07 19:53:27.830229",
=======
"modified": "2026-09-16 12:00:00.000000",
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -146,12 +146,7 @@ class Item(Document):
taxes: DF.Table[ItemTax]
total_projected_qty: DF.Float
uoms: DF.Table[UOMConversionDetail]
<<<<<<< HEAD
valuation_method: DF.Literal["", "FIFO", "Moving Average", "LIFO", "Standard Cost"]
=======
use_serial_no_wise_valuation: DF.Check
valuation_method: DF.Literal["", "FIFO", "Moving Average", "LIFO"]
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
valuation_rate: DF.Currency
variant_based_on: DF.Literal["Item Attribute", "Manufacturer"]
variant_of: DF.Link | None
@@ -249,23 +244,15 @@ class Item(Document):
self.validate_auto_reorder_enabled_in_stock_settings()
self.cant_change()
self.validate_serialized_change_with_bundle()
<<<<<<< HEAD
self.validate_standard_cost_change()
=======
self.validate_serial_no_wise_valuation()
self.set_valuation_method_for_serial_no_wise_valuation()
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
self.validate_item_tax_net_rate_range()
if not self.is_new():
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
def on_update(self):
from erpnext.stock.utils import clear_valuation_method_cache
self.update_variants()
self.update_item_price()
clear_valuation_method_cache()
def validate_description(self):
"""Clean HTML description if set"""
@@ -1174,43 +1161,6 @@ class Item(Document):
frappe.throw(msg, title=_("Linked with submitted documents"))
def validate_serial_no_wise_valuation(self):
if self.is_new() or not self._doc_before_save:
return
if not self.use_serial_no_wise_valuation or self._doc_before_save.use_serial_no_wise_valuation:
return
if frappe.db.exists("Serial No", {"item_code": self.name}):
frappe.throw(
_(
"Serial No Wise Valuation cannot be enabled for Item {0} because Serial Nos already exist for it. Valuation for those Serial Nos was not tracked, so enabling it now would value outward entries incorrectly."
).format(frappe.bold(self.name)),
title=_("Serial Nos Exist"),
)
def set_valuation_method_for_serial_no_wise_valuation(self):
if not self.has_serial_no or self.use_serial_no_wise_valuation:
return
if not frappe.db.exists("Stock Ledger Entry", {"item_code": self.name, "is_cancelled": 0}):
return
if (
not self.is_new()
and self._doc_before_save
and self.has_value_changed("valuation_method")
and self.valuation_method in ("FIFO", "LIFO")
):
frappe.throw(
_(
"Valuation Method for Item {0} must be Moving Average because Serial No Wise Valuation is disabled. Enable Serial No Wise Valuation to use FIFO or LIFO."
).format(frappe.bold(self.name)),
title=_("Invalid Valuation Method"),
)
self.valuation_method = "Moving Average"
def validate_serialized_change_with_bundle(self):
"""Block turning a serialized item non-serialized while any Serial and Batch Bundle still exists
for it. Such bundles carry the item's serial numbers; the user must delete or cancel them first."""

View File

@@ -410,9 +410,8 @@ class SerialandBatchBundle(Document):
def set_valuation_rate_for_return_entry(self, return_against, row, save=False, prev_sle=None):
if valuation_details := self.get_valuation_rate_for_return_entry(return_against):
from erpnext.stock.utils import get_valuation_method, is_serial_no_wise_valuation_disabled
from erpnext.stock.utils import get_valuation_method
skip_rate_update = is_serial_no_wise_valuation_disabled(self.item_code)
valuation_method = get_valuation_method(self.item_code, self.company)
# An outward return must go out at the batch's current average rate for a
@@ -441,9 +440,6 @@ class SerialandBatchBundle(Document):
if valuation_details:
self.validate_returned_serial_batch_no(return_against, row, valuation_details)
if skip_rate_update:
continue
if row.serial_no:
valuation_rate = valuation_details["serial_nos"].get(row.serial_no)
else:
@@ -700,10 +696,7 @@ class SerialandBatchBundle(Document):
)
def set_incoming_rate_for_outward_transaction(self, row=None, save=False, allow_negative_stock=False):
from erpnext.stock.utils import get_valuation_method, is_serial_no_wise_valuation_disabled
if is_serial_no_wise_valuation_disabled(self.item_code):
return
from erpnext.stock.utils import get_valuation_method
sle = self.get_sle_for_outward_transaction()

View File

@@ -1682,216 +1682,6 @@ class TestSerialandBatchBundle(ERPNextTestSuite):
)
self.assertRaises(NegativeStockError, backdated.submit)
def make_serial_item_for_valuation(self, item_code, use_serial_no_wise_valuation):
return make_item(
item_code,
{
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": item_code + "-.####",
"valuation_method": "FIFO" if use_serial_no_wise_valuation else "Moving Average",
"use_serial_no_wise_valuation": use_serial_no_wise_valuation,
},
)
def receive_serial_stock(self, item_code, qty, rate, warehouse):
entry = make_stock_entry(
item_code=item_code, target=warehouse, qty=qty, basic_rate=rate, use_serial_batch_fields=1
)
return get_serial_nos_from_bundle(entry.items[0].serial_and_batch_bundle)
def issue_serial_no(self, item_code, serial_no, warehouse):
return make_stock_entry(
item_code=item_code,
source=warehouse,
qty=1,
serial_no=serial_no,
use_serial_batch_fields=1,
)
def get_stock_value_difference(self, voucher_no):
return frappe.db.get_value(
"Stock Ledger Entry", {"voucher_no": voucher_no, "is_cancelled": 0}, "stock_value_difference"
)
def test_serial_no_wise_valuation_uses_serial_rate_when_enabled(self):
warehouse = "_Test Warehouse - _TC"
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation On", 1)
self.receive_serial_stock(item.name, 2, 100, warehouse)
newer_serial_nos = self.receive_serial_stock(item.name, 2, 200, warehouse)
issue = self.issue_serial_no(item.name, newer_serial_nos[-1], warehouse)
self.assertEqual(flt(self.get_stock_value_difference(issue.name)), -200.0)
def test_serial_no_wise_valuation_uses_item_valuation_method_when_disabled(self):
warehouse = "_Test Warehouse - _TC"
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Off", 0)
self.receive_serial_stock(item.name, 2, 100, warehouse)
newer_serial_nos = self.receive_serial_stock(item.name, 2, 200, warehouse)
issue = self.issue_serial_no(item.name, newer_serial_nos[-1], warehouse)
self.assertEqual(flt(self.get_stock_value_difference(issue.name)), -150.0)
def test_outward_bundle_rate_not_set_when_serial_no_wise_valuation_disabled(self):
warehouse = "_Test Warehouse - _TC"
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation No Rate", 0)
serial_nos = self.receive_serial_stock(item.name, 2, 100, warehouse)
issue = self.issue_serial_no(item.name, serial_nos[-1], warehouse)
rates = frappe.get_all(
"Serial and Batch Entry",
filters={"parent": issue.items[0].serial_and_batch_bundle},
pluck="incoming_rate",
)
self.assertTrue(rates)
for rate in rates:
self.assertEqual(flt(rate), 0.0)
def test_cannot_enable_serial_no_wise_valuation_when_serial_nos_exist(self):
warehouse = "_Test Warehouse - _TC"
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Toggle", 1)
self.receive_serial_stock(item.name, 1, 100, warehouse)
item.reload()
item.use_serial_no_wise_valuation = 0
item.save()
item.reload()
item.use_serial_no_wise_valuation = 1
self.assertRaises(frappe.ValidationError, item.save)
def make_purchase_return_for_serial_no(self, item_code, serial_no, receipt):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
entry = make_return_doc("Purchase Receipt", receipt.name)
entry.items[0].qty = -1
entry.items[0].received_qty = -1
for row in entry.items:
row.serial_and_batch_bundle = None
row.use_serial_batch_fields = 1
row.serial_no = serial_no
entry.save()
entry.submit()
return entry
def test_purchase_return_uses_item_valuation_method_when_disabled(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
warehouse = "_Test Warehouse - _TC"
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Pur Return Off", 0)
make_purchase_receipt(item_code=item.name, qty=2, rate=100, warehouse=warehouse)
costlier_receipt = make_purchase_receipt(item_code=item.name, qty=2, rate=200, warehouse=warehouse)
serial_nos = get_serial_nos_from_bundle(costlier_receipt.items[0].serial_and_batch_bundle)
entry = self.make_purchase_return_for_serial_no(item.name, serial_nos[-1], costlier_receipt)
self.assertEqual(flt(self.get_stock_value_difference(entry.name)), -150.0)
def test_purchase_return_uses_serial_rate_when_enabled(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
warehouse = "_Test Warehouse - _TC"
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Pur Return On", 1)
make_purchase_receipt(item_code=item.name, qty=2, rate=100, warehouse=warehouse)
costlier_receipt = make_purchase_receipt(item_code=item.name, qty=2, rate=200, warehouse=warehouse)
serial_nos = get_serial_nos_from_bundle(costlier_receipt.items[0].serial_and_batch_bundle)
entry = self.make_purchase_return_for_serial_no(item.name, serial_nos[-1], costlier_receipt)
self.assertEqual(flt(self.get_stock_value_difference(entry.name)), -200.0)
def test_valuation_method_forced_to_moving_average_when_disabled(self):
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Forced MA", 1)
self.receive_serial_stock(item.name, 1, 100, "_Test Warehouse - _TC")
item.reload()
item.valuation_method = "FIFO"
item.use_serial_no_wise_valuation = 0
item.save()
item.reload()
self.assertEqual(item.valuation_method, "Moving Average")
def test_valuation_method_kept_when_disabled_without_stock_transactions(self):
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation No MA Yet", 1)
item.reload()
item.valuation_method = "FIFO"
item.use_serial_no_wise_valuation = 0
item.save()
item.reload()
self.assertEqual(item.valuation_method, "FIFO")
def test_fifo_allowed_when_disabled_without_stock_transactions(self):
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation FIFO Ok", 0)
item.reload()
item.valuation_method = "FIFO"
item.save()
item.reload()
self.assertEqual(item.valuation_method, "FIFO")
def test_cannot_set_fifo_when_serial_no_wise_valuation_disabled(self):
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation No FIFO", 0)
self.receive_serial_stock(item.name, 1, 100, "_Test Warehouse - _TC")
item.reload()
self.assertEqual(item.valuation_method, "Moving Average")
item.valuation_method = "FIFO"
self.assertRaises(frappe.ValidationError, item.save)
def test_valuation_helpers_not_stale_after_disabling_in_same_request(self):
from collections import defaultdict
from erpnext.stock.utils import get_valuation_method, is_serial_no_wise_valuation_disabled
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Cache", 1)
self.receive_serial_stock(item.name, 1, 100, "_Test Warehouse - _TC")
previous_cache = getattr(frappe.local, "request_cache", None)
self.addCleanup(setattr, frappe.local, "request_cache", previous_cache)
frappe.local.request_cache = defaultdict(dict)
self.assertEqual(get_valuation_method(item.name), "FIFO")
self.assertFalse(is_serial_no_wise_valuation_disabled(item.name))
item.reload()
item.use_serial_no_wise_valuation = 0
item.save()
self.assertEqual(get_valuation_method(item.name), "Moving Average")
self.assertTrue(is_serial_no_wise_valuation_disabled(item.name))
def test_valuation_method_untouched_when_serial_no_wise_valuation_enabled(self):
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation Keeps FIFO", 1)
item.reload()
self.assertEqual(item.valuation_method, "FIFO")
def test_enable_serial_no_wise_valuation_allowed_without_serial_nos(self):
item = self.make_serial_item_for_valuation("_Test Serial Wise Valuation No Serials", 0)
item.reload()
item.use_serial_no_wise_valuation = 1
item.save()
item.reload()
self.assertEqual(item.use_serial_no_wise_valuation, 1)
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos

View File

@@ -248,6 +248,33 @@ class TestWarehouse(ERPNextTestSuite):
fetch_stock_accounts.assert_called_once_with(company)
def test_warehouse_account_company_validation(self):
company_1 = "_Test Company"
company_2 = "_Test Company 1"
account_company_2 = frappe.db.get_value(
"Account", {"company": company_2, "account_type": "Stock", "is_group": 0}, "name"
)
warehouse = frappe.get_doc(
{
"doctype": "Warehouse",
"warehouse_name": "Test Company Account Mismatch",
"company": company_1,
"account": account_company_2,
}
)
self.assertRaisesRegex(frappe.ValidationError, "does not belong to Company", warehouse.insert)
warehouse.account = None
warehouse.insert()
warehouse.account = account_company_2
self.assertRaisesRegex(frappe.ValidationError, "does not belong to Company", warehouse.save)
warehouse.delete()
def create_inventory_fallback_company():
company = "_Test Company Inventory Fallback"

View File

@@ -33,6 +33,12 @@ frappe.ui.form.on("Warehouse", {
});
},
company: function (frm) {
if (frm.doc.account) {
frm.set_value("account", "");
}
},
refresh: function (frm) {
frm.toggle_display("warehouse_name", frm.doc.__islocal);
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);

View File

@@ -71,9 +71,20 @@ class Warehouse(NestedSet):
self.set_onload("stock_exists", self.check_if_sle_exists(non_cancelled_only=True))
def validate(self):
self.validate_warehouse_account()
self.validate_inventory_account()
self.warn_about_multiple_warehouse_account()
def validate_warehouse_account(self):
if self.account and self.company:
account_company = frappe.get_cached_value("Account", self.account, "company")
if account_company and account_company != self.company:
frappe.throw(
_("Account {0} does not belong to Company {1}").format(
frappe.bold(self.account), frappe.bold(self.company)
)
)
def validate_inventory_account(self):
if (
not self.is_new()

View File

@@ -42,7 +42,6 @@ from erpnext.stock.utils import (
get_serial_nos_data,
get_stock_balance,
get_valuation_method,
is_serial_no_wise_valuation_disabled,
)
from erpnext.stock.valuation import FIFOValuation, LIFOValuation, round_off_if_near_zero
@@ -647,7 +646,6 @@ class update_entries_after:
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
self.set_precision()
self.valuation_method = get_valuation_method(self.item_code, self.company)
self.skip_serial_batch_valuation = is_serial_no_wise_valuation_disabled(self.item_code)
self.repost_affected_transaction = args.get("repost_affected_transaction") or set()
self.new_items_found = False
@@ -1087,17 +1085,13 @@ class update_entries_after:
if sle.get(dimension.get("fieldname")):
has_dimensions = True
<<<<<<< HEAD
if self.valuation_method == "Standard Cost":
# Inventory is always carried at the standard rate effective on the posting date;
# FIFO/Moving Average/serial-batch valuation is bypassed entirely.
self.process_standard_cost(sle)
elif sle.serial_and_batch_bundle:
=======
if sle.serial_and_batch_bundle and not self.skip_serial_batch_valuation:
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
self.calculate_valuation_for_serial_batch_bundle(sle)
elif sle.serial_no and not self.skip_serial_batch_valuation and not self.args.get("sle_id"):
elif sle.serial_no and not self.args.get("sle_id"):
# Only run in reposting
self.get_serialized_values(sle)
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
@@ -1109,7 +1103,6 @@ class update_entries_after:
)
elif (
sle.batch_no
and not self.skip_serial_batch_valuation
and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True)
and not self.args.get("sle_id")
):
@@ -1453,9 +1446,11 @@ class update_entries_after:
get_rate_for_return, # don't move this import to top
)
if self.valuation_method == "Moving Average" and (
self.skip_serial_batch_valuation
or not (sle.get("serial_no") or sle.get("batch_no") or sle.get("serial_and_batch_bundle"))
if (
self.valuation_method == "Moving Average"
and not sle.get("serial_no")
and not sle.get("batch_no")
and not sle.get("serial_and_batch_bundle")
):
rate = self.get_moving_average_rate_for_return(sle)
@@ -1468,9 +1463,6 @@ class update_entries_after:
sle=sle,
)
elif self.skip_serial_batch_valuation and flt(sle.actual_qty) < 0:
rate = 0.0
else:
rate = get_rate_for_return(
sle.voucher_type,
@@ -1812,9 +1804,6 @@ class update_entries_after:
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
def is_return_purchase_entry(self, sle):
if self.skip_serial_batch_valuation:
return False
if sle.voucher_type in ["Purchase Invoice", "Purchase Receipt"]:
return frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return")

View File

@@ -290,7 +290,6 @@ def _create_bin(item_code, warehouse):
@frappe.whitelist()
def get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fallbacks: bool = True):
<<<<<<< HEAD
"""Whitelisted entry point: authorise the caller, then compute the rate."""
args = frappe.parse_json(args)
@@ -307,8 +306,6 @@ def get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fal
def _get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fallbacks: bool = True):
=======
>>>>>>> f09ce05 (feat: use serial no wise valuation switch on item (#59082))
"""Get Incoming Rate based on valuation method"""
from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate
@@ -320,26 +317,15 @@ def _get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fa
in_rate = None
item_details = frappe.get_cached_value(
"Item",
args.get("item_code"),
["has_serial_no", "has_batch_no", "use_serial_no_wise_valuation"],
as_dict=1,
"Item", args.get("item_code"), ["has_serial_no", "has_batch_no"], as_dict=1
)
use_moving_avg_for_batch = frappe.get_single_value("Stock Settings", "do_not_use_batchwise_valuation")
skip_serial_batch_valuation = bool(
item_details and item_details.has_serial_no and not item_details.use_serial_no_wise_valuation
)
if isinstance(args, dict):
args = frappe._dict(args)
if (
item_details
and item_details.has_serial_no
and args.get("serial_and_batch_bundle")
and not skip_serial_batch_valuation
):
if item_details and item_details.has_serial_no and args.get("serial_and_batch_bundle"):
args.actual_qty = args.qty
sn_obj = SerialNoValuation(
sle=args,
@@ -354,7 +340,6 @@ def _get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fa
and item_details.has_batch_no
and args.get("serial_and_batch_bundle")
and not use_moving_avg_for_batch
and not skip_serial_batch_valuation
):
args.actual_qty = args.qty
batch_obj = BatchNoValuation(
@@ -365,23 +350,14 @@ def _get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fa
return batch_obj.get_incoming_rate()
elif (
(args.get("serial_no") or "").strip()
and not args.get("serial_and_batch_bundle")
and not skip_serial_batch_valuation
):
elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
args.actual_qty = args.qty
args.serial_nos = get_serial_nos_data(args.get("serial_no"))
sn_obj = SerialNoValuation(sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code"))
return sn_obj.get_incoming_rate()
elif (
args.get("batch_no")
and not args.get("serial_and_batch_bundle")
and not use_moving_avg_for_batch
and not skip_serial_batch_valuation
):
elif args.get("batch_no") and not args.get("serial_and_batch_bundle") and not use_moving_avg_for_batch:
args.actual_qty = args.qty
args.batch_nos = frappe._dict({args.batch_no: args})
@@ -434,14 +410,6 @@ def get_avg_purchase_rate(serial_nos):
)
def is_serial_no_wise_valuation_disabled(item_code) -> bool:
item_details = frappe.get_cached_value(
"Item", item_code, ["has_serial_no", "use_serial_no_wise_valuation"], as_dict=1
)
return bool(item_details and item_details.has_serial_no and not item_details.use_serial_no_wise_valuation)
@frappe.request_cache
def get_valuation_method(item_code, company=None):
"""get valuation method from item or default"""
@@ -455,14 +423,6 @@ def get_valuation_method(item_code, company=None):
return val_method
def clear_valuation_method_cache():
cache = getattr(frappe.local, "request_cache", None)
if not cache:
return
cache.pop(getattr(get_valuation_method, "__wrapped__", get_valuation_method), None)
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
return _get_fifo_lifo_rate(previous_stock_queue, qty, "FIFO")