From 5de91cf55e768abfe419e6e8b6f83fa381769cc1 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 19 Jun 2024 14:31:52 +0530 Subject: [PATCH 1/6] refactor: item-wise purchase history (query to script report) --- .../item_wise_purchase_history.js | 69 +++++ .../item_wise_purchase_history.json | 45 +-- .../item_wise_purchase_history.py | 292 ++++++++++++++++++ 3 files changed, 385 insertions(+), 21 deletions(-) create mode 100644 erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js create mode 100644 erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js new file mode 100644 index 00000000000..f4f311cc1dd --- /dev/null +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js @@ -0,0 +1,69 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Item-wise Purchase History"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1, + }, + { + fieldname: "from_date", + reqd: 1, + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_months( + frappe.datetime.get_today(), + -1, + ), + }, + { + fieldname: "to_date", + reqd: 1, + default: frappe.datetime.get_today(), + label: __("To Date"), + fieldtype: "Date", + }, + { + fieldname: "item_group", + label: __("Item Group"), + fieldtype: "Link", + options: "Item Group", + }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + options: "Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.item_query", + }; + }, + }, + { + fieldname: "supplier", + label: __("Supplier"), + fieldtype: "Link", + options: "Supplier", + }, + ], + + formatter: function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + let format_fields = ["received_qty", "billed_amt"]; + + if ( + in_list(format_fields, column.fieldname) && + data && + data[column.fieldname] > 0 + ) { + value = "" + value + ""; + } + return value; + }, +}; diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json index 521c68c5329..e6c3c624e01 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json @@ -1,30 +1,33 @@ { - "add_total_row": 1, - "apply_user_permissions": 1, - "creation": "2013-05-03 14:55:53", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2017-02-24 20:08:57.446613", - "modified_by": "Administrator", - "module": "Buying", - "name": "Item-wise Purchase History", - "owner": "Administrator", - "query": "select\n po_item.item_code as \"Item Code:Link/Item:120\",\n\tpo_item.item_name as \"Item Name::120\",\n po_item.item_group as \"Item Group:Link/Item Group:120\",\n\tpo_item.description as \"Description::150\",\n\tpo_item.qty as \"Qty:Float:100\",\n\tpo_item.uom as \"UOM:Link/UOM:80\",\n\tpo_item.base_rate as \"Rate:Currency:120\",\n\tpo_item.base_amount as \"Amount:Currency:120\",\n\tpo.name as \"Purchase Order:Link/Purchase Order:120\",\n\tpo.transaction_date as \"Transaction Date:Date:140\",\n\tpo.supplier as \"Supplier:Link/Supplier:130\",\n sup.supplier_name as \"Supplier Name::150\",\n\tpo_item.project as \"Project:Link/Project:130\",\n\tifnull(po_item.received_qty, 0) as \"Received Qty:Float:120\",\n\tpo.company as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order` po, `tabPurchase Order Item` po_item, `tabSupplier` sup\nwhere\n\tpo.name = po_item.parent and po.supplier = sup.name and po.docstatus = 1\norder by po.name desc", - "ref_doctype": "Purchase Order", - "report_name": "Item-wise Purchase History", - "report_type": "Query Report", - "roles": [ + "add_total_row": 1, + "columns": [], + "creation": "2013-05-03 14:55:53", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 5, + "is_standard": "Yes", + "letterhead": null, + "modified": "2024-06-19 12:12:15.418799", + "modified_by": "Administrator", + "module": "Buying", + "name": "Item-wise Purchase History", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Purchase Order", + "report_name": "Item-wise Purchase History", + "report_type": "Script Report", +"roles": [ { "role": "Stock User" - }, + }, { "role": "Purchase Manager" - }, + }, { "role": "Purchase User" } - ] + ] } \ No newline at end of file diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py new file mode 100644 index 00000000000..6665c7a8033 --- /dev/null +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py @@ -0,0 +1,292 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import flt +from frappe.utils.nestedset import get_descendants_of + + +def execute(filters=None): + filters = frappe._dict(filters or {}) + if filters.from_date > filters.to_date: + frappe.throw(_("From Date cannot be greater than To Date")) + + columns = get_columns(filters) + data = get_data(filters) + + chart_data = get_chart_data(data) + + return columns, data, None, chart_data + + +def get_columns(filters): + return [ + { + "label": _("Item Code"), + "fieldtype": "Link", + "fieldname": "item_code", + "options": "Item", + "width": 120, + }, + { + "label": _("Item Name"), + "fieldtype": "Data", + "fieldname": "item_name", + "width": 140, + }, + { + "label": _("Item Group"), + "fieldtype": "Link", + "fieldname": "item_group", + "options": "Item Group", + "width": 120, + }, + { + "label": _("Description"), + "fieldtype": "Data", + "fieldname": "description", + "width": 140, + }, + { + "label": _("Quantity"), + "fieldtype": "Float", + "fieldname": "quantity", + "width": 120, + }, + { + "label": _("UOM"), + "fieldtype": "Link", + "fieldname": "uom", + "options": "UOM", + "width": 90, + }, + { + "label": _("Rate"), + "fieldname": "rate", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + }, + { + "label": _("Amount"), + "fieldname": "amount", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + }, + { + "label": _("Purchase Order"), + "fieldtype": "Link", + "fieldname": "purchase_order", + "options": "Purchase Order", + "width": 160, + }, + { + "label": _("Transaction Date"), + "fieldtype": "Date", + "fieldname": "transaction_date", + "width": 110, + }, + { + "label": _("Supplier"), + "fieldtype": "Link", + "fieldname": "supplier", + "options": "Supplier", + "width": 100, + }, + { + "label": _("Supplier Name"), + "fieldtype": "Data", + "fieldname": "supplier_name", + "width": 140, + }, + { + "label": _("Supplier Group"), + "fieldtype": "Link", + "fieldname": "supplier_group", + "options": "Supplier Group", + "width": 120, + }, + { + "label": _("Project"), + "fieldtype": "Link", + "fieldname": "project", + "options": "Project", + "width": 100, + }, + { + "label": _("Received Quantity"), + "fieldtype": "Float", + "fieldname": "received_qty", + "width": 150, + }, + { + "label": _("Billed Amount"), + "fieldtype": "Currency", + "fieldname": "billed_amt", + "options": "currency", + "width": 120, + }, + { + "label": _("Company"), + "fieldtype": "Link", + "fieldname": "company", + "options": "Company", + "width": 100, + }, + { + "label": _("Currency"), + "fieldtype": "Link", + "fieldname": "currency", + "options": "Currency", + "hidden": 1, + }, + ] + + +def get_data(filters): + data = [] + + company_list = get_descendants_of("Company", filters.get("company")) + company_list.append(filters.get("company")) + + supplier_details = get_supplier_details() + item_details = get_item_details() + purchase_order_records = get_purchase_order_details(company_list, filters) + + for record in purchase_order_records: + supplier_record = supplier_details.get(record.supplier) + item_record = item_details.get(record.item_code) + row = { + "item_code": record.get("item_code"), + "item_name": item_record.get("item_name"), + "item_group": item_record.get("item_group"), + "description": record.get("description"), + "quantity": record.get("qty"), + "uom": record.get("uom"), + "rate": record.get("base_rate"), + "amount": record.get("base_amount"), + "purchase_order": record.get("name"), + "transaction_date": record.get("transaction_date"), + "supplier": record.get("supplier"), + "supplier_name": supplier_record.get("supplier_name"), + "supplier_group": supplier_record.get("supplier_group"), + "project": record.get("project"), + "received_qty": flt(record.get("received_qty")), + "billed_amt": flt(record.get("billed_amt")), + "company": record.get("company"), + } + row["currency"] = frappe.get_cached_value( + "Company", row["company"], "default_currency" + ) + data.append(row) + + return data + + +def get_supplier_details(): + details = frappe.get_all( + "Supplier", fields=["name", "supplier_name", "supplier_group"] + ) + supplier_details = {} + for d in details: + supplier_details.setdefault( + d.name, + frappe._dict( + {"supplier_name": d.supplier_name, "supplier_group": d.supplier_group} + ), + ) + return supplier_details + + +def get_item_details(): + details = frappe.db.get_all("Item", fields=["name", "item_name", "item_group"]) + item_details = {} + for d in details: + item_details.setdefault( + d.name, frappe._dict({"item_name": d.item_name, "item_group": d.item_group}) + ) + return item_details + + +def get_purchase_order_details(company_list, filters): + db_po = frappe.qb.DocType("Purchase Order") + db_po_item = frappe.qb.DocType("Purchase Order Item") + + query = ( + frappe.qb.from_(db_po) + .inner_join(db_po_item) + .on(db_po_item.parent == db_po.name) + .select( + db_po.name, + db_po.supplier, + db_po.transaction_date, + db_po.project, + db_po.company, + db_po_item.item_code, + db_po_item.description, + db_po_item.qty, + db_po_item.uom, + db_po_item.base_rate, + db_po_item.base_amount, + db_po_item.received_qty, + (db_po_item.billed_amt * db_po.conversion_rate).as_("billed_amt"), + ) + .where(db_po.docstatus == 1) + .where(db_po.company.isin(tuple(company_list))) + ) + + if filters.get("item_group"): + query = query.where(db_po_item.item_group == filters.item_group) + + if filters.get("from_date"): + query = query.where(db_po.transaction_date >= filters.from_date) + + if filters.get("to_date"): + query = query.where(db_po.transaction_date <= filters.to_date) + + if filters.get("item_code"): + query = query.where(db_po_item.item_code == filters.item_code) + + if filters.get("supplier"): + query = query.where(db_po.supplier == filters.supplier) + + return query.run(as_dict=1) + + +def get_chart_data(data): + item_wise_purchase_map = {} + labels, datapoints = [], [] + + for row in data: + item_key = row.get("item_code") + + if item_key not in item_wise_purchase_map: + item_wise_purchase_map[item_key] = 0 + + item_wise_purchase_map[item_key] = flt(item_wise_purchase_map[item_key]) + flt( + row.get("amount") + ) + + item_wise_purchase_map = { + item: value + for item, value in ( + sorted(item_wise_purchase_map.items(), key=lambda i: i[1], reverse=True) + ) + } + + for key in item_wise_purchase_map: + labels.append(key) + datapoints.append(item_wise_purchase_map[key]) + + return { + "data": { + "labels": labels[:30], # show max of 30 items in chart + "datasets": [ + {"name": _("Total Purchase Amount"), "values": datapoints[:30]} + ], + }, + "type": "bar", + "fieldtype": "Currency", + } From 49331e6109fec93619c1fdf02a12f0c228973d3b Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 19 Jun 2024 14:37:29 +0530 Subject: [PATCH 2/6] refactor: item-wise purchase history (query to script report) --- .../item_wise_purchase_history.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json index e6c3c624e01..35045afcf8b 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.json @@ -1,25 +1,22 @@ { "add_total_row": 1, - "columns": [], "creation": "2013-05-03 14:55:53", + "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", - "filters": [], "idx": 5, "is_standard": "Yes", - "letterhead": null, "modified": "2024-06-19 12:12:15.418799", "modified_by": "Administrator", "module": "Buying", "name": "Item-wise Purchase History", "owner": "Administrator", "prepared_report": 0, - "query": "", "ref_doctype": "Purchase Order", "report_name": "Item-wise Purchase History", "report_type": "Script Report", -"roles": [ + "roles": [ { "role": "Stock User" }, From 003a9608dcecd5f5691c23ff9fb189bf541aea87 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 19 Jun 2024 14:57:02 +0530 Subject: [PATCH 3/6] refactor: item-wise purchase history (query to script report) -- formatter --- .../item_wise_purchase_history.js | 120 +++++++++--------- .../item_wise_purchase_history.py | 28 +--- 2 files changed, 65 insertions(+), 83 deletions(-) diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js index f4f311cc1dd..fd1cbf7bede 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js @@ -2,68 +2,64 @@ // For license information, please see license.txt frappe.query_reports["Item-wise Purchase History"] = { - filters: [ - { - fieldname: "company", - label: __("Company"), - fieldtype: "Link", - options: "Company", - default: frappe.defaults.get_user_default("Company"), - reqd: 1, - }, - { - fieldname: "from_date", - reqd: 1, - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months( - frappe.datetime.get_today(), - -1, - ), - }, - { - fieldname: "to_date", - reqd: 1, - default: frappe.datetime.get_today(), - label: __("To Date"), - fieldtype: "Date", - }, - { - fieldname: "item_group", - label: __("Item Group"), - fieldtype: "Link", - options: "Item Group", - }, - { - fieldname: "item_code", - label: __("Item"), - fieldtype: "Link", - options: "Item", - get_query: () => { - return { - query: "erpnext.controllers.queries.item_query", - }; - }, - }, - { - fieldname: "supplier", - label: __("Supplier"), - fieldtype: "Link", - options: "Supplier", - }, - ], + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1, + }, + { + fieldname: "from_date", + reqd: 1, + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_months( + frappe.datetime.get_today(), + -1, + ), + }, + { + fieldname: "to_date", + reqd: 1, + default: frappe.datetime.get_today(), + label: __("To Date"), + fieldtype: "Date", + }, + { + fieldname: "item_group", + label: __("Item Group"), + fieldtype: "Link", + options: "Item Group", + }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + options: "Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.item_query", + }; + }, + }, + { + fieldname: "supplier", + label: __("Supplier"), + fieldtype: "Link", + options: "Supplier", + }, + ], - formatter: function (value, row, column, data, default_formatter) { - value = default_formatter(value, row, column, data); - let format_fields = ["received_qty", "billed_amt"]; + formatter: function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + let format_fields = ["received_qty", "billed_amt"]; - if ( - in_list(format_fields, column.fieldname) && - data && - data[column.fieldname] > 0 - ) { - value = "" + value + ""; - } - return value; - }, + if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) { + value = "" + value + ""; + } + return value; + }, }; diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py index 6665c7a8033..27aec9c3abe 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py @@ -177,25 +177,19 @@ def get_data(filters): "billed_amt": flt(record.get("billed_amt")), "company": record.get("company"), } - row["currency"] = frappe.get_cached_value( - "Company", row["company"], "default_currency" - ) + row["currency"] = frappe.get_cached_value("Company", row["company"], "default_currency") data.append(row) return data def get_supplier_details(): - details = frappe.get_all( - "Supplier", fields=["name", "supplier_name", "supplier_group"] - ) + details = frappe.get_all("Supplier", fields=["name", "supplier_name", "supplier_group"]) supplier_details = {} for d in details: supplier_details.setdefault( d.name, - frappe._dict( - {"supplier_name": d.supplier_name, "supplier_group": d.supplier_group} - ), + frappe._dict({"supplier_name": d.supplier_name, "supplier_group": d.supplier_group}), ) return supplier_details @@ -204,9 +198,7 @@ def get_item_details(): details = frappe.db.get_all("Item", fields=["name", "item_name", "item_group"]) item_details = {} for d in details: - item_details.setdefault( - d.name, frappe._dict({"item_name": d.item_name, "item_group": d.item_group}) - ) + item_details.setdefault(d.name, frappe._dict({"item_name": d.item_name, "item_group": d.item_group})) return item_details @@ -265,15 +257,11 @@ def get_chart_data(data): if item_key not in item_wise_purchase_map: item_wise_purchase_map[item_key] = 0 - item_wise_purchase_map[item_key] = flt(item_wise_purchase_map[item_key]) + flt( - row.get("amount") - ) + item_wise_purchase_map[item_key] = flt(item_wise_purchase_map[item_key]) + flt(row.get("amount")) item_wise_purchase_map = { item: value - for item, value in ( - sorted(item_wise_purchase_map.items(), key=lambda i: i[1], reverse=True) - ) + for item, value in (sorted(item_wise_purchase_map.items(), key=lambda i: i[1], reverse=True)) } for key in item_wise_purchase_map: @@ -283,9 +271,7 @@ def get_chart_data(data): return { "data": { "labels": labels[:30], # show max of 30 items in chart - "datasets": [ - {"name": _("Total Purchase Amount"), "values": datapoints[:30]} - ], + "datasets": [{"name": _("Total Purchase Amount"), "values": datapoints[:30]}], }, "type": "bar", "fieldtype": "Currency", From f740c943638c5e226e5ef4ee41e434d8ab6ec509 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 19 Jun 2024 14:59:24 +0530 Subject: [PATCH 4/6] refactor: item-wise purchase history (query to script report) --prettier --- .../item_wise_purchase_history/item_wise_purchase_history.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js index fd1cbf7bede..174f2f7cf9d 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js @@ -16,10 +16,7 @@ frappe.query_reports["Item-wise Purchase History"] = { reqd: 1, label: __("From Date"), fieldtype: "Date", - default: frappe.datetime.add_months( - frappe.datetime.get_today(), - -1, - ), + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), }, { fieldname: "to_date", From 7bae18aba81ca9dbe64299a707f6713553d31336 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 19 Jun 2024 15:06:09 +0530 Subject: [PATCH 5/6] refactor: item-wise purchase history (query to script report) --prettier --- .../item_wise_purchase_history/item_wise_purchase_history.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js index 174f2f7cf9d..37870b43b6d 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.js @@ -54,7 +54,7 @@ frappe.query_reports["Item-wise Purchase History"] = { value = default_formatter(value, row, column, data); let format_fields = ["received_qty", "billed_amt"]; - if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) { + if (format_fields.includes(column.fieldname) && data && data[column.fieldname] > 0) { value = "" + value + ""; } return value; From 2851764ed6ba977a0327aa09c32be7a1db27c301 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Wed, 19 Jun 2024 16:09:44 +0530 Subject: [PATCH 6/6] refactor: item-wise purchase history (query to script report) --upd --- .../item_wise_purchase_history.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py index 27aec9c3abe..a8950af3ea3 100644 --- a/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py +++ b/erpnext/buying/report/item_wise_purchase_history/item_wise_purchase_history.py @@ -229,8 +229,9 @@ def get_purchase_order_details(company_list, filters): .where(db_po.company.isin(tuple(company_list))) ) - if filters.get("item_group"): - query = query.where(db_po_item.item_group == filters.item_group) + for field in ("item_code", "item_group"): + if filters.get(field): + query = query.where(db_po_item[field] == filters[field]) if filters.get("from_date"): query = query.where(db_po.transaction_date >= filters.from_date) @@ -238,9 +239,6 @@ def get_purchase_order_details(company_list, filters): if filters.get("to_date"): query = query.where(db_po.transaction_date <= filters.to_date) - if filters.get("item_code"): - query = query.where(db_po_item.item_code == filters.item_code) - if filters.get("supplier"): query = query.where(db_po.supplier == filters.supplier)