mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-27 02:28:30 +00:00
perf: SABB taking time to save the record
(cherry picked from commit 20320c4a6c)
# Conflicts:
# erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
# erpnext/stock/serial_batch_bundle.py
This commit is contained in:
committed by
Mergify
parent
d83365734e
commit
ee9debe581
@@ -481,6 +481,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
rate=1000,
|
rate=1000,
|
||||||
serial_no=[serial_nos[0]],
|
serial_no=[serial_nos[0]],
|
||||||
do_not_save=1,
|
do_not_save=1,
|
||||||
|
ignore_sabb_validation=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
|
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
|
||||||
@@ -956,6 +957,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
qty=1,
|
qty=1,
|
||||||
rate=100,
|
rate=100,
|
||||||
do_not_submit=True,
|
do_not_submit=True,
|
||||||
|
ignore_sabb_validation=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||||
@@ -1097,6 +1099,7 @@ def create_pos_invoice(**args):
|
|||||||
"posting_time": pos_inv.posting_time,
|
"posting_time": pos_inv.posting_time,
|
||||||
"type_of_transaction": type_of_transaction,
|
"type_of_transaction": type_of_transaction,
|
||||||
"do_not_submit": True,
|
"do_not_submit": True,
|
||||||
|
"ignore_sabb_validation": args.ignore_sabb_validation,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).name
|
).name
|
||||||
|
|||||||
@@ -778,9 +778,8 @@ class TestSubcontractingController(FrappeTestCase):
|
|||||||
row.serial_no = "ABC"
|
row.serial_no = "ABC"
|
||||||
break
|
break
|
||||||
|
|
||||||
bundle.save()
|
self.assertRaises(frappe.ValidationError, bundle.save)
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, scr1.save)
|
|
||||||
bundle.load_from_db()
|
bundle.load_from_db()
|
||||||
for row in bundle.entries:
|
for row in bundle.entries:
|
||||||
if row.idx == 1:
|
if row.idx == 1:
|
||||||
|
|||||||
@@ -116,10 +116,24 @@ class SerialandBatchBundle(Document):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.allow_existing_serial_nos()
|
self.allow_existing_serial_nos()
|
||||||
|
<<<<<<< HEAD
|
||||||
if not self.flags.ignore_validate_serial_batch or frappe.flags.in_test:
|
if not self.flags.ignore_validate_serial_batch or frappe.flags.in_test:
|
||||||
self.validate_serial_nos_duplicate()
|
self.validate_serial_nos_duplicate()
|
||||||
|
=======
|
||||||
|
if self.docstatus == 1:
|
||||||
|
if not self.flags.ignore_validate_serial_batch or frappe.in_test:
|
||||||
|
self.validate_serial_nos_duplicate()
|
||||||
|
|
||||||
|
self.check_future_entries_exists()
|
||||||
|
elif (
|
||||||
|
self.has_serial_no
|
||||||
|
and self.type_of_transaction == "Outward"
|
||||||
|
and self.voucher_type != "Stock Reconciliation"
|
||||||
|
and self.voucher_no
|
||||||
|
):
|
||||||
|
self.validate_serial_no_status()
|
||||||
|
>>>>>>> 20320c4a6c (perf: SABB taking time to save the record)
|
||||||
|
|
||||||
self.check_future_entries_exists()
|
|
||||||
self.set_is_outward()
|
self.set_is_outward()
|
||||||
self.calculate_total_qty()
|
self.calculate_total_qty()
|
||||||
self.set_warehouse()
|
self.set_warehouse()
|
||||||
@@ -129,6 +143,25 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
self.calculate_qty_and_amount()
|
self.calculate_qty_and_amount()
|
||||||
|
|
||||||
|
def validate_serial_no_status(self):
|
||||||
|
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
|
invalid_serial_nos = frappe.get_all(
|
||||||
|
"Serial No",
|
||||||
|
filters={
|
||||||
|
"name": ("in", serial_nos),
|
||||||
|
"warehouse": ("!=", self.warehouse),
|
||||||
|
},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
if invalid_serial_nos:
|
||||||
|
msg = _(
|
||||||
|
"You cannot outward following {0} as either they are Delivered, Inactive or located in a different warehouse."
|
||||||
|
).format(_("Serial Nos") if len(invalid_serial_nos) > 1 else _("Serial No"))
|
||||||
|
msg += "<hr>"
|
||||||
|
msg += ", ".join(sn for sn in invalid_serial_nos)
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
def validate_voucher_detail_no(self):
|
def validate_voucher_detail_no(self):
|
||||||
if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
|
if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
|
||||||
"Installation Note",
|
"Installation Note",
|
||||||
@@ -702,10 +735,16 @@ class SerialandBatchBundle(Document):
|
|||||||
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
precision = frappe.get_precision("Serial and Batch Entry", "incoming_rate")
|
||||||
for d in self.entries:
|
for d in self.entries:
|
||||||
if self.is_rejected and not set_valuation_rate_for_rejected_materials:
|
if self.is_rejected and not set_valuation_rate_for_rejected_materials:
|
||||||
rate = 0.0
|
rate = 0.0
|
||||||
elif (d.incoming_rate == rate) and not stock_queue and d.qty and d.stock_value_difference:
|
elif (
|
||||||
|
(flt(d.incoming_rate, precision) == flt(rate, precision))
|
||||||
|
and not stock_queue
|
||||||
|
and d.qty
|
||||||
|
and d.stock_value_difference
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if is_packed_item and d.incoming_rate:
|
if is_packed_item and d.incoming_rate:
|
||||||
@@ -766,7 +805,7 @@ class SerialandBatchBundle(Document):
|
|||||||
self.calculate_total_qty(save=True)
|
self.calculate_total_qty(save=True)
|
||||||
|
|
||||||
# If user has changed the rate in the child table
|
# If user has changed the rate in the child table
|
||||||
if self.docstatus == 0:
|
if self.docstatus == 0 and self.type_of_transaction == "Inward":
|
||||||
self.set_incoming_rate(parent=parent, row=row, save=True)
|
self.set_incoming_rate(parent=parent, row=row, save=True)
|
||||||
|
|
||||||
if self.docstatus == 0 and parent.get("is_return") and parent.is_new():
|
if self.docstatus == 0 and parent.get("is_return") and parent.is_new():
|
||||||
|
|||||||
@@ -982,6 +982,7 @@ def make_serial_batch_bundle(kwargs):
|
|||||||
"type_of_transaction": type_of_transaction,
|
"type_of_transaction": type_of_transaction,
|
||||||
"company": kwargs.company or "_Test Company",
|
"company": kwargs.company or "_Test Company",
|
||||||
"do_not_submit": kwargs.do_not_submit,
|
"do_not_submit": kwargs.do_not_submit,
|
||||||
|
"ignore_sabb_validation": kwargs.ignore_sabb_validation or False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ import frappe
|
|||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.naming import NamingSeries, make_autoname, parse_naming_series
|
from frappe.model.naming import NamingSeries, make_autoname, parse_naming_series
|
||||||
from frappe.query_builder import Case
|
from frappe.query_builder import Case
|
||||||
|
<<<<<<< HEAD
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
|
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
|
||||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today
|
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today
|
||||||
|
=======
|
||||||
|
from frappe.query_builder.functions import Max, Sum
|
||||||
|
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowtime, today
|
||||||
|
>>>>>>> 20320c4a6c (perf: SABB taking time to save the record)
|
||||||
from pypika import Order
|
from pypika import Order
|
||||||
from pypika.terms import ExistsCriterion
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
@@ -616,8 +621,9 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
self.old_serial_nos = []
|
self.old_serial_nos = []
|
||||||
|
|
||||||
serial_nos = self.get_serial_nos()
|
serial_nos = self.get_serial_nos()
|
||||||
|
result = self.get_serial_no_wise_incoming_rate(serial_nos)
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
incoming_rate = self.get_incoming_rate_from_bundle(serial_no)
|
incoming_rate = result.get(serial_no)
|
||||||
if incoming_rate is None:
|
if incoming_rate is None:
|
||||||
self.old_serial_nos.append(serial_no)
|
self.old_serial_nos.append(serial_no)
|
||||||
continue
|
continue
|
||||||
@@ -627,32 +633,100 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
|
|
||||||
self.calculate_stock_value_from_deprecarated_ledgers()
|
self.calculate_stock_value_from_deprecarated_ledgers()
|
||||||
|
|
||||||
def get_incoming_rate_from_bundle(self, serial_no) -> float:
|
def get_serial_no_wise_incoming_rate(self, serial_nos):
|
||||||
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
bundle_child = frappe.qb.DocType("Serial and Batch Entry")
|
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)
|
||||||
|
.select(
|
||||||
|
bundle_child.serial_no,
|
||||||
|
Max(bundle.posting_datetime).as_("max_posting_dt"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(bundle.is_cancelled == 0)
|
||||||
|
& (bundle.docstatus == 1)
|
||||||
|
& (bundle.type_of_transaction == "Inward")
|
||||||
|
& (bundle_child.qty > 0)
|
||||||
|
& (bundle.item_code == self.sle.item_code)
|
||||||
|
& (bundle_child.warehouse == self.sle.warehouse)
|
||||||
|
& (bundle_child.serial_no.isin(serial_nos))
|
||||||
|
)
|
||||||
|
.groupby(bundle_child.serial_no)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
if self.sle.posting_datetime:
|
||||||
|
timestamp_condition = bundle.posting_datetime <= self.sle.posting_datetime
|
||||||
|
|
||||||
|
latest_posting = latest_posting.where(timestamp_condition)
|
||||||
|
|
||||||
|
latest_posting = latest_posting.as_("latest_posting")
|
||||||
|
|
||||||
|
return latest_posting
|
||||||
|
|
||||||
|
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)
|
||||||
|
.join(latest_posting)
|
||||||
|
.on(
|
||||||
|
(latest_posting.serial_no == bundle_child.serial_no)
|
||||||
|
& (latest_posting.max_posting_dt == bundle.posting_datetime)
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
bundle_child.serial_no,
|
||||||
|
Max(bundle.creation).as_("max_creation"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(bundle.is_cancelled == 0)
|
||||||
|
& (bundle.docstatus == 1)
|
||||||
|
& (bundle.type_of_transaction == "Inward")
|
||||||
|
& (bundle_child.qty > 0)
|
||||||
|
& (bundle.item_code == self.sle.item_code)
|
||||||
|
& (bundle_child.warehouse == self.sle.warehouse)
|
||||||
|
)
|
||||||
|
.groupby(bundle_child.serial_no)
|
||||||
|
).as_("latest_creation")
|
||||||
|
|
||||||
|
return latest_creation
|
||||||
|
|
||||||
|
latest_posting = get_latest_based_on_posting_datetime()
|
||||||
|
latest_creation = get_latest_based_on_creation(latest_posting)
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(bundle)
|
frappe.qb.from_(bundle)
|
||||||
.inner_join(bundle_child)
|
.join(bundle_child)
|
||||||
.on(bundle.name == bundle_child.parent)
|
.on(bundle.name == bundle_child.parent)
|
||||||
.select((bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"))
|
.join(latest_creation)
|
||||||
.where(
|
.on(
|
||||||
(bundle.is_cancelled == 0)
|
(latest_creation.serial_no == bundle_child.serial_no)
|
||||||
& (bundle.docstatus == 1)
|
& (latest_creation.max_creation == bundle.creation)
|
||||||
& (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)
|
|
||||||
)
|
)
|
||||||
|
.select(
|
||||||
|
bundle_child.serial_no,
|
||||||
|
(bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"),
|
||||||
|
)
|
||||||
|
<<<<<<< HEAD
|
||||||
.orderby(Timestamp(bundle.posting_date, bundle.posting_time), order=Order.desc)
|
.orderby(Timestamp(bundle.posting_date, bundle.posting_time), order=Order.desc)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
|
=======
|
||||||
|
>>>>>>> 20320c4a6c (perf: SABB taking time to save the record)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Important to exclude the current voucher to calculate correct the stock value difference
|
result = query.run(as_list=1)
|
||||||
if self.sle.voucher_no:
|
|
||||||
query = query.where(bundle.voucher_no != self.sle.voucher_no)
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
if self.sle.posting_date:
|
if self.sle.posting_date:
|
||||||
if self.sle.posting_time is None:
|
if self.sle.posting_time is None:
|
||||||
self.sle.posting_time = nowtime()
|
self.sle.posting_time = nowtime()
|
||||||
@@ -665,6 +739,9 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
|
|
||||||
incoming_rate = query.run()
|
incoming_rate = query.run()
|
||||||
return flt(incoming_rate[0][0]) if incoming_rate else None
|
return flt(incoming_rate[0][0]) if incoming_rate else None
|
||||||
|
=======
|
||||||
|
return frappe._dict(result) if result else frappe._dict({})
|
||||||
|
>>>>>>> 20320c4a6c (perf: SABB taking time to save the record)
|
||||||
|
|
||||||
def get_serial_nos(self):
|
def get_serial_nos(self):
|
||||||
if self.sle.get("serial_nos"):
|
if self.sle.get("serial_nos"):
|
||||||
@@ -1131,6 +1208,9 @@ class SerialBatchCreation:
|
|||||||
|
|
||||||
doc.submit()
|
doc.submit()
|
||||||
else:
|
else:
|
||||||
|
if self.get("ignore_sabb_validation"):
|
||||||
|
doc.flags.ignore_validate = True
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
self.validate_qty(doc)
|
self.validate_qty(doc)
|
||||||
|
|||||||
Reference in New Issue
Block a user