mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-12 17:23:38 +00:00
refactor: single table for better performance
This commit is contained in:
@@ -921,6 +921,10 @@ class StockController(AccountsController):
|
||||
"Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1}
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Entry", {"parent": row.serial_and_batch_bundle}, {"is_cancelled": 1}
|
||||
)
|
||||
|
||||
if update_values:
|
||||
row.db_set(update_values)
|
||||
|
||||
@@ -929,6 +933,12 @@ class StockController(AccountsController):
|
||||
"Serial and Batch Bundle", row.rejected_serial_and_batch_bundle, {"is_cancelled": 1}
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Entry",
|
||||
{"parent": row.rejected_serial_and_batch_bundle},
|
||||
{"is_cancelled": 1},
|
||||
)
|
||||
|
||||
row.db_set("rejected_serial_and_batch_bundle", None)
|
||||
|
||||
if row.get("current_serial_and_batch_bundle"):
|
||||
@@ -2310,6 +2320,7 @@ def make_bundle_for_material_transfer(**kwargs):
|
||||
row.voucher_no = bundle_doc.voucher_no
|
||||
row.voucher_detail_no = bundle_doc.voucher_detail_no
|
||||
row.type_of_transaction = bundle_doc.type_of_transaction
|
||||
row.item_code = bundle_doc.item_code
|
||||
|
||||
bundle_doc.set_incoming_rate()
|
||||
bundle_doc.calculate_qty_and_amount()
|
||||
|
||||
@@ -443,7 +443,7 @@ erpnext.patches.v16_0.rename_subcontracted_quantity
|
||||
erpnext.patches.v16_0.add_new_stock_entry_types
|
||||
erpnext.patches.v15_0.set_asset_status_if_not_already_set
|
||||
erpnext.patches.v15_0.toggle_legacy_controller_for_period_closing
|
||||
erpnext.patches.v16_0.update_serial_batch_entries
|
||||
erpnext.patches.v16_0.update_serial_batch_entries #11-01-2026 10:00:00
|
||||
erpnext.patches.v16_0.set_company_wise_warehouses
|
||||
erpnext.patches.v16_0.set_valuation_method_on_companies
|
||||
erpnext.patches.v15_0.migrate_old_item_wise_tax_detail_data_to_table
|
||||
|
||||
@@ -11,7 +11,9 @@ def execute():
|
||||
SABE.voucher_type = SABB.voucher_type,
|
||||
SABE.voucher_no = SABB.voucher_no,
|
||||
SABE.voucher_detail_no = SABB.voucher_detail_no,
|
||||
SABE.type_of_transaction = SABB.type_of_transaction
|
||||
SABE.type_of_transaction = SABB.type_of_transaction,
|
||||
SABE.is_cancelled = SABB.is_cancelled,
|
||||
SABE.item_code = SABB.item_code
|
||||
WHERE SABE.parent = SABB.name
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -2806,6 +2806,64 @@ class TestDeliveryNote(IntegrationTestCase):
|
||||
|
||||
frappe.db.set_single_value("System Settings", "float_precision", original_flt_precision)
|
||||
|
||||
def test_different_rate_for_same_serial_nos(self):
|
||||
item_code = make_item(
|
||||
"Test Different Rate Serial No Item",
|
||||
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DRSN-.#####"},
|
||||
).name
|
||||
|
||||
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100)
|
||||
serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
rate=300,
|
||||
use_serial_batch_fields=1,
|
||||
serial_no="\n".join(serial_nos),
|
||||
)
|
||||
|
||||
dn.reload()
|
||||
|
||||
sabb = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle)
|
||||
for entry in sabb.entries:
|
||||
self.assertEqual(entry.incoming_rate, 100)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=1,
|
||||
basic_rate=200,
|
||||
use_serial_batch_fields=1,
|
||||
serial_no="\n".join(serial_nos),
|
||||
)
|
||||
dn1 = create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
rate=300,
|
||||
use_serial_batch_fields=1,
|
||||
serial_no="\n".join(serial_nos),
|
||||
)
|
||||
|
||||
dn1.reload()
|
||||
|
||||
sabb = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle)
|
||||
for entry in sabb.entries:
|
||||
self.assertEqual(entry.incoming_rate, 200)
|
||||
|
||||
doc = frappe.new_doc("Repost Item Valuation")
|
||||
doc.voucher_type = "Stock Entry"
|
||||
doc.voucher_no = se.name
|
||||
doc.submit()
|
||||
|
||||
sabb = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle)
|
||||
for entry in sabb.entries:
|
||||
self.assertEqual(entry.incoming_rate, 100)
|
||||
|
||||
sabb = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle)
|
||||
for entry in sabb.entries:
|
||||
self.assertEqual(entry.incoming_rate, 200)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -139,6 +139,7 @@ class SerialandBatchBundle(Document):
|
||||
self.set_incoming_rate()
|
||||
|
||||
self.calculate_qty_and_amount()
|
||||
self.set_child_details()
|
||||
|
||||
def validate_serial_no_status(self):
|
||||
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||
@@ -1342,8 +1343,18 @@ class SerialandBatchBundle(Document):
|
||||
self.set_source_document_no()
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_docstatus()
|
||||
self.validate_serial_nos_inventory()
|
||||
|
||||
def validate_docstatus(self):
|
||||
for row in self.entries:
|
||||
if row.docstatus != 1:
|
||||
frappe.throw(
|
||||
_("At Row {0}: In Serial and Batch Bundle {1} must have docstatus as 1 and not 0").format(
|
||||
bold(row.idx), bold(self.name)
|
||||
)
|
||||
)
|
||||
|
||||
def set_child_details(self):
|
||||
for row in self.entries:
|
||||
for field in [
|
||||
@@ -1353,6 +1364,7 @@ class SerialandBatchBundle(Document):
|
||||
"voucher_no",
|
||||
"voucher_detail_no",
|
||||
"type_of_transaction",
|
||||
"item_code",
|
||||
]:
|
||||
if not row.get(field) or row.get(field) != self.get(field):
|
||||
row.set(field, self.get(field))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"field_order": [
|
||||
"serial_no",
|
||||
"batch_no",
|
||||
"item_code",
|
||||
"column_break_2",
|
||||
"qty",
|
||||
"warehouse",
|
||||
@@ -22,6 +23,7 @@
|
||||
"reference_for_reservation",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"is_cancelled",
|
||||
"column_break_eykr",
|
||||
"posting_datetime",
|
||||
"type_of_transaction",
|
||||
@@ -146,24 +148,28 @@
|
||||
"fieldname": "posting_datetime",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Posting Datetime",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Type",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher No",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Detail No",
|
||||
"no_copy": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -171,18 +177,35 @@
|
||||
"fieldname": "type_of_transaction",
|
||||
"fieldtype": "Data",
|
||||
"label": "Type of Transaction",
|
||||
"no_copy": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_eykr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_cancelled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Cancelled",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Code",
|
||||
"no_copy": 1,
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-09 23:28:35.191959",
|
||||
"modified": "2026-01-11 11:05:10.789054",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Serial and Batch Entry",
|
||||
|
||||
@@ -17,7 +17,9 @@ class SerialandBatchEntry(Document):
|
||||
batch_no: DF.Link | None
|
||||
delivered_qty: DF.Float
|
||||
incoming_rate: DF.Float
|
||||
is_cancelled: DF.Check
|
||||
is_outward: DF.Check
|
||||
item_code: DF.Link | None
|
||||
outgoing_rate: DF.Float
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
|
||||
@@ -324,6 +324,12 @@ class SerialBatchBundle:
|
||||
{"is_cancelled": 1},
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Entry",
|
||||
{"voucher_no": self.sle.voucher_no, "voucher_type": self.sle.voucher_type},
|
||||
{"is_cancelled": 1},
|
||||
)
|
||||
|
||||
if self.sle.serial_and_batch_bundle:
|
||||
frappe.get_cached_doc(
|
||||
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle
|
||||
@@ -642,26 +648,23 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
self.calculate_stock_value_from_deprecarated_ledgers()
|
||||
|
||||
def get_serial_no_wise_incoming_rate(self, serial_nos):
|
||||
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
||||
bundle_child = frappe.qb.DocType("Serial and Batch Entry")
|
||||
|
||||
def get_latest_based_on_posting_datetime():
|
||||
# Get latest inward record based on posting datetime for each serial no
|
||||
|
||||
latest_posting = (
|
||||
frappe.qb.from_(bundle)
|
||||
.inner_join(bundle_child)
|
||||
.on(bundle.name == bundle_child.parent)
|
||||
frappe.qb.from_(bundle_child)
|
||||
.select(
|
||||
bundle_child.serial_no,
|
||||
Max(bundle.posting_datetime).as_("max_posting_dt"),
|
||||
Max(bundle_child.posting_datetime).as_("max_posting_dt"),
|
||||
)
|
||||
.where(
|
||||
(bundle.is_cancelled == 0)
|
||||
& (bundle.docstatus == 1)
|
||||
& (bundle.type_of_transaction == "Inward")
|
||||
(bundle_child.is_cancelled == 0)
|
||||
& (bundle_child.docstatus == 1)
|
||||
& (bundle_child.type_of_transaction == "Inward")
|
||||
& (bundle_child.qty > 0)
|
||||
& (bundle.item_code == self.sle.item_code)
|
||||
& (bundle_child.item_code == self.sle.item_code)
|
||||
& (bundle_child.warehouse == self.sle.warehouse)
|
||||
& (bundle_child.serial_no.isin(serial_nos))
|
||||
)
|
||||
@@ -670,10 +673,10 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
|
||||
# Important to exclude the current voucher to calculate correct the stock value difference
|
||||
if self.sle.voucher_no:
|
||||
latest_posting = latest_posting.where(bundle.voucher_no != self.sle.voucher_no)
|
||||
latest_posting = latest_posting.where(bundle_child.voucher_no != self.sle.voucher_no)
|
||||
|
||||
if self.sle.posting_datetime:
|
||||
timestamp_condition = bundle.posting_datetime <= self.sle.posting_datetime
|
||||
timestamp_condition = bundle_child.posting_datetime <= self.sle.posting_datetime
|
||||
|
||||
latest_posting = latest_posting.where(timestamp_condition)
|
||||
|
||||
@@ -684,24 +687,22 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
def get_latest_based_on_creation(latest_posting):
|
||||
# Get latest inward record based on creation for each serial no
|
||||
latest_creation = (
|
||||
frappe.qb.from_(bundle)
|
||||
.join(bundle_child)
|
||||
.on(bundle.name == bundle_child.parent)
|
||||
frappe.qb.from_(bundle_child)
|
||||
.join(latest_posting)
|
||||
.on(
|
||||
(latest_posting.serial_no == bundle_child.serial_no)
|
||||
& (latest_posting.max_posting_dt == bundle.posting_datetime)
|
||||
& (latest_posting.max_posting_dt == bundle_child.posting_datetime)
|
||||
)
|
||||
.select(
|
||||
bundle_child.serial_no,
|
||||
Max(bundle.creation).as_("max_creation"),
|
||||
Max(bundle_child.creation).as_("max_creation"),
|
||||
)
|
||||
.where(
|
||||
(bundle.is_cancelled == 0)
|
||||
& (bundle.docstatus == 1)
|
||||
& (bundle.type_of_transaction == "Inward")
|
||||
(bundle_child.is_cancelled == 0)
|
||||
& (bundle_child.docstatus == 1)
|
||||
& (bundle_child.type_of_transaction == "Inward")
|
||||
& (bundle_child.qty > 0)
|
||||
& (bundle.item_code == self.sle.item_code)
|
||||
& (bundle_child.item_code == self.sle.item_code)
|
||||
& (bundle_child.warehouse == self.sle.warehouse)
|
||||
)
|
||||
.groupby(bundle_child.serial_no)
|
||||
@@ -713,13 +714,11 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
latest_creation = get_latest_based_on_creation(latest_posting)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(bundle)
|
||||
.join(bundle_child)
|
||||
.on(bundle.name == bundle_child.parent)
|
||||
frappe.qb.from_(bundle_child)
|
||||
.join(latest_creation)
|
||||
.on(
|
||||
(latest_creation.serial_no == bundle_child.serial_no)
|
||||
& (latest_creation.max_creation == bundle.creation)
|
||||
& (latest_creation.max_creation == bundle_child.creation)
|
||||
)
|
||||
.select(
|
||||
bundle_child.serial_no,
|
||||
@@ -841,7 +840,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
Sum(child.qty).as_("total_qty"),
|
||||
)
|
||||
.where(
|
||||
(child.warehouse == self.sle.warehouse)
|
||||
(child.item_code == self.sle.item_code)
|
||||
& (child.warehouse == self.sle.warehouse)
|
||||
& (child.batch_no.isin(self.batchwise_valuation_batches))
|
||||
& (child.docstatus == 1)
|
||||
& (child.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
@@ -887,7 +887,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
Sum(child.qty).as_("qty"),
|
||||
)
|
||||
.where(
|
||||
(child.warehouse == self.sle.warehouse)
|
||||
(child.item_code == self.sle.item_code)
|
||||
& (child.warehouse == self.sle.warehouse)
|
||||
& (child.batch_no.isin(self.batchwise_valuation_batches))
|
||||
& (child.docstatus == 1)
|
||||
& (child.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
|
||||
Reference in New Issue
Block a user