diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py new file mode 100644 index 00000000000..0e2f78f3479 --- /dev/null +++ b/erpnext/buying/dashboard_fixtures.py @@ -0,0 +1,207 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +import json +from frappe import _ +from frappe.utils import nowdate +from erpnext.accounts.utils import get_fiscal_year + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts(), + "number_cards": get_number_cards(), + }) + +def get_company_for_dashboards(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company_list = frappe.get_list("Company") + if company_list: + return company_list[0].name + return None + +company = frappe.get_doc("Company", get_company_for_dashboards()) +fiscal_year = get_fiscal_year(nowdate(), as_dict=1) +fiscal_year_name = fiscal_year.get("name") +start_date = str(fiscal_year.get("year_start_date")) +end_date = str(fiscal_year.get("year_end_date")) + +def get_dashboards(): + return [{ + "name": "Buying", + "dashboard_name": "Buying", + "charts": [ + { "chart": "Purchase Order Trends", "width": "Full"}, + { "chart": "Material Request Analysis", "width": "Half"}, + { "chart": "Purchase Order Analysis", "width": "Half"}, + { "chart": "Top Suppliers", "width": "Full"} + ], + "cards": [ + { "card": "This Year Purchases"}, + { "card": "Purchase Orders to Receive"}, + { "card": "Purchase Orders to Bill"}, + { "card": "Active Suppliers"} + ] + }] + +def get_charts(): + return [ + { + "name": "Purchase Order Analysis", + "chart_name": _("Purchase Order Analysis"), + "chart_type": "Report", + "custom_options": json.dumps({ + "type": "donut", + "height": 300, + "axisOptions": {"shortenYAxisNumbers": 1} + }), + "doctype": "Dashboard Chart", + "filters_json": json.dumps({ + "company": company.name, + "from_date": start_date, + "to_date": end_date + }), + "is_custom": 1, + "is_public": 1, + "owner": "Administrator", + "report_name": "Purchase Order Analysis", + "type": "Donut" + }, + { + "name": "Material Request Analysis", + "chart_name": _("Material Request Analysis"), + "chart_type": "Group By", + "custom_options": json.dumps({"height": 300}), + "doctype": "Dashboard Chart", + "document_type": "Material Request", + "filters_json": json.dumps( + [["Material Request", "status", "not in", ["Draft", "Cancelled", "Stopped", None], False], + ["Material Request", "material_request_type", "=", "Purchase", False], + ["Material Request", "company", "=", company.name, False], + ["Material Request", "docstatus", "=", 1, False], + ["Material Request", "transaction_date", "Between", [start_date, end_date], False]] + ), + "group_by_based_on": "status", + "group_by_type": "Count", + "is_custom": 0, + "is_public": 1, + "number_of_groups": 0, + "owner": "Administrator", + "type": "Donut" + }, + { + "name": "Purchase Order Trends", + "chart_name": _("Purchase Order Trends"), + "chart_type": "Report", + "custom_options": json.dumps({ + "type": "line", + "axisOptions": {"shortenYAxisNumbers": 1}, + "tooltipOptions": {}, + "lineOptions": { + "regionFill": 1 + } + }), + "doctype": "Dashboard Chart", + "filters_json": json.dumps({ + "company": company.name, + "period": "Monthly", + "fiscal_year": fiscal_year_name, + "period_based_on": "posting_date", + "based_on": "Item" + }), + "is_custom": 1, + "is_public": 1, + "owner": "Administrator", + "report_name": "Purchase Order Trends", + "type": "Line" + }, + { + "name": "Top Suppliers", + "chart_name": _("Top Suppliers"), + "chart_type": "Report", + "doctype": "Dashboard Chart", + "filters_json": json.dumps({ + "company": company.name, + "period": "Monthly", + "fiscal_year": fiscal_year_name, + "period_based_on": "posting_date", + "based_on": "Supplier" + }), + "is_custom": 1, + "is_public": 1, + "owner": "Administrator", + "report_name": "Purchase Receipt Trends", + "type": "Bar" + } + ] + +def get_number_cards(): + return [ + { + "name": "This Year Purchases", + "aggregate_function_based_on": "base_net_total", + "doctype": "Number Card", + "document_type": "Purchase Order", + "filters_json": json.dumps([ + ["Purchase Order", "transaction_date", "Between", [start_date, end_date], False], + ["Purchase Order", "status", "not in", ["Draft", "Cancelled", "Closed", None], False], + ["Purchase Order", "docstatus", "=", 1, False], + ["Purchase Order", "company", "=", company.name, False], + ["Purchase Order", "transaction_date", "Between", [start_date,end_date], False] + ]), + "function": "Sum", + "is_public": 1, + "label": _("This Year Purchases"), + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly" + }, + { + "name": "Purchase Orders to Receive", + "doctype": "Number Card", + "document_type": "Purchase Order", + "filters_json": json.dumps([ + ["Purchase Order", "status", "in", ["To Receive and Bill", "To Receive", None], False], + ["Purchase Order", "docstatus", "=", 1, False], + ["Purchase Order", "company", "=", company.name, False] + ]), + "function": "Count", + "is_public": 1, + "label": _("Purchase Orders to Receive"), + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly" + }, + { + "name": "Purchase Orders to Bill", + "doctype": "Number Card", + "document_type": "Purchase Order", + "filters_json": json.dumps([ + ["Purchase Order", "status", "in", ["To Receive and Bill", "To Bill", None], False], + ["Purchase Order", "docstatus", "=", 1, False], + ["Purchase Order", "company", "=", company.name, False] + ]), + "function": "Count", + "is_public": 1, + "label": _("Purchase Orders to Bill"), + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Weekly" + }, + { + "name": "Active Suppliers", + "doctype": "Number Card", + "document_type": "Supplier", + "filters_json": json.dumps([["Supplier", "disabled", "=", "0"]]), + "function": "Count", + "is_public": 1, + "label": "Active Suppliers", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly" + } + ] \ No newline at end of file diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json index 5e764cf8bbe..ee18545fd44 100644 --- a/erpnext/buying/desk_page/buying/buying.json +++ b/erpnext/buying/desk_page/buying/buying.json @@ -2,24 +2,24 @@ "cards": [ { "hidden": 0, - "label": "Supplier", - "links": "[\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier Group master.\",\n \"label\": \"Supplier Group\",\n \"name\": \"Supplier Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n }\n]" + "label": "Buying", + "links": "[ \n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Request for purchase.\",\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Purchase Orders given to Suppliers.\",\n \"label\": \"Purchase Order\",\n \"name\": \"Purchase Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Request for quotation.\",\n \"label\": \"Request for Quotation\",\n \"name\": \"Request for Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Quotations received from Suppliers.\",\n \"label\": \"Supplier Quotation\",\n \"name\": \"Supplier Quotation\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "label": "Purchasing", - "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Purchase Orders given to Suppliers.\",\n \"label\": \"Purchase Order\",\n \"name\": \"Purchase Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Request for purchase.\",\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Request for quotation.\",\n \"label\": \"Request for Quotation\",\n \"name\": \"Request for Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Quotations received from Suppliers.\",\n \"label\": \"Supplier Quotation\",\n \"name\": \"Supplier Quotation\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Items and Pricing", - "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n }\n]" + "label": "Items & Pricing", + "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, "label": "Settings", "links": "[\n {\n \"description\": \"Default settings for buying transactions.\",\n \"label\": \"Buying Settings\",\n \"name\": \"Buying Settings\",\n \"settings\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for buying transactions.\",\n \"label\": \"Purchase Taxes and Charges Template\",\n \"name\": \"Purchase Taxes and Charges Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"type\": \"doctype\"\n }\n]" }, + { + "hidden": 0, + "label": "Supplier", + "links": "[\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier Group master.\",\n \"label\": \"Supplier Group\",\n \"name\": \"Supplier Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n }\n]" + }, { "hidden": 0, "label": "Supplier Scorecard", @@ -28,67 +28,81 @@ { "hidden": 0, "label": "Key Reports", - "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Analytics\",\n \"name\": \"Purchase Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier-Wise Sales Analytics\",\n \"name\": \"Supplier-Wise Sales Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Stock Ledger Entry\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Trends\",\n \"name\": \"Purchase Order Trends\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Procurement Tracker\",\n \"name\": \"Procurement Tracker\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Requested Items To Be Ordered\",\n \"name\": \"Requested Items To Be Ordered\",\n \"onboard\": 1,\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Analytics\",\n \"name\": \"Purchase Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier-Wise Sales Analytics\",\n \"name\": \"Supplier-Wise Sales Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Stock Ledger Entry\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Requested Items to Order\",\n \"name\": \"Requested Items to Order\",\n \"onboard\": 1,\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Trends\",\n \"name\": \"Purchase Order Trends\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Procurement Tracker\",\n \"name\": \"Procurement Tracker\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, "label": "Other Reports", - "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" } ], + "cards_label": "Masters & Reports ", "category": "Modules", "charts": [ { - "chart_name": "Expenses", - "label": "Expenses" + "chart_name": "Purchase Order Trends", + "label": "Purchase Order Trends" } ], + "charts_label": "Buying Dashboard", "creation": "2020-01-28 11:50:26.195467", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, - "icon": "", + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Buying", - "modified": "2020-04-01 11:28:51.192097", + "modified": "2020-05-19 19:44:36.260982", "modified_by": "Administrator", "module": "Buying", "name": "Buying", + "onboarding": "Buying", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 0, "shortcuts": [ { - "format": "{} Unpaid", - "label": "Purchase Invoice", - "link_to": "Purchase Invoice", - "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Unpaid\"\n}", + "color": "#cef6d1", + "format": "{} available", + "label": "Item", + "link_to": "Item", + "stats_filter": "{\n \"disabled\": 0\n}", "type": "DocType" }, { - "format": "{} to receive", + "color": "#ffe8cd", + "format": "{} Pending", + "label": "Material Request", + "link_to": "Material Request", + "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}", + "type": "DocType" + }, + { + "color": "#ffe8cd", + "format": "{} to Receive", "label": "Purchase Order", "link_to": "Purchase Order", - "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Receive\"\n}", + "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Receive\", \"To Receive and Bill\"]]\n}", "type": "DocType" }, { - "label": "Supplier Quotation", - "link_to": "Supplier Quotation", - "type": "DocType" - }, - { - "label": "Accounts Payable", - "link_to": "Accounts Payable", + "label": "Purchase Analytics", + "link_to": "Purchase Analytics", "type": "Report" }, { - "label": "Purchase Register", - "link_to": "Purchase Register", + "label": "Purchase Order Analysis", + "link_to": "Purchase Order Analysis", "type": "Report" + }, + { + "label": "Buying Dashboard", + "link_to": "Buying", + "type": "Dashboard" } - ] + ], + "shortcuts_label": "Quick Access" } \ No newline at end of file diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js index 403b1c95e74..a27950a9414 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.js +++ b/erpnext/buying/doctype/buying_settings/buying_settings.js @@ -6,3 +6,26 @@ frappe.ui.form.on('Buying Settings', { // } }); + +frappe.tour['Buying Settings'] = [ + { + fieldname: "supp_master_name", + title: "Supplier Naming By", + description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set ") + "Naming Series" + __(" choose the 'Naming Series' option."), + }, + { + fieldname: "buying_price_list", + title: "Default Buying Price List", + description: __("Configure the default Price List when creating a new Buying transaction, the default is set as 'Standard Buying'. Item prices will be fetched from this Price List.") + }, + { + fieldname: "po_required", + title: "Purchase Order Required for Purchase Invoice & Receipt Creation", + description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice or Receipt without creating a Purchase Order first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Order' checkbox in supplier master.") + }, + { + fieldname: "pr_required", + title: "Purchase Receipt Required for Purchase Invoice Creation", + description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in supplier master.") + } +]; \ No newline at end of file diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index a492519591b..a0ab2a00f99 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -1,8 +1,10 @@ { + "actions": [], "creation": "2013-06-25 11:04:03", "description": "Settings for Buying Module", "doctype": "DocType", "document_type": "Other", + "engine": "InnoDB", "field_order": [ "supp_master_name", "supplier_group", @@ -44,13 +46,13 @@ { "fieldname": "po_required", "fieldtype": "Select", - "label": "Purchase Order Required", + "label": "Purchase Order Required for Purchase Invoice & Receipt Creation", "options": "No\nYes" }, { "fieldname": "pr_required", "fieldtype": "Select", - "label": "Purchase Receipt Required", + "label": "Purchase Receipt Required for Purchase Invoice Creation", "options": "No\nYes" }, { @@ -92,7 +94,8 @@ "icon": "fa fa-cog", "idx": 1, "issingle": 1, - "modified": "2019-08-20 13:13:09.055189", + "links": [], + "modified": "2020-05-15 14:49:32.513611", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", @@ -107,5 +110,7 @@ "share": 1, "write": 1 } - ] + ], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 3bc441af6d1..7db1516ce1b 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -761,7 +761,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nStopped\nCancelled", + "options": "\nDraft\nSubmitted\nStopped\nCancelled\nExpired", "print_hide": 1, "read_only": 1, "reqd": 1, @@ -803,7 +803,7 @@ "idx": 29, "is_submittable": 1, "links": [], - "modified": "2020-04-15 11:44:52.958022", + "modified": "2020-05-15 21:24:12.639482", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json new file mode 100644 index 00000000000..8c798b34731 --- /dev/null +++ b/erpnext/buying/module_onboarding/buying/buying.json @@ -0,0 +1,54 @@ +{ + "allow_roles": [ + { + "role": "Purchase Manager" + }, + { + "role": "Purchase User" + }, + { + "role": "Stock Manager" + }, + { + "role": "Stock User" + } + ], + "creation": "2020-05-06 15:56:35.049205", + "docstatus": 0, + "doctype": "Module Onboarding", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying", + "idx": 0, + "is_complete": 0, + "modified": "2020-05-19 20:03:55.776080", + "modified_by": "Administrator", + "module": "Buying", + "name": "Buying", + "owner": "Administrator", + "steps": [ + { + "step": "Introduction to Buying" + }, + { + "step": "Create a Supplier" + }, + { + "step": "Setup your Warehouse" + }, + { + "step": "Create a Product" + }, + { + "step": "Create a Material Request" + }, + { + "step": "Create your first Purchase Order" + }, + { + "step": "Buying Settings" + } + ], + "subtitle": "Products, Purchases, Analysis and more.", + "success_message": "The Buying Module is all set up!", + "title": "Let's Setup the Buying Module.", + "user_can_dismiss": 1 +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/buying_settings/buying_settings.json b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json new file mode 100644 index 00000000000..a788ccd4cc9 --- /dev/null +++ b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json @@ -0,0 +1,19 @@ +{ + "action": "Update Settings", + "creation": "2020-05-06 15:53:44.667414", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-12 18:30:06.323797", + "modified_by": "Administrator", + "name": "Buying Settings", + "owner": "Administrator", + "reference_document": "Buying Settings", + "show_full_form": 0, + "title": "Configure Buying Settings.", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json new file mode 100644 index 00000000000..9dc493dd499 --- /dev/null +++ b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-15 14:39:09.818764", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 1, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-15 14:39:09.818764", + "modified_by": "Administrator", + "name": "Create a Material Request", + "owner": "Administrator", + "reference_document": "Material Request", + "show_full_form": 1, + "title": "Create a Material Request", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/create_a_product/create_a_product.json b/erpnext/buying/onboarding_step/create_a_product/create_a_product.json new file mode 100644 index 00000000000..d2068e167b7 --- /dev/null +++ b/erpnext/buying/onboarding_step/create_a_product/create_a_product.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-12 18:16:06.624554", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-12 18:30:02.489949", + "modified_by": "Administrator", + "name": "Create a Product", + "owner": "Administrator", + "reference_document": "Item", + "show_full_form": 0, + "title": "Create a Product", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json new file mode 100644 index 00000000000..7a64224bd43 --- /dev/null +++ b/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 22:09:10.043554", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 22:09:10.043554", + "modified_by": "Administrator", + "name": "Create a Supplier", + "owner": "Administrator", + "reference_document": "Supplier", + "show_full_form": 0, + "title": "Create a Supplier", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json new file mode 100644 index 00000000000..9dbed239789 --- /dev/null +++ b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-12 18:17:49.976035", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-12 18:31:56.856112", + "modified_by": "Administrator", + "name": "Create your first Purchase Order", + "owner": "Administrator", + "reference_document": "Purchase Order", + "show_full_form": 0, + "title": "Create your first Purchase Order", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json new file mode 100644 index 00000000000..fd98fddafae --- /dev/null +++ b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json @@ -0,0 +1,19 @@ +{ + "action": "Watch Video", + "creation": "2020-05-06 15:37:09.477765", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-12 18:25:08.509900", + "modified_by": "Administrator", + "name": "Introduction to Buying", + "owner": "Administrator", + "show_full_form": 0, + "title": "Introduction to Buying", + "validate_action": 1, + "video_url": "https://youtu.be/efFajTTQBa8" +} \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json new file mode 100644 index 00000000000..557c905bd6c --- /dev/null +++ b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json @@ -0,0 +1,20 @@ +{ + "action": "Go to Page", + "creation": "2020-05-19 18:54:19.383397", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-19 18:54:19.383397", + "modified_by": "Administrator", + "name": "Setup your Warehouse", + "owner": "Administrator", + "path": "Tree/Warehouse", + "reference_document": "Warehouse", + "show_full_form": 0, + "title": "Setup your Warehouse", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/buying/report/purchase_order_analysis/__init__.py b/erpnext/buying/report/purchase_order_analysis/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js new file mode 100644 index 00000000000..701da4380aa --- /dev/null +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -0,0 +1,78 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Purchase Order Analysis"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "width": "80", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_default("company") + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "width": "80", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "width": "80", + "reqd": 1, + "default": frappe.datetime.get_today() + }, + { + "fieldname": "purchase_order", + "label": __("Purchase Order"), + "fieldtype": "Link", + "width": "80", + "options": "Purchase Order", + "get_query": () =>{ + return { + filters: { "docstatus": 1 } + } + } + }, + { + "fieldname": "status", + "label": __("Status"), + "fieldtype": "MultiSelectList", + "width": "80", + get_data: function(txt) { + let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"] + let options = [] + for (let option of status){ + options.push({ + "value": option, + "description": "" + }) + } + return options + } + }, + { + "fieldname": "group_by_po", + "label": __("Group by Purchase Order"), + "fieldtype": "Check", + "default": 0 + } + ], + + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + let format_fields = ["received_qty", "billed_amount"]; + + if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) { + value = "" + value + ""; + } + return value; + } +}; diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json new file mode 100644 index 00000000000..5ba3101ec51 --- /dev/null +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 1, + "creation": "2020-05-04 18:41:28.625119", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-05-15 20:57:52.623455", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order Analysis", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Purchase Order", + "report_name": "Purchase Order Analysis", + "report_type": "Script Report", + "roles": [ + { + "role": "Purchase Manager" + }, + { + "role": "Purchase User" + }, + { + "role": "Stock User" + }, + { + "role": "Supplier" + } + ] +} \ No newline at end of file diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py new file mode 100644 index 00000000000..89be62231b9 --- /dev/null +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -0,0 +1,271 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import copy +from frappe import _ +from frappe.utils import flt, date_diff, getdate + +def execute(filters=None): + if not filters: + return [], [] + + validate_filters(filters) + + columns = get_columns(filters) + conditions = get_conditions(filters) + + data = get_data(conditions, filters) + + if not data: + return [], [], None, [] + + data, chart_data = prepare_data(data, filters) + + return columns, data, None, chart_data + +def validate_filters(filters): + from_date, to_date = filters.get("from_date"), filters.get("to_date") + + if not from_date and to_date: + frappe.throw(_("From and To Dates are required.")) + elif date_diff(to_date, from_date) < 0: + frappe.throw(_("To Date cannot be before From Date.")) + +def get_conditions(filters): + conditions = "" + if filters.get("from_date") and filters.get("to_date"): + conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" + + if filters.get("company"): + conditions += " and po.company = %(company)s" + + if filters.get("purchase_order"): + conditions += " and po.name = %(purchase_order)s" + + if filters.get("status"): + conditions += " and po.status in %(status)s" + + return conditions + +def get_data(conditions, filters): + data = frappe.db.sql(""" + SELECT + po.transaction_date as date, + poi.schedule_date as required_date, + po.name as purchase_order, + po.status, po.supplier, poi.item_code, + poi.qty, poi.received_qty, + (poi.qty - poi.received_qty) AS pending_qty, + IFNULL(pii.qty, 0) as billed_qty, + poi.base_amount as amount, + (poi.received_qty * poi.base_rate) as received_qty_amount, + (poi.billed_amt * IFNULL(po.conversion_rate, 1)) as billed_amount, + (poi.base_amount - (poi.billed_amt * IFNULL(po.conversion_rate, 1))) as pending_amount, + po.set_warehouse as warehouse, + po.company, poi.name + FROM + `tabPurchase Order` po, + `tabPurchase Order Item` poi + LEFT JOIN `tabPurchase Invoice Item` pii + ON pii.po_detail = poi.name + WHERE + poi.parent = po.name + and po.status not in ('Stopped', 'Closed') + and po.docstatus = 1 + {0} + GROUP BY poi.name + ORDER BY po.transaction_date ASC + """.format(conditions), filters, as_dict=1) + + return data + +def prepare_data(data, filters): + completed, pending = 0, 0 + pending_field = "pending_amount" + completed_field = "billed_amount" + + if filters.get("group_by_po"): + purchase_order_map = {} + + for row in data: + # sum data for chart + completed += row[completed_field] + pending += row[pending_field] + + # prepare data for report view + row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"]) + + if filters.get("group_by_po"): + po_name = row["purchase_order"] + + if not po_name in purchase_order_map: + # create an entry + row_copy = copy.deepcopy(row) + purchase_order_map[po_name] = row_copy + else: + # update existing entry + po_row = purchase_order_map[po_name] + po_row["required_date"] = min(getdate(po_row["required_date"]), getdate(row["required_date"])) + + # sum numeric columns + fields = ["qty", "received_qty", "pending_qty", "billed_qty", "qty_to_bill", "amount", + "received_qty_amount", "billed_amount", "pending_amount"] + for field in fields: + po_row[field] = flt(row[field]) + flt(po_row[field]) + + chart_data = prepare_chart_data(pending, completed) + + if filters.get("group_by_po"): + data = [] + for po in purchase_order_map: + data.append(purchase_order_map[po]) + return data, chart_data + + return data, chart_data + +def prepare_chart_data(pending, completed): + labels = ["Amount to Bill", "Billed Amount"] + + return { + "data" : { + "labels": labels, + "datasets": [ + {"values": [pending, completed]} + ] + }, + "type": 'donut', + "height": 300 + } + +def get_columns(filters): + columns = [ + { + "label":_("Date"), + "fieldname": "date", + "fieldtype": "Date", + "width": 90 + }, + { + "label":_("Required By"), + "fieldname": "required_date", + "fieldtype": "Date", + "width": 90 + }, + { + "label": _("Purchase Order"), + "fieldname": "purchase_order", + "fieldtype": "Link", + "options": "Purchase Order", + "width": 160 + }, + { + "label":_("Status"), + "fieldname": "status", + "fieldtype": "Data", + "width": 130 + }, + { + "label": _("Supplier"), + "fieldname": "supplier", + "fieldtype": "Link", + "options": "Supplier", + "width": 130 + }] + + if not filters.get("group_by_po"): + columns.append({ + "label":_("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 100 + }) + + columns.extend([ + { + "label": _("Qty"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, + { + "label": _("Received Qty"), + "fieldname": "received_qty", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, + { + "label": _("Pending Qty"), + "fieldname": "pending_qty", + "fieldtype": "Float", + "width": 80, + "convertible": "qty" + }, + { + "label": _("Billed Qty"), + "fieldname": "billed_qty", + "fieldtype": "Float", + "width": 80, + "convertible": "qty" + }, + { + "label": _("Qty to Bill"), + "fieldname": "qty_to_bill", + "fieldtype": "Float", + "width": 80, + "convertible": "qty" + }, + { + "label": _("Amount"), + "fieldname": "amount", + "fieldtype": "Currency", + "width": 110, + "options": "Company:company:default_currency", + "convertible": "rate" + }, + { + "label": _("Billed Amount"), + "fieldname": "billed_amount", + "fieldtype": "Currency", + "width": 110, + "options": "Company:company:default_currency", + "convertible": "rate" + }, + { + "label": _("Pending Amount"), + "fieldname": "pending_amount", + "fieldtype": "Currency", + "width": 130, + "options": "Company:company:default_currency", + "convertible": "rate" + }, + { + "label": _("Received Qty Amount"), + "fieldname": "received_qty_amount", + "fieldtype": "Currency", + "width": 130, + "options": "Company:company:default_currency", + "convertible": "rate" + }, + { + "label": _("Warehouse"), + "fieldname": "warehouse", + "fieldtype": "Link", + "options": "Warehouse", + "width": 100 + }, + { + "label": _("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "width": 100 + } + ]) + + return columns + diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py index 888676cf64f..1ed6cad6b46 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe import _ from erpnext.controllers.trends import get_columns,get_data def execute(filters=None): @@ -10,5 +11,48 @@ def execute(filters=None): data = [] conditions = get_columns(filters, "Purchase Order") data = get_data(filters, conditions) + chart_data = get_chart_data(data, conditions, filters) - return conditions["columns"], data \ No newline at end of file + return conditions["columns"], data, None, chart_data + +def get_chart_data(data, conditions, filters): + if not (data and conditions): + return [] + + datapoints = [] + + start = 2 if filters.get("based_on") in ["Item", "Supplier"] else 1 + if filters.get("group_by"): + start += 1 + + # fetch only periodic columns as labels + columns = conditions.get("columns")[start:-2][1::2] + labels = [column.split(':')[0] for column in columns] + datapoints = [0] * len(labels) + + for row in data: + # If group by filter, don't add first row of group (it's already summed) + if not row[start-1]: + continue + # Remove None values and compute only periodic data + row = [x if x else 0 for x in row[start:-2]] + row = row[1::2] + + for i in range(len(row)): + datapoints[i] += row[i] + + return { + "data" : { + "labels" : labels, + "datasets" : [ + { + "name" : _("{0}").format(filters.get("period")) + _(" Purchase Value"), + "values" : datapoints + } + ] + }, + "type" : "line", + "lineOptions": { + "regionFill": 1 + } + } \ No newline at end of file diff --git a/erpnext/buying/report/requested_items_to_order/__init__.py b/erpnext/buying/report/requested_items_to_order/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js new file mode 100644 index 00000000000..21adb135476 --- /dev/null +++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js @@ -0,0 +1,64 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Requested Items to Order"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "width": "80", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_default("company") + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "width": "80", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "width": "80", + "reqd": 1, + "default": frappe.datetime.get_today() + }, + { + "fieldname": "material_request", + "label": __("Material Request"), + "fieldtype": "Link", + "width": "80", + "options": "Material Request", + "get_query": () =>{ + return { + filters: { + "docstatus": 1, + "material_request_type": "Purchase", + "per_received": ["<", 100] + } + } + } + }, + { + "fieldname": "group_by_mr", + "label": __("Group by Material Request"), + "fieldtype": "Check", + "default": 0 + } + ], + + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "ordered_qty" && data && data.ordered_qty > 0) { + value = "" + value + ""; + } + return value; + } +}; diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json new file mode 100644 index 00000000000..4a0578be4bf --- /dev/null +++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json @@ -0,0 +1,34 @@ +{ + "add_total_row": 1, + "creation": "2020-05-04 20:23:57.750719", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-05-05 13:05:51.723951", + "modified_by": "Administrator", + "module": "Buying", + "name": "Requested Items to Order", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Material Request", + "report_name": "Requested Items to Order", + "report_type": "Script Report", + "roles": [ + { + "role": "Purchase Manager" + }, + { + "role": "Stock Manager" + }, + { + "role": "Stock User" + }, + { + "role": "Purchase User" + } + ] +} \ No newline at end of file diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py new file mode 100644 index 00000000000..a021d3c1cab --- /dev/null +++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py @@ -0,0 +1,211 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import copy +from frappe import _ +from frappe.utils import flt, date_diff, getdate + +def execute(filters=None): + if not filters: + return [],[] + + validate_filters(filters) + + columns = get_columns(filters) + conditions = get_conditions(filters) + + #get queried data + data = get_data(filters, conditions) + + #prepare data for report and chart views + data, chart_data = prepare_data(data, filters) + + return columns, data, None, chart_data + +def validate_filters(filters): + from_date, to_date = filters.get("from_date"), filters.get("to_date") + + if not from_date and to_date: + frappe.throw(_("From and To Dates are required.")) + elif date_diff(to_date, from_date) < 0: + frappe.throw(_("To Date cannot be before From Date.")) + +def get_conditions(filters): + conditions = '' + + if filters.get("from_date") and filters.get("to_date"): + conditions += " and mr.transaction_date between '{0}' and '{1}'".format(filters.get("from_date"),filters.get("to_date")) + + if filters.get("company"): + conditions += " and mr.company = '{0}'".format(filters.get("company")) + + if filters.get("material_request"): + conditions += " and mr.name = '{0}'".format(filters.get("material_request")) + + return conditions + +def get_data(filters, conditions): + data = frappe.db.sql(""" + select + mr.name as material_request, + mr.transaction_date as date, + mr_item.schedule_date as required_date, + mr_item.item_code as item_code, + sum(ifnull(mr_item.stock_qty, 0)) as qty, + ifnull(mr_item.stock_uom, '') as uom, + sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty, + (sum(mr_item.stock_qty) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order, + mr_item.item_name as item_name, + mr.company as company + from + `tabMaterial Request` mr, `tabMaterial Request Item` mr_item + where + mr_item.parent = mr.name + and mr.material_request_type = "Purchase" + and mr.docstatus = 1 + and mr.status != "Stopped" + {conditions} + group by mr.name, mr_item.item_code + having + sum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0)) + order by mr.transaction_date, mr.schedule_date""".format(conditions=conditions), as_dict=1) + + return data + +def prepare_data(data, filters): + """Prepare consolidated Report data and Chart data""" + material_request_map = {} + + for row in data: + if not row["material_request"] in material_request_map: + # create an entry with mr as key + row_copy = copy.deepcopy(row) + material_request_map[row["material_request"]] = row_copy + else: + mr_row = material_request_map[row["material_request"]] + mr_row["required_date"] = min(getdate(mr_row["required_date"]), getdate(row["required_date"])) + + #sum numeric rows + fields = ["qty", "ordered_qty", "qty_to_order"] + for field in fields: + mr_row[field] = flt(mr_row[field]) + flt(row[field]) + + chart_data = prepare_chart_data(material_request_map) + + if filters.get("group_by_mr"): + data =[] + for mr in material_request_map: + data.append(material_request_map[mr]) + return data, chart_data + + return data, chart_data + +def prepare_chart_data(data): + labels, qty_to_order, ordered_qty = [], [], [] + + for row in data: + mr_row = data[row] + labels.append(mr_row["material_request"]) + qty_to_order.append(mr_row["qty_to_order"]) + ordered_qty.append(mr_row["ordered_qty"]) + + chart_data = { + "data" : { + "labels": labels, + "datasets": [ + { + 'name': _('Qty to Order'), + 'values': qty_to_order + }, + { + 'name': _('Ordered Qty'), + 'values': ordered_qty + } + ] + }, + "type": "bar", + "barOptions": { + "stacked": 1 + }, + } + + return chart_data + +def get_columns(filters): + columns = [ + { + "label": _("Material Request"), + "fieldname": "material_request", + "fieldtype": "Link", + "options": "Material Request", + "width": 150 + }, + { + "label":_("Date"), + "fieldname": "date", + "fieldtype": "Date", + "width": 90 + }, + { + "label":_("Required By"), + "fieldname": "required_date", + "fieldtype": "Date", + "width": 100 + } + ] + + if not filters.get("group_by_mr"): + columns.extend([{ + "label":_("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 100 + }, + { + "label":_("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 100 + }, + { + "label": _("UOM"), + "fieldname": "uom", + "fieldtype": "Data", + "width": 100, + }]) + + columns.extend([ + { + "label": _("Qty"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, + { + "label": _("Ordered Qty"), + "fieldname": "ordered_qty", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, + { + "label": _("Qty to Order"), + "fieldname": "qty_to_order", + "fieldtype": "Float", + "width": 120, + "convertible": "qty" + }, + { + "label": _("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "width": 100 + } + ]) + + return columns diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py index 1d4054786e6..16b49a1e578 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -166,7 +166,7 @@ def get_data(): { "type": "report", "is_query_report": True, - "name": "Requested Items To Be Ordered", + "name": "Requested Items To Order", "reference_doctype": "Material Request", "onboard": 1, }, diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js index d95507b7d1b..c786a8674e6 100644 --- a/erpnext/public/js/purchase_trends_filters.js +++ b/erpnext/public/js/purchase_trends_filters.js @@ -53,7 +53,7 @@ erpnext.get_purchase_trends_filters = function() { ], "default": "Item", "dashboard_config": { - "read_only": 1, + "read_only": 1 } }, { diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py index 43f1f373d35..8227f1548c1 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py @@ -26,9 +26,10 @@ def get_chart_data(data, filters): # consider only consolidated row data = [row for row in data if row[0]] + data = sorted(data, key = lambda i: i[-1], reverse=True) + if len(data) > 10: # get top 10 if data too long - data = sorted(data, key = lambda i: i[-1],reverse=True) data = data[:10] for row in data: