mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-17 02:26:33 +00:00
This commit is contained in:
@@ -1571,6 +1571,18 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
raise_error_if_no_rate=raise_error_if_no_rate,
|
raise_error_if_no_rate=raise_error_if_no_rate,
|
||||||
batch_no=d.batch_no,
|
batch_no=d.batch_no,
|
||||||
serial_and_batch_bundle=d.serial_and_batch_bundle,
|
serial_and_batch_bundle=d.serial_and_batch_bundle,
|
||||||
|
posting_datetime=get_combine_datetime(self.posting_date, self.posting_time),
|
||||||
|
creation=self.first_sle_creation,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_sle_creation(self):
|
||||||
|
"""Creation of this entry's earliest ledger entry, if it has posted any yet."""
|
||||||
|
return frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_no": self.name, "voucher_type": self.doctype, "is_cancelled": 0},
|
||||||
|
"creation",
|
||||||
|
order_by="creation asc",
|
||||||
)
|
)
|
||||||
|
|
||||||
def has_consumption_basis(self) -> bool:
|
def has_consumption_basis(self) -> bool:
|
||||||
|
|||||||
@@ -57,6 +57,19 @@ def get_sle(**args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def stock_entry_row(item_code, qty, **kwargs):
|
||||||
|
stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
|
||||||
|
return {
|
||||||
|
"item_code": item_code,
|
||||||
|
"qty": qty,
|
||||||
|
"transfer_qty": qty,
|
||||||
|
"uom": stock_uom,
|
||||||
|
"stock_uom": stock_uom,
|
||||||
|
"conversion_factor": 1,
|
||||||
|
**kwargs,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestStockEntry(ERPNextTestSuite):
|
class TestStockEntry(ERPNextTestSuite):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.load_test_records("Stock Entry")
|
self.load_test_records("Stock Entry")
|
||||||
@@ -1371,32 +1384,25 @@ class TestStockEntry(ERPNextTestSuite):
|
|||||||
|
|
||||||
make_stock_entry(item_code=rm_item, target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
make_stock_entry(item_code=rm_item, target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
||||||
|
|
||||||
def row(item_code, qty, **kwargs):
|
|
||||||
stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
|
|
||||||
return {
|
|
||||||
"item_code": item_code,
|
|
||||||
"qty": qty,
|
|
||||||
"transfer_qty": qty,
|
|
||||||
"uom": stock_uom,
|
|
||||||
"stock_uom": stock_uom,
|
|
||||||
"conversion_factor": 1,
|
|
||||||
**kwargs,
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = frappe.new_doc("Stock Entry")
|
entry = frappe.new_doc("Stock Entry")
|
||||||
entry.company = "_Test Company"
|
entry.company = "_Test Company"
|
||||||
entry.purpose = "Manufacture"
|
entry.purpose = "Manufacture"
|
||||||
entry.set_stock_entry_type()
|
entry.set_stock_entry_type()
|
||||||
entry.fg_completed_qty = 1
|
entry.fg_completed_qty = 1
|
||||||
entry.append("items", row(rm_item, 10, s_warehouse="_Test Warehouse - _TC"))
|
entry.append("items", stock_entry_row(rm_item, 10, s_warehouse="_Test Warehouse - _TC"))
|
||||||
entry.append("items", row(fg_item, 1, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1))
|
|
||||||
entry.append(
|
entry.append(
|
||||||
"items",
|
"items",
|
||||||
row(scrap_item, 2, t_warehouse="_Test Warehouse 1 - _TC", secondary_item_type="Scrap"),
|
stock_entry_row(fg_item, 1, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1),
|
||||||
)
|
)
|
||||||
entry.append(
|
entry.append(
|
||||||
"items",
|
"items",
|
||||||
row(
|
stock_entry_row(
|
||||||
|
scrap_item, 2, t_warehouse="_Test Warehouse 1 - _TC", secondary_item_type="Scrap"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
entry.append(
|
||||||
|
"items",
|
||||||
|
stock_entry_row(
|
||||||
manual_item,
|
manual_item,
|
||||||
1,
|
1,
|
||||||
t_warehouse="_Test Warehouse 1 - _TC",
|
t_warehouse="_Test Warehouse 1 - _TC",
|
||||||
@@ -1425,6 +1431,88 @@ class TestStockEntry(ERPNextTestSuite):
|
|||||||
manual_row.valuation_type = "% of Component Cost"
|
manual_row.valuation_type = "% of Component Cost"
|
||||||
self.assertRaises(frappe.ValidationError, entry.save)
|
self.assertRaises(frappe.ValidationError, entry.save)
|
||||||
|
|
||||||
|
@ERPNextTestSuite.change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
|
||||||
|
def test_repost_values_costed_out_row_as_of_posting_date(self):
|
||||||
|
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries
|
||||||
|
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
rm_item = make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name
|
||||||
|
scrap_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=rm_item,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
posting_date=add_days(today(), -10),
|
||||||
|
)
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=scrap_item,
|
||||||
|
target="_Test Warehouse 1 - _TC",
|
||||||
|
qty=5,
|
||||||
|
basic_rate=50,
|
||||||
|
posting_date=add_days(today(), -10),
|
||||||
|
)
|
||||||
|
|
||||||
|
entry = frappe.new_doc("Stock Entry")
|
||||||
|
entry.company = "_Test Company"
|
||||||
|
entry.purpose = "Manufacture"
|
||||||
|
entry.set_stock_entry_type()
|
||||||
|
entry.set_posting_time = 1
|
||||||
|
entry.posting_date = add_days(today(), -5)
|
||||||
|
entry.posting_time = "10:00:00"
|
||||||
|
entry.fg_completed_qty = 1
|
||||||
|
entry.append("items", stock_entry_row(rm_item, 10, s_warehouse="_Test Warehouse - _TC"))
|
||||||
|
entry.append(
|
||||||
|
"items",
|
||||||
|
stock_entry_row(fg_item, 1, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1),
|
||||||
|
)
|
||||||
|
entry.append(
|
||||||
|
"items",
|
||||||
|
stock_entry_row(
|
||||||
|
scrap_item, 2, t_warehouse="_Test Warehouse 1 - _TC", secondary_item_type="Scrap"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
entry.insert()
|
||||||
|
entry.submit()
|
||||||
|
|
||||||
|
self.assertEqual(entry.items[2].basic_rate, 50)
|
||||||
|
self.assertEqual(entry.items[1].basic_rate, 900)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=scrap_item,
|
||||||
|
target="_Test Warehouse 1 - _TC",
|
||||||
|
qty=10,
|
||||||
|
basic_rate=5000,
|
||||||
|
posting_date=add_days(today(), -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=scrap_item,
|
||||||
|
target="_Test Warehouse 1 - _TC",
|
||||||
|
qty=10,
|
||||||
|
basic_rate=9000,
|
||||||
|
posting_date=add_days(today(), -5),
|
||||||
|
posting_time="10:00:00",
|
||||||
|
)
|
||||||
|
|
||||||
|
backdated_receipt = make_stock_entry(
|
||||||
|
item_code=rm_item,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=10,
|
||||||
|
basic_rate=200,
|
||||||
|
posting_date=add_days(today(), -8),
|
||||||
|
)
|
||||||
|
repost = frappe.db.get_value(
|
||||||
|
"Repost Item Valuation", {"voucher_no": backdated_receipt.name, "docstatus": 1}, "name"
|
||||||
|
)
|
||||||
|
repost_sl_entries(frappe.get_doc("Repost Item Valuation", repost))
|
||||||
|
|
||||||
|
entry.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(entry.items[2].basic_rate, 50)
|
||||||
|
self.assertEqual(entry.items[1].basic_rate, 1400)
|
||||||
|
|
||||||
def test_valuation_rate_lookup_without_voucher_no(self):
|
def test_valuation_rate_lookup_without_voucher_no(self):
|
||||||
from erpnext.stock.stock_ledger import get_valuation_rate
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
|
|
||||||
|
|||||||
@@ -2001,6 +2001,15 @@ def get_sle_by_voucher_detail_no(voucher_detail_no):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_prior_ledger_condition(table, posting_datetime, creation):
|
||||||
|
"""Restrict a ledger lookup to the entries that precede a voucher in ledger order."""
|
||||||
|
if creation:
|
||||||
|
return (table.posting_datetime < posting_datetime) | (
|
||||||
|
(table.posting_datetime == posting_datetime) & (table.creation < creation)
|
||||||
|
)
|
||||||
|
return table.posting_datetime <= posting_datetime
|
||||||
|
|
||||||
|
|
||||||
def get_valuation_rate(
|
def get_valuation_rate(
|
||||||
item_code,
|
item_code,
|
||||||
warehouse,
|
warehouse,
|
||||||
@@ -2013,6 +2022,8 @@ def get_valuation_rate(
|
|||||||
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,
|
||||||
|
posting_datetime=None,
|
||||||
|
creation=None,
|
||||||
):
|
):
|
||||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation
|
from erpnext.stock.serial_batch_bundle import BatchNoValuation
|
||||||
|
|
||||||
@@ -2035,6 +2046,9 @@ def get_valuation_rate(
|
|||||||
# Comparing against a None voucher_no yields NULL, which filters out every row
|
# Comparing against a None voucher_no yields NULL, which filters out every row
|
||||||
query = query.where((table.voucher_no != voucher_no) | (table.voucher_type != voucher_type))
|
query = query.where((table.voucher_no != voucher_no) | (table.voucher_type != voucher_type))
|
||||||
|
|
||||||
|
if posting_datetime:
|
||||||
|
query = query.where(get_prior_ledger_condition(table, posting_datetime, creation))
|
||||||
|
|
||||||
last_valuation_rate = query.run()
|
last_valuation_rate = query.run()
|
||||||
if last_valuation_rate and last_valuation_rate[0][0] is not None:
|
if last_valuation_rate and last_valuation_rate[0][0] is not None:
|
||||||
return flt(last_valuation_rate[0][0])
|
return flt(last_valuation_rate[0][0])
|
||||||
@@ -2058,25 +2072,32 @@ def get_valuation_rate(
|
|||||||
return batch_obj.get_incoming_rate()
|
return batch_obj.get_incoming_rate()
|
||||||
|
|
||||||
# Get valuation rate from last sle for the same item and warehouse
|
# Get valuation rate from last sle for the same item and warehouse
|
||||||
exclude_voucher_condition = ""
|
sle_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
values = [item_code, warehouse]
|
last_sle_query = (
|
||||||
|
frappe.qb.from_(sle_entry)
|
||||||
|
.select(sle_entry.valuation_rate)
|
||||||
|
.where(
|
||||||
|
(sle_entry.item_code == item_code)
|
||||||
|
& (sle_entry.warehouse == warehouse)
|
||||||
|
& (sle_entry.valuation_rate >= 0)
|
||||||
|
& (sle_entry.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.orderby(sle_entry.posting_datetime, order=frappe.qb.desc)
|
||||||
|
.orderby(sle_entry.creation, order=frappe.qb.desc)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
if voucher_no:
|
if voucher_no:
|
||||||
# Comparing against a None voucher_no yields NULL, which filters out every row
|
# Comparing against a None voucher_no yields NULL, which filters out every row
|
||||||
exclude_voucher_condition = "AND NOT (voucher_no = %s AND voucher_type = %s)"
|
last_sle_query = last_sle_query.where(
|
||||||
values.extend([voucher_no, voucher_type])
|
~((sle_entry.voucher_no == voucher_no) & (sle_entry.voucher_type == voucher_type))
|
||||||
|
)
|
||||||
|
|
||||||
if last_valuation_rate := frappe.db.sql( # nosemgrep
|
if posting_datetime:
|
||||||
f"""select valuation_rate
|
last_sle_query = last_sle_query.where(
|
||||||
from `tabStock Ledger Entry`
|
get_prior_ledger_condition(sle_entry, posting_datetime, creation)
|
||||||
where
|
)
|
||||||
item_code = %s
|
|
||||||
AND warehouse = %s
|
if last_valuation_rate := last_sle_query.run():
|
||||||
AND valuation_rate >= 0
|
|
||||||
AND is_cancelled = 0
|
|
||||||
{exclude_voucher_condition}
|
|
||||||
order by posting_datetime desc, creation desc limit 1""",
|
|
||||||
values,
|
|
||||||
):
|
|
||||||
return flt(last_valuation_rate[0][0])
|
return flt(last_valuation_rate[0][0])
|
||||||
|
|
||||||
if fallbacks:
|
if fallbacks:
|
||||||
@@ -2507,13 +2528,7 @@ def get_stock_value_difference(
|
|||||||
elif voucher_no:
|
elif voucher_no:
|
||||||
query = query.where(table.voucher_no != voucher_no)
|
query = query.where(table.voucher_no != voucher_no)
|
||||||
|
|
||||||
if creation:
|
query = query.where(get_prior_ledger_condition(table, posting_datetime, creation))
|
||||||
query = query.where(
|
|
||||||
(table.posting_datetime < posting_datetime)
|
|
||||||
| ((table.posting_datetime == posting_datetime) & (table.creation < creation))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
query = query.where(table.posting_datetime <= posting_datetime)
|
|
||||||
|
|
||||||
difference_amount = query.run()
|
difference_amount = query.run()
|
||||||
return flt(difference_amount[0][0]) if difference_amount else 0
|
return flt(difference_amount[0][0]) if difference_amount else 0
|
||||||
|
|||||||
Reference in New Issue
Block a user