mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-25 09:38:31 +00:00
perf: stock entry with batch
(cherry picked from commit 0b1b964b77)
Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
This commit is contained in:
@@ -224,9 +224,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
|
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
|
||||||
)
|
)
|
||||||
|
|
||||||
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
|
||||||
self.assertEqual(batch_qty, 10)
|
|
||||||
|
|
||||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||||
self.assertEqual(batch_qty_with_pos, 0.0)
|
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||||
|
|
||||||
@@ -256,9 +253,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
pcv_doc.reload()
|
pcv_doc.reload()
|
||||||
pcv_doc.cancel()
|
pcv_doc.cancel()
|
||||||
|
|
||||||
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
|
||||||
self.assertEqual(batch_qty, 10)
|
|
||||||
|
|
||||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||||
self.assertEqual(batch_qty_with_pos, 0.0)
|
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||||
|
|
||||||
|
|||||||
@@ -921,9 +921,11 @@ class StockController(AccountsController):
|
|||||||
row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname])
|
row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname])
|
||||||
|
|
||||||
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||||
|
from erpnext.stock.serial_batch_bundle import update_batch_qty
|
||||||
from erpnext.stock.stock_ledger import make_sl_entries
|
from erpnext.stock.stock_ledger import make_sl_entries
|
||||||
|
|
||||||
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
|
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
|
||||||
|
update_batch_qty(self.doctype, self.name, via_landed_cost_voucher=via_landed_cost_voucher)
|
||||||
|
|
||||||
def make_gl_entries_on_cancel(self):
|
def make_gl_entries_on_cancel(self):
|
||||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||||
|
|||||||
@@ -455,10 +455,14 @@ def get_available_batches(kwargs):
|
|||||||
|
|
||||||
batches = get_auto_batch_nos(kwargs)
|
batches = get_auto_batch_nos(kwargs)
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
if batch.get("batch_no") not in batchwise_qty:
|
key = batch.get("batch_no")
|
||||||
batchwise_qty[batch.get("batch_no")] = batch.get("qty")
|
if kwargs.get("based_on_warehouse"):
|
||||||
|
key = (batch.get("batch_no"), batch.get("warehouse"))
|
||||||
|
|
||||||
|
if key not in batchwise_qty:
|
||||||
|
batchwise_qty[key] = batch.get("qty")
|
||||||
else:
|
else:
|
||||||
batchwise_qty[batch.get("batch_no")] += batch.get("qty")
|
batchwise_qty[key] += batch.get("qty")
|
||||||
|
|
||||||
return batchwise_qty
|
return batchwise_qty
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ class SerialandBatchBundle(Document):
|
|||||||
self.set_is_outward()
|
self.set_is_outward()
|
||||||
self.calculate_total_qty()
|
self.calculate_total_qty()
|
||||||
self.set_warehouse()
|
self.set_warehouse()
|
||||||
self.set_incoming_rate()
|
|
||||||
|
if self.voucher_type != "Stock Entry" or not self.voucher_no or self.docstatus == 1:
|
||||||
|
self.set_incoming_rate()
|
||||||
|
|
||||||
self.calculate_qty_and_amount()
|
self.calculate_qty_and_amount()
|
||||||
|
|
||||||
def allow_existing_serial_nos(self):
|
def allow_existing_serial_nos(self):
|
||||||
@@ -1026,7 +1029,6 @@ class SerialandBatchBundle(Document):
|
|||||||
self.set_purchase_document_no()
|
self.set_purchase_document_no()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_batch_inventory()
|
|
||||||
self.validate_serial_nos_inventory()
|
self.validate_serial_nos_inventory()
|
||||||
|
|
||||||
def set_purchase_document_no(self):
|
def set_purchase_document_no(self):
|
||||||
@@ -1053,25 +1055,9 @@ class SerialandBatchBundle(Document):
|
|||||||
self.validate_batch_inventory()
|
self.validate_batch_inventory()
|
||||||
|
|
||||||
def validate_batch_inventory(self):
|
def validate_batch_inventory(self):
|
||||||
if (
|
|
||||||
self.voucher_type in ["Purchase Invoice", "Purchase Receipt"]
|
|
||||||
and frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.voucher_type in ["Sales Invoice", "Delivery Note"] and self.type_of_transaction == "Inward":
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.has_batch_no:
|
if not self.has_batch_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
|
||||||
self.voucher_type == "Stock Reconciliation"
|
|
||||||
and self.type_of_transaction == "Outward"
|
|
||||||
and frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty") > 0
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
batches = [d.batch_no for d in self.entries if d.batch_no]
|
batches = [d.batch_no for d in self.entries if d.batch_no]
|
||||||
if not batches:
|
if not batches:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -302,9 +302,6 @@ class SerialBatchBundle:
|
|||||||
):
|
):
|
||||||
self.set_batch_no_in_serial_nos()
|
self.set_batch_no_in_serial_nos()
|
||||||
|
|
||||||
if self.item_details.has_batch_no == 1:
|
|
||||||
self.update_batch_qty()
|
|
||||||
|
|
||||||
if self.sle.is_cancelled and self.sle.serial_and_batch_bundle:
|
if self.sle.is_cancelled and self.sle.serial_and_batch_bundle:
|
||||||
self.cancel_serial_and_batch_bundle()
|
self.cancel_serial_and_batch_bundle()
|
||||||
|
|
||||||
@@ -410,26 +407,6 @@ class SerialBatchBundle:
|
|||||||
.where(sn_table.name.isin(serial_nos))
|
.where(sn_table.name.isin(serial_nos))
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
def update_batch_qty(self):
|
|
||||||
from erpnext.stock.doctype.batch.batch import get_available_batches
|
|
||||||
|
|
||||||
batches = get_batch_nos(self.sle.serial_and_batch_bundle)
|
|
||||||
if not self.sle.serial_and_batch_bundle and self.sle.batch_no:
|
|
||||||
batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
|
|
||||||
|
|
||||||
batches_qty = get_available_batches(
|
|
||||||
frappe._dict(
|
|
||||||
{
|
|
||||||
"item_code": self.item_code,
|
|
||||||
"batch_no": list(batches.keys()),
|
|
||||||
"consider_negative_batches": 1,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for batch_no in batches:
|
|
||||||
frappe.db.set_value("Batch", batch_no, "batch_qty", batches_qty.get(batch_no, 0))
|
|
||||||
|
|
||||||
|
|
||||||
def get_serial_nos(serial_and_batch_bundle, serial_nos=None):
|
def get_serial_nos(serial_and_batch_bundle, serial_nos=None):
|
||||||
if not serial_and_batch_bundle:
|
if not serial_and_batch_bundle:
|
||||||
@@ -1258,3 +1235,53 @@ def get_serial_nos_batch(serial_nos):
|
|||||||
as_list=1,
|
as_list=1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_batch_qty(voucher_type, voucher_no, via_landed_cost_voucher=False):
|
||||||
|
from erpnext.stock.doctype.batch.batch import get_available_batches
|
||||||
|
|
||||||
|
batches = get_distinct_batches(voucher_type, voucher_no)
|
||||||
|
if not batches:
|
||||||
|
return
|
||||||
|
|
||||||
|
precision = frappe.get_precision("Batch", "batch_qty")
|
||||||
|
batch_data = get_available_batches(
|
||||||
|
frappe._dict({"batch_no": batches, "consider_negative_batches": 1, "based_on_warehouse": True})
|
||||||
|
)
|
||||||
|
batchwise_qty = defaultdict(float)
|
||||||
|
|
||||||
|
for (batch_no, warehouse), qty in batch_data.items():
|
||||||
|
if not via_landed_cost_voucher and flt(qty, precision) < 0:
|
||||||
|
throw_negative_batch_validation(batch_no, warehouse, qty)
|
||||||
|
|
||||||
|
batchwise_qty[batch_no] += qty
|
||||||
|
|
||||||
|
for batch_no in batches:
|
||||||
|
qty = flt(batchwise_qty.get(batch_no, 0), precision)
|
||||||
|
frappe.db.set_value("Batch", batch_no, "batch_qty", qty)
|
||||||
|
|
||||||
|
|
||||||
|
def throw_negative_batch_validation(batch_no, warehouse, qty):
|
||||||
|
frappe.throw(
|
||||||
|
_("The Batch {0} has negative quantity {1} in warehouse {2}. Please correct the quantity.").format(
|
||||||
|
bold(batch_no), bold(qty), bold(warehouse)
|
||||||
|
),
|
||||||
|
title=_("Negative Batch Quantity"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_distinct_batches(voucher_type, voucher_no):
|
||||||
|
bundles = frappe.get_all(
|
||||||
|
"Serial and Batch Bundle",
|
||||||
|
filters={"voucher_no": voucher_no, "voucher_type": voucher_type},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
if not bundles:
|
||||||
|
return
|
||||||
|
|
||||||
|
return frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
filters={"parent": ("in", bundles), "batch_no": ("is", "set")},
|
||||||
|
group_by="batch_no",
|
||||||
|
pluck="batch_no",
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user