Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: optimize update_purchase_cost method (backport #38298) #38318

Merged
merged 5 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,11 @@ def on_submit(self):
if self.update_stock == 1:
self.repost_future_sle_and_gle()

self.update_project()
if (
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
self.update_project()

update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.update_advance_tax_references()

Expand Down Expand Up @@ -1269,7 +1273,10 @@ def on_cancel(self):
if self.update_stock == 1:
self.repost_future_sle_and_gle()

self.update_project()
if (
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
):
self.update_project()
self.db_set("status", "Cancelled")

unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
Expand All @@ -1287,13 +1294,21 @@ def on_cancel(self):
self.update_advance_tax_references(cancel=1)

def update_project(self):
project_list = []
projects = frappe._dict()
for d in self.items:
if d.project and d.project not in project_list:
project = frappe.get_doc("Project", d.project)
project.update_purchase_costing()
project.db_update()
project_list.append(d.project)
if d.project:
if self.docstatus == 1:
projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
elif self.docstatus == 2:
projects[d.project] = projects.get(d.project, 0) - d.base_net_amount

pj = frappe.qb.DocType("Project")
for proj, value in projects.items():
res = (
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
)
current_purchase_cost = res and res[0][0] or 0
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)

def validate_supplier_invoice(self):
if self.bill_date:
Expand Down
11 changes: 10 additions & 1 deletion erpnext/buying/doctype/buying_settings/buying_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"po_required",
"pr_required",
"blanket_order_allowance",
"project_update_frequency",
"column_break_12",
"maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
Expand Down Expand Up @@ -172,14 +173,22 @@
"fieldname": "blanket_order_allowance",
"fieldtype": "Float",
"label": "Blanket Order Allowance (%)"
},
{
"default": "Each Transaction",
"description": "How often should Project be updated of Total Purchase Cost ?",
"fieldname": "project_update_frequency",
"fieldtype": "Select",
"label": "Update frequency of Project",
"options": "Each Transaction\nManual"
}
],
"icon": "fa fa-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-10-25 14:03:32.520418",
"modified": "2023-11-24 10:55:51.287327",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
Expand Down
1 change: 1 addition & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -351,5 +351,6 @@ erpnext.patches.v14_0.rename_depreciation_amount_based_on_num_days_in_month_to_d
erpnext.patches.v14_0.add_default_for_repost_settings
erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
20 changes: 20 additions & 0 deletions erpnext/projects/doctype/project/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ frappe.ui.form.on("Project", {
frm.events.create_duplicate(frm);
}, __("Actions"));

frm.add_custom_button(__('Update Total Purchase Cost'), () => {
frm.events.update_total_purchase_cost(frm);
}, __("Actions"));

frm.trigger("set_project_status_button");


Expand All @@ -92,6 +96,22 @@ frappe.ui.form.on("Project", {

},

update_total_purchase_cost: function(frm) {
frappe.call({
method: "erpnext.projects.doctype.project.project.recalculate_project_total_purchase_cost",
args: {project: frm.doc.name},
freeze: true,
freeze_message: __('Recalculating Purchase Cost against this Project...'),
callback: function(r) {
if (r && !r.exc) {
frappe.msgprint(__('Total Purchase Cost has been updated'));
frm.refresh();
}
}

});
},

set_project_status_button: function(frm) {
frm.add_custom_button(__('Set Project Status'), () => {
let d = new frappe.ui.Dialog({
Expand Down
36 changes: 29 additions & 7 deletions erpnext/projects/doctype/project/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

import frappe
from email_reply_parser import EmailReplyParser
from frappe import _
from frappe import _, qb
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today

from erpnext import get_default_company
Expand Down Expand Up @@ -246,12 +247,7 @@ def calculate_gross_margin(self):
self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100

def update_purchase_costing(self):
total_purchase_cost = frappe.db.sql(
"""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = %s and docstatus=1""",
self.name,
)

total_purchase_cost = calculate_total_purchase_cost(self.name)
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0

def update_sales_amount(self):
Expand Down Expand Up @@ -671,3 +667,29 @@ def get_holiday_list(company=None):

def get_users_email(doc):
return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]


def calculate_total_purchase_cost(project: str | None = None):
if project:
pitem = qb.DocType("Purchase Invoice Item")
frappe.qb.DocType("Purchase Invoice Item")
total_purchase_cost = (
qb.from_(pitem)
.select(Sum(pitem.base_net_amount))
.where((pitem.project == project) & (pitem.docstatus == 1))
.run(as_list=True)
)
return total_purchase_cost
return None


@frappe.whitelist()
def recalculate_project_total_purchase_cost(project: str | None = None):
if project:
total_purchase_cost = calculate_total_purchase_cost(project)
frappe.db.set_value(
"Project",
project,
"total_purchase_cost",
(total_purchase_cost and total_purchase_cost[0][0] or 0),
)
Loading