Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: serial and batch bundle valuation (reposting) (backport #40255) #40262

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions erpnext/stock/deprecated_serial_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ def calculate_stock_value_from_deprecarated_ledgers(self):
):
return

serial_nos = self.get_serial_nos()
serial_nos = self.get_filterd_serial_nos()
if not serial_nos:
return

actual_qty = flt(self.sle.actual_qty)

Expand All @@ -25,8 +27,21 @@ def calculate_stock_value_from_deprecarated_ledgers(self):

self.stock_value_change += stock_value_change

def get_filterd_serial_nos(self):
serial_nos = []
non_filtered_serial_nos = self.get_serial_nos()

# If the serial no inwarded using the Serial and Batch Bundle, then the serial no should not be considered
for serial_no in non_filtered_serial_nos:
if serial_no and serial_no not in self.serial_no_incoming_rate:
serial_nos.append(serial_no)

return serial_nos

@deprecated
def get_incoming_value_for_serial_nos(self, serial_nos):
from erpnext.stock.utils import get_combine_datetime

# get rate from serial nos within same company
incoming_values = 0.0
for serial_no in serial_nos:
Expand All @@ -42,18 +57,20 @@ def get_incoming_value_for_serial_nos(self, serial_nos):
| (table.serial_no.like("%\n" + serial_no + "\n%"))
)
& (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 <= get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
)
)
.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)
if sle.actual_qty > 0
else (sle.stock_value_difference / sle.actual_qty) * -1
)
self.serial_no_incoming_rate[serial_no] += flt(sle.incoming_rate)
incoming_values += self.serial_no_incoming_rate[serial_no]

return incoming_values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,23 +415,6 @@ def test_serialized_lcv_delivered(self):
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=charges)
new_purchase_rate = serial_no_rate + charges

sn_obj = SerialNoValuation(
sle=frappe._dict(
{
"posting_date": today(),
"posting_time": nowtime(),
"item_code": "_Test Serialized Item",
"warehouse": "Stores - TCP1",
"serial_nos": [serial_no],
}
)
)

new_serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)

# Since the serial no is already delivered the rate must be zero
self.assertFalse(new_serial_no_rate)

stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
filters={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,8 @@ def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)

for d in self.entries:
if not rate or (
flt(rate, precision) == flt(d.incoming_rate, precision) and d.stock_value_difference
):
continue

d.incoming_rate = flt(rate, precision)
if self.has_batch_no:
if d.qty:
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)

if save:
Expand Down
36 changes: 19 additions & 17 deletions erpnext/stock/serial_batch_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import frappe
from frappe import _, bold
from frappe.model.naming import make_autoname
from frappe.query_builder.functions import CombineDatetime, Sum
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
from pypika import Order

from erpnext.stock.deprecated_serial_batch import (
DeprecatedBatchNoValuation,
Expand Down Expand Up @@ -424,40 +425,40 @@ def calculate_stock_value_change(self):
)

else:
entries = self.get_serial_no_ledgers()

self.serial_no_incoming_rate = defaultdict(float)
self.stock_value_change = 0.0

for ledger in entries:
self.stock_value_change += ledger.incoming_rate
self.serial_no_incoming_rate[ledger.serial_no] += ledger.incoming_rate
serial_nos = self.get_serial_nos()
for serial_no in serial_nos:
incoming_rate = self.get_incoming_rate_from_bundle(serial_no)
if not incoming_rate:
continue

self.stock_value_change += incoming_rate
self.serial_no_incoming_rate[serial_no] += incoming_rate

self.calculate_stock_value_from_deprecarated_ledgers()

def get_serial_no_ledgers(self):
serial_nos = self.get_serial_nos()
def get_incoming_rate_from_bundle(self, serial_no) -> float:
bundle = frappe.qb.DocType("Serial and Batch Bundle")
bundle_child = frappe.qb.DocType("Serial and Batch Entry")

query = (
frappe.qb.from_(bundle)
.inner_join(bundle_child)
.on(bundle.name == bundle_child.parent)
.select(
bundle.name,
bundle_child.serial_no,
(bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"),
)
.select((bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"))
.where(
(bundle.is_cancelled == 0)
& (bundle.docstatus == 1)
& (bundle_child.serial_no.isin(serial_nos))
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
& (bundle_child.serial_no == serial_no)
& (bundle.type_of_transaction == "Inward")
& (bundle_child.qty > 0)
& (bundle.item_code == self.sle.item_code)
& (bundle_child.warehouse == self.sle.warehouse)
)
.orderby(bundle.posting_date, bundle.posting_time, bundle.creation)
.orderby(Timestamp(bundle.posting_date, bundle.posting_time), order=Order.desc)
.limit(1)
)

# Important to exclude the current voucher to calculate correct the stock value difference
Expand All @@ -474,7 +475,8 @@ def get_serial_no_ledgers(self):

query = query.where(timestamp_condition)

return query.run(as_dict=True)
incoming_rate = query.run()
return flt(incoming_rate[0][0]) if incoming_rate else 0.0

def get_serial_nos(self):
if self.sle.get("serial_nos"):
Expand Down
19 changes: 18 additions & 1 deletion erpnext/stock/stock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,12 @@ def get_incoming_outgoing_rate_from_transaction(self, sle):
get_rate_for_return, # don't move this import to top
)

if self.valuation_method == "Moving Average":
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 = get_incoming_rate(
{
"item_code": sle.item_code,
Expand All @@ -979,6 +984,18 @@ def get_incoming_outgoing_rate_from_transaction(self, sle):
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)

if (
sle.get("serial_and_batch_bundle")
and rate > 0
and sle.voucher_type in ["Delivery Note", "Sales Invoice"]
):
frappe.db.set_value(
sle.voucher_type + " Item",
sle.voucher_detail_no,
"incoming_rate",
rate,
)
elif (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no
Expand Down
Loading