From aa17110bde44603574d4532afaa6cf1181423ac5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 17:39:48 +0530 Subject: [PATCH 1/4] perf: optimize total_purchase_cost update --- .../purchase_invoice/purchase_invoice.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d7c2361b4daf..e83525dbac52 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1281,13 +1281,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: From bcbe6c4a531986836a0a2497e05bdbe1a327407a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 17:53:42 +0530 Subject: [PATCH 2/4] refactor: provide UI button to recalculate when needed --- erpnext/projects/doctype/project/project.js | 20 +++++++++++ erpnext/projects/doctype/project/project.py | 37 ++++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index f366f775560e..2dac399d88f7 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -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"); @@ -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({ diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index e9aed1afb4a3..4f2e39539d54 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -4,11 +4,11 @@ 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 import Interval -from frappe.query_builder.functions import Count, CurDate, Date, UnixTimestamp +from frappe.query_builder.functions import Count, CurDate, Date, Sum, UnixTimestamp from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today from frappe.utils.user import is_website_user @@ -249,12 +249,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): @@ -695,3 +690,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), + ) From 0fe6dcd74288d5f2df1ffc37485e22ee96329b9e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Nov 2023 10:53:00 +0530 Subject: [PATCH 3/4] refactor: make update_project_cost optional through Buying Settings --- .../doctype/buying_settings/buying_settings.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 059999245d10..0af93bfc9029 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -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", @@ -172,6 +173,14 @@ "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", @@ -179,7 +188,7 @@ "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", From dd016e6ced432b6f8754042fbad808b1cba56cec Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Nov 2023 10:54:31 +0530 Subject: [PATCH 4/4] refactor: update project costing based on frequency --- .../doctype/purchase_invoice/purchase_invoice.py | 11 +++++++++-- erpnext/patches.txt | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e83525dbac52..c6ae9377a0d7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -527,7 +527,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() @@ -1262,7 +1266,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) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a71d71b83a89..4991b4821618 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -351,5 +351,6 @@ erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_d erpnext.patches.v15_0.set_reserved_stock_in_bin 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