mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-05 05:09:11 +00:00
perf: batch status check for on-hold/closed documents, remove N+1 queries (#54798)
This commit is contained in:
@@ -36,7 +36,6 @@ from erpnext.accounts.party import get_due_date, get_party_account
|
|||||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update_voucher_outstanding
|
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update_voucher_outstanding
|
||||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
|
||||||
from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head
|
from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
@@ -282,7 +281,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.check_conversion_rate()
|
self.check_conversion_rate()
|
||||||
self.validate_credit_to_acc()
|
self.validate_credit_to_acc()
|
||||||
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_for_on_hold_or_closed_status(
|
||||||
|
"Purchase Order", "purchase_order", exclude_if_field="purchase_receipt"
|
||||||
|
)
|
||||||
self.validate_with_previous_doc()
|
self.validate_with_previous_doc()
|
||||||
self.validate_uom_is_integer("uom", "qty")
|
self.validate_uom_is_integer("uom", "qty")
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
@@ -387,14 +388,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
def check_on_hold_or_closed_status(self):
|
|
||||||
check_list = []
|
|
||||||
|
|
||||||
for d in self.get("items"):
|
|
||||||
if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
|
|
||||||
check_list.append(d.purchase_order)
|
|
||||||
check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
|
|
||||||
|
|
||||||
def validate_with_previous_doc(self):
|
def validate_with_previous_doc(self):
|
||||||
super().validate_with_previous_doc(
|
super().validate_with_previous_doc(
|
||||||
{
|
{
|
||||||
@@ -1681,7 +1674,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
PurchaseTaxWithholding(self).on_cancel()
|
PurchaseTaxWithholding(self).on_cancel()
|
||||||
|
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_for_on_hold_or_closed_status(
|
||||||
|
"Purchase Order", "purchase_order", exclude_if_field="purchase_receipt"
|
||||||
|
)
|
||||||
|
|
||||||
if self.is_return and not self.update_billed_amount_in_purchase_order:
|
if self.is_return and not self.update_billed_amount_in_purchase_order:
|
||||||
# NOTE status updating bypassed for is_return
|
# NOTE status updating bypassed for is_return
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
|||||||
validate_inter_company_party,
|
validate_inter_company_party,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
from erpnext.buying.utils import validate_for_items
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
|
from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
|
||||||
validate_against_blanket_order,
|
validate_against_blanket_order,
|
||||||
@@ -201,7 +201,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.validate_supplier()
|
self.validate_supplier()
|
||||||
self.validate_schedule_date()
|
self.validate_schedule_date()
|
||||||
validate_for_items(self)
|
validate_for_items(self)
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_for_on_hold_or_closed_status("Material Request", "material_request")
|
||||||
|
|
||||||
self.validate_uom_is_integer("uom", "qty")
|
self.validate_uom_is_integer("uom", "qty")
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
@@ -380,18 +380,6 @@ class PurchaseOrder(BuyingController):
|
|||||||
d.base_rate
|
d.base_rate
|
||||||
) = d.price_list_rate = d.rate = d.last_purchase_rate = item_last_purchase_rate
|
) = d.price_list_rate = d.rate = d.last_purchase_rate = item_last_purchase_rate
|
||||||
|
|
||||||
# Check for Closed status
|
|
||||||
def check_on_hold_or_closed_status(self):
|
|
||||||
check_list = []
|
|
||||||
for d in self.get("items"):
|
|
||||||
if (
|
|
||||||
d.meta.get_field("material_request")
|
|
||||||
and d.material_request
|
|
||||||
and d.material_request not in check_list
|
|
||||||
):
|
|
||||||
check_list.append(d.material_request)
|
|
||||||
check_on_hold_or_closed_status("Material Request", d.material_request)
|
|
||||||
|
|
||||||
def update_ordered_qty(self, po_item_rows=None):
|
def update_ordered_qty(self, po_item_rows=None):
|
||||||
"""update requested qty (before ordered_qty is updated)"""
|
"""update requested qty (before ordered_qty is updated)"""
|
||||||
item_wh_list = []
|
item_wh_list = []
|
||||||
@@ -473,7 +461,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.set_received_qty_to_zero_for_drop_ship_items()
|
self.set_received_qty_to_zero_for_drop_ship_items()
|
||||||
self.update_receiving_percentage()
|
self.update_receiving_percentage()
|
||||||
|
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_for_on_hold_or_closed_status("Material Request", "material_request")
|
||||||
|
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,14 @@ def check_on_hold_or_closed_status(doctype, docname) -> None:
|
|||||||
status = frappe.db.get_value(doctype, docname, "status")
|
status = frappe.db.get_value(doctype, docname, "status")
|
||||||
|
|
||||||
if status in ("Closed", "On Hold"):
|
if status in ("Closed", "On Hold"):
|
||||||
frappe.throw(_("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError)
|
frappe.throw(
|
||||||
|
_("{0} {1} status is {2}.").format(
|
||||||
|
frappe.bold(_(doctype)),
|
||||||
|
frappe.bold(docname),
|
||||||
|
frappe.bold(_(status)),
|
||||||
|
),
|
||||||
|
frappe.InvalidStatusError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -683,19 +683,6 @@ class BuyingController(SubcontractingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname):
|
|
||||||
for d in self.get("items"):
|
|
||||||
if d.get(ref_fieldname):
|
|
||||||
status = frappe.db.get_value(ref_doctype, d.get(ref_fieldname), "status")
|
|
||||||
if status in ("Closed", "On Hold"):
|
|
||||||
frappe.throw(
|
|
||||||
_("{ref_doctype} {ref_name} is {status}.").format(
|
|
||||||
ref_doctype=frappe.bold(_(ref_doctype)),
|
|
||||||
ref_name=frappe.bold(d.get(ref_fieldname)),
|
|
||||||
status=frappe.bold(_(status)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||||
self.update_ordered_and_reserved_qty()
|
self.update_ordered_and_reserved_qty()
|
||||||
|
|
||||||
|
|||||||
@@ -469,11 +469,9 @@ class SellingController(StockController):
|
|||||||
return so_qty, so_warehouse
|
return so_qty, so_warehouse
|
||||||
|
|
||||||
def check_sales_order_on_hold_or_close(self, ref_fieldname):
|
def check_sales_order_on_hold_or_close(self, ref_fieldname):
|
||||||
for d in self.get("items"):
|
if self.is_return:
|
||||||
if d.get(ref_fieldname):
|
return
|
||||||
status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status")
|
self.check_for_on_hold_or_closed_status("Sales Order", ref_fieldname)
|
||||||
if status in ("Closed", "On Hold") and not self.is_return:
|
|
||||||
frappe.throw(_("Sales Order {0} is {1}").format(d.get(ref_fieldname), status))
|
|
||||||
|
|
||||||
def update_reserved_qty(self):
|
def update_reserved_qty(self):
|
||||||
so_map = {}
|
so_map = {}
|
||||||
|
|||||||
@@ -1937,6 +1937,43 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
qty -= working_qty
|
qty -= working_qty
|
||||||
|
|
||||||
|
def check_for_on_hold_or_closed_status(
|
||||||
|
self, ref_doctype: str, ref_fieldname: str, exclude_if_field: str | None = None
|
||||||
|
) -> None:
|
||||||
|
def _include(d):
|
||||||
|
return d.get(ref_fieldname) and not (exclude_if_field and d.get(exclude_if_field))
|
||||||
|
|
||||||
|
included = [(d, d.get(ref_fieldname)) for d in self.get("items") if _include(d)]
|
||||||
|
if not included:
|
||||||
|
return
|
||||||
|
|
||||||
|
status_map = {
|
||||||
|
r.name: r.status
|
||||||
|
for r in frappe.get_all(
|
||||||
|
ref_doctype,
|
||||||
|
filters={"name": ["in", {name for _, name in included}]},
|
||||||
|
fields=["name", "status"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
seen = set()
|
||||||
|
for _d, ref_name in included:
|
||||||
|
if ref_name in seen:
|
||||||
|
continue
|
||||||
|
seen.add(ref_name)
|
||||||
|
if (status := status_map.get(ref_name)) in ("Closed", "On Hold"):
|
||||||
|
errors.append(
|
||||||
|
_("{ref_doctype} {ref_name} status is {status}.").format(
|
||||||
|
ref_doctype=frappe.bold(_(ref_doctype)),
|
||||||
|
ref_name=frappe.bold(ref_name),
|
||||||
|
status=frappe.bold(_(status)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
frappe.throw("<br>".join(errors), frappe.InvalidStatusError)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def show_accounting_ledger_preview(company: str, doctype: str, docname: str):
|
def show_accounting_ledger_preview(company: str, doctype: str, docname: str):
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from frappe.utils import (
|
|||||||
)
|
)
|
||||||
from pypika import functions as fn
|
from pypika import functions as fn
|
||||||
|
|
||||||
|
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||||
from erpnext.manufacturing.doctype.bom.bom import (
|
from erpnext.manufacturing.doctype.bom.bom import (
|
||||||
get_bom_item_rate,
|
get_bom_item_rate,
|
||||||
get_bom_items_as_dict,
|
get_bom_items_as_dict,
|
||||||
@@ -439,7 +440,7 @@ class WorkOrder(Document):
|
|||||||
production_item = main_item_code
|
production_item = main_item_code
|
||||||
|
|
||||||
if self.sales_order:
|
if self.sales_order:
|
||||||
self.check_sales_order_on_hold_or_close()
|
check_on_hold_or_closed_status("Sales Order", self.sales_order)
|
||||||
|
|
||||||
SalesOrder = frappe.qb.DocType("Sales Order")
|
SalesOrder = frappe.qb.DocType("Sales Order")
|
||||||
SalesOrderItem = frappe.qb.DocType("Sales Order Item")
|
SalesOrderItem = frappe.qb.DocType("Sales Order Item")
|
||||||
@@ -495,11 +496,6 @@ class WorkOrder(Document):
|
|||||||
else:
|
else:
|
||||||
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
|
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
|
||||||
|
|
||||||
def check_sales_order_on_hold_or_close(self):
|
|
||||||
status = frappe.db.get_value("Sales Order", self.sales_order, "status")
|
|
||||||
if status in ("Closed", "On Hold"):
|
|
||||||
frappe.throw(_("Sales Order {0} is {1}").format(self.sales_order, status))
|
|
||||||
|
|
||||||
def set_default_warehouse(self):
|
def set_default_warehouse(self):
|
||||||
if not self.wip_warehouse and not self.skip_transfer:
|
if not self.wip_warehouse and not self.skip_transfer:
|
||||||
self.wip_warehouse = frappe.get_cached_value("Company", self.company, "default_wip_warehouse")
|
self.wip_warehouse = frappe.get_cached_value("Company", self.company, "default_wip_warehouse")
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from pypika import functions as fn
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
|
||||||
from erpnext.controllers.accounts_controller import merge_taxes
|
from erpnext.controllers.accounts_controller import merge_taxes
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
|
||||||
@@ -265,7 +264,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.validate_cwip_accounts()
|
self.validate_cwip_accounts()
|
||||||
self.validate_provisional_expense_account()
|
self.validate_provisional_expense_account()
|
||||||
|
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_for_on_hold_or_closed_status("Purchase Order", "purchase_order")
|
||||||
|
|
||||||
if getdate(self.posting_date) > getdate(nowdate()):
|
if getdate(self.posting_date) > getdate(nowdate()):
|
||||||
throw(_("Posting Date cannot be future date"))
|
throw(_("Posting Date cannot be future date"))
|
||||||
@@ -373,14 +372,6 @@ class PurchaseReceipt(BuyingController):
|
|||||||
po_qty, po_warehouse = frappe.db.get_value("Purchase Order Item", po_detail, ["qty", "warehouse"])
|
po_qty, po_warehouse = frappe.db.get_value("Purchase Order Item", po_detail, ["qty", "warehouse"])
|
||||||
return po_qty, po_warehouse
|
return po_qty, po_warehouse
|
||||||
|
|
||||||
# Check for Closed status
|
|
||||||
def check_on_hold_or_closed_status(self):
|
|
||||||
check_list = []
|
|
||||||
for d in self.get("items"):
|
|
||||||
if d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list:
|
|
||||||
check_list.append(d.purchase_order)
|
|
||||||
check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
|
|
||||||
|
|
||||||
# on submit
|
# on submit
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
super().on_submit()
|
super().on_submit()
|
||||||
@@ -456,7 +447,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
|
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_for_on_hold_or_closed_status("Purchase Order", "purchase_order")
|
||||||
# Check if Purchase Invoice has been submitted against current Purchase Order
|
# Check if Purchase Invoice has been submitted against current Purchase Order
|
||||||
submitted = frappe.db.sql(
|
submitted = frappe.db.sql(
|
||||||
"""select t1.name
|
"""select t1.name
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
|
||||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
@@ -216,9 +215,7 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.create_raw_materials_supplied_or_received()
|
self.create_raw_materials_supplied_or_received()
|
||||||
|
|
||||||
def validate_closed_subcontracting_order(self):
|
def validate_closed_subcontracting_order(self):
|
||||||
for item in self.items:
|
self.check_for_on_hold_or_closed_status("Subcontracting Order", "subcontracting_order")
|
||||||
if item.subcontracting_order:
|
|
||||||
check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order)
|
|
||||||
|
|
||||||
def update_job_card(self):
|
def update_job_card(self):
|
||||||
for row in self.get("items"):
|
for row in self.get("items"):
|
||||||
|
|||||||
Reference in New Issue
Block a user