mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-16 08:05:00 +00:00
Merge pull request #49920 from frappe/mergify/bp/version-15-hotfix/pr-49890
perf: serial nos / batches reposting (backport #49890)
This commit is contained in:
@@ -7,6 +7,7 @@ from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import flt, nowtime
|
||||
from frappe.utils.deprecations import deprecated
|
||||
from pypika import Order
|
||||
from pypika.functions import Coalesce
|
||||
|
||||
|
||||
class DeprecatedSerialNoValuation:
|
||||
@@ -197,9 +198,15 @@ class DeprecatedBatchNoValuation:
|
||||
|
||||
@deprecated
|
||||
def set_balance_value_for_non_batchwise_valuation_batches(self):
|
||||
self.last_sle = self.get_last_sle_for_non_batch()
|
||||
if hasattr(self, "prev_sle"):
|
||||
self.last_sle = self.prev_sle
|
||||
else:
|
||||
self.last_sle = self.get_last_sle_for_non_batch()
|
||||
|
||||
if self.last_sle and self.last_sle.stock_queue:
|
||||
self.stock_queue = json.loads(self.last_sle.stock_queue or "[]") or []
|
||||
self.stock_queue = self.last_sle.stock_queue
|
||||
if isinstance(self.stock_queue, str):
|
||||
self.stock_queue = json.loads(self.stock_queue) or []
|
||||
|
||||
self.set_balance_value_from_sl_entries()
|
||||
self.set_balance_value_from_bundle()
|
||||
@@ -293,10 +300,7 @@ class DeprecatedBatchNoValuation:
|
||||
query = query.where(sle.name != self.sle.name)
|
||||
|
||||
if self.sle.serial_and_batch_bundle:
|
||||
query = query.where(
|
||||
(sle.serial_and_batch_bundle != self.sle.serial_and_batch_bundle)
|
||||
| (sle.serial_and_batch_bundle.isnull())
|
||||
)
|
||||
query = query.where(Coalesce(sle.serial_and_batch_bundle, "") != self.sle.serial_and_batch_bundle)
|
||||
|
||||
data = query.run(as_dict=True)
|
||||
|
||||
|
||||
@@ -1323,9 +1323,18 @@ class TestStockEntry(FrappeTestCase):
|
||||
posting_date="2021-07-02", # Illegal SE
|
||||
purpose="Material Transfer",
|
||||
),
|
||||
dict(
|
||||
item_code=item_code,
|
||||
qty=2,
|
||||
from_warehouse=warehouse_names[0],
|
||||
to_warehouse=warehouse_names[1],
|
||||
batch_no=batch_no,
|
||||
posting_date="2021-07-02", # Illegal SE
|
||||
purpose="Material Transfer",
|
||||
),
|
||||
]
|
||||
|
||||
self.assertRaises(NegativeStockError, create_stock_entries, sequence_of_entries)
|
||||
self.assertRaises(frappe.ValidationError, create_stock_entries, sequence_of_entries)
|
||||
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||
def test_future_negative_sle_batch(self):
|
||||
|
||||
@@ -1010,13 +1010,12 @@ class update_entries_after:
|
||||
if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle):
|
||||
return
|
||||
|
||||
if self.args.get("sle_id") and sle.actual_qty < 0:
|
||||
doc = frappe.db.get_value(
|
||||
"Serial and Batch Bundle",
|
||||
sle.serial_and_batch_bundle,
|
||||
["total_amount", "total_qty"],
|
||||
as_dict=1,
|
||||
)
|
||||
if sle.actual_qty < 0 and (
|
||||
sle.voucher_type in ["Stock Reconciliation", "Asset Capitalization"]
|
||||
or not frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_return")
|
||||
):
|
||||
doc = frappe._dict({})
|
||||
self.update_serial_batch_no_valuation(sle, doc, prev_sle=self.wh_data)
|
||||
else:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
|
||||
doc.set_incoming_rate(
|
||||
@@ -1040,6 +1039,88 @@ class update_entries_after:
|
||||
self.wh_data.qty_after_transaction, self.flt_precision
|
||||
)
|
||||
|
||||
def update_serial_batch_no_valuation(self, sle, doc, prev_sle=None):
|
||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
|
||||
|
||||
sabb_data = get_serial_from_sabb(sle.serial_and_batch_bundle)
|
||||
if not sabb_data:
|
||||
doc.update({"total_amount": 0.0, "total_qty": 0.0, "avg_rate": 0.0})
|
||||
return
|
||||
|
||||
serial_nos = [d.serial_no for d in sabb_data if d.serial_no]
|
||||
if serial_nos:
|
||||
sle["serial_nos"] = get_serial_nos_data(",".join(serial_nos))
|
||||
sn_obj = SerialNoValuation(
|
||||
sle=sle,
|
||||
item_code=self.item_code,
|
||||
warehouse=sle.warehouse,
|
||||
)
|
||||
else:
|
||||
sle["batch_nos"] = {row.batch_no: row for row in sabb_data if row.batch_no}
|
||||
sn_obj = BatchNoValuation(
|
||||
sle=sle,
|
||||
item_code=self.item_code,
|
||||
warehouse=sle.warehouse,
|
||||
prev_sle=prev_sle,
|
||||
)
|
||||
|
||||
tot_amt = 0.0
|
||||
total_qty = 0.0
|
||||
avg_rate = 0.0
|
||||
|
||||
for d in sabb_data:
|
||||
incoming_rate = get_incoming_rate_for_serial_and_batch(self.item_code, d, sn_obj)
|
||||
|
||||
if flt(incoming_rate, self.currency_precision) == flt(
|
||||
d.valuation_rate, self.currency_precision
|
||||
) and not getattr(d, "stock_queue", None):
|
||||
continue
|
||||
|
||||
amount = incoming_rate * flt(d.qty)
|
||||
tot_amt += flt(amount)
|
||||
total_qty += flt(d.qty)
|
||||
|
||||
values_to_update = {
|
||||
"incoming_rate": incoming_rate,
|
||||
"stock_value_difference": amount,
|
||||
}
|
||||
|
||||
if d.stock_queue:
|
||||
values_to_update["stock_queue"] = d.stock_queue
|
||||
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Entry",
|
||||
d.name,
|
||||
values_to_update,
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
if total_qty:
|
||||
avg_rate = tot_amt / total_qty
|
||||
|
||||
doc.update(
|
||||
{
|
||||
"total_amount": tot_amt,
|
||||
"total_qty": total_qty,
|
||||
"avg_rate": avg_rate,
|
||||
}
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle",
|
||||
sle.serial_and_batch_bundle,
|
||||
{
|
||||
"total_qty": total_qty,
|
||||
"avg_rate": avg_rate,
|
||||
"total_amount": tot_amt,
|
||||
},
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
for key in ("serial_nos", "batch_nos"):
|
||||
if key in sle:
|
||||
del sle[key]
|
||||
|
||||
def get_outgoing_rate_for_batched_item(self, sle):
|
||||
if self.wh_data.qty_after_transaction == 0:
|
||||
return 0
|
||||
@@ -2297,3 +2378,45 @@ def is_transfer_stock_entry(voucher_no):
|
||||
purpose = frappe.get_cached_value("Stock Entry", voucher_no, "purpose")
|
||||
|
||||
return purpose in ["Material Transfer", "Material Transfer for Manufacture", "Send to Subcontractor"]
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_serial_from_sabb(serial_and_batch_bundle):
|
||||
return frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": serial_and_batch_bundle},
|
||||
fields=["serial_no", "batch_no", "name", "qty", "incoming_rate"],
|
||||
order_by="idx",
|
||||
)
|
||||
|
||||
|
||||
def get_incoming_rate_for_serial_and_batch(item_code, row, sn_obj):
|
||||
if row.serial_no:
|
||||
return abs(sn_obj.serial_no_incoming_rate.get(row.serial_no, 0.0))
|
||||
else:
|
||||
stock_queue = []
|
||||
if hasattr(sn_obj, "stock_queue") and sn_obj.stock_queue:
|
||||
stock_queue = parse_json(sn_obj.stock_queue)
|
||||
|
||||
val_method = get_valuation_method(item_code)
|
||||
|
||||
actual_qty = row.qty
|
||||
if stock_queue and val_method == "FIFO" and row.batch_no in sn_obj.non_batchwise_valuation_batches:
|
||||
if actual_qty < 0:
|
||||
stock_queue = FIFOValuation(stock_queue)
|
||||
_prev_qty, prev_stock_value = stock_queue.get_total_stock_and_value()
|
||||
|
||||
stock_queue.remove_stock(qty=abs(actual_qty))
|
||||
_qty, stock_value = stock_queue.get_total_stock_and_value()
|
||||
|
||||
stock_value_difference = stock_value - prev_stock_value
|
||||
incoming_rate = abs(flt(stock_value_difference) / abs(flt(actual_qty)))
|
||||
stock_queue = stock_queue.state
|
||||
else:
|
||||
incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(row.batch_no)))
|
||||
stock_queue.append([row.qty, incoming_rate])
|
||||
row.stock_queue = json.dumps(stock_queue)
|
||||
else:
|
||||
incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(row.batch_no)))
|
||||
|
||||
return incoming_rate
|
||||
|
||||
Reference in New Issue
Block a user