Merge branch 'project-refresh' of https://github.com/scmmishra/erpnext into scmmishra-project-refresh
diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py
index 214e467..1eed5a0 100644
--- a/erpnext/accounts/dashboard_fixtures.py
+++ b/erpnext/accounts/dashboard_fixtures.py
@@ -26,8 +26,8 @@
def get_dashboards():
return [{
- "name": "Accounts Dashboard",
- "dashboard_name": "Accounts Dashboard",
+ "name": "Accounts",
+ "dashboard_name": "Accounts",
"doctype": "Dashboard",
"charts": [
{ "chart": "Profit and Loss" , "width": "Full"},
@@ -81,7 +81,7 @@
"timespan": "Last Year",
"color": "#a83333",
"value_based_on": "base_net_total",
- "filters_json": json.dumps({"docstatus": 1}),
+ "filters_json": json.dumps([["Purchase Invoice", "docstatus", "=", 1]]),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
@@ -99,7 +99,7 @@
"timespan": "Last Year",
"color": "#7b933d",
"value_based_on": "base_net_total",
- "filters_json": json.dumps({"docstatus": 1}),
+ "filters_json": json.dumps([["Sales Invoice", "docstatus", "=", 1]]),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index a783b1d..576d10c 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -122,8 +122,8 @@
"type": "DocType"
},
{
- "label": "Accounts Dashboard",
- "link_to": "Accounts Dashboard",
+ "label": "Dashboard",
+ "link_to": "Accounts",
"type": "Dashboard"
},
{
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index db20589..63c34ed 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -398,7 +398,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order",
"no_copy": 1,
"print_hide": 1
@@ -1579,7 +1579,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-29 13:37:09.355300",
+ "modified": "2020-05-19 17:00:57.208696",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py
new file mode 100644
index 0000000..0e2f78f
--- /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 5e764cf..ee18545 100644
--- a/erpnext/buying/desk_page/buying/buying.json
+++ b/erpnext/buying/desk_page/buying/buying.json
@@ -2,18 +2,13 @@
"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,
@@ -22,73 +17,92 @@
},
{
"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",
"links": "[\n {\n \"description\": \"All Supplier scorecards.\",\n \"label\": \"Supplier Scorecard\",\n \"name\": \"Supplier Scorecard\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier scorecard variables.\",\n \"label\": \"Supplier Scorecard Variable\",\n \"name\": \"Supplier Scorecard Variable\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier scorecard criteria.\",\n \"label\": \"Supplier Scorecard Criteria\",\n \"name\": \"Supplier Scorecard Criteria\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier standings.\",\n \"label\": \"Supplier Scorecard Standing\",\n \"name\": \"Supplier Scorecard Standing\",\n \"type\": \"doctype\"\n }\n]"
},
{
"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 403b1c9..a27950a 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.js
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.js
@@ -6,3 +6,26 @@
// }
});
+
+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 ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series'>Naming Series</a>" + __(" 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 a492519..a0ab2a0 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 3bc441a..7db1516 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 0000000..8c798b3
--- /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 0000000..a788ccd
--- /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 0000000..9dc493d
--- /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 0000000..d2068e1
--- /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 0000000..7a64224
--- /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 0000000..9dbed23
--- /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 0000000..fd98fdd
--- /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 0000000..557c905
--- /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 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/__init__.py
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 0000000..701da43
--- /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 = "<span style='color:green'>" + value + "</span>";
+ }
+ 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 0000000..5ba3101
--- /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 0000000..89be622
--- /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 888676c..1ed6cad 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 @@
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 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/__init__.py
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 0000000..21adb13
--- /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 = "<span style='color:green'>" + value + "</span>";
+ }
+ 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 0000000..4a0578b
--- /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 0000000..a021d3c
--- /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 1d40547..16b49a1 100644
--- a/erpnext/config/buying.py
+++ b/erpnext/config/buying.py
@@ -166,7 +166,7 @@
{
"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/config/manufacturing.py b/erpnext/config/manufacturing.py
index 2c18eeb..012f1ca 100644
--- a/erpnext/config/manufacturing.py
+++ b/erpnext/config/manufacturing.py
@@ -120,13 +120,7 @@
{
"type": "report",
"is_query_report": True,
- "name": "Open Work Orders",
- "doctype": "Work Order"
- },
- {
- "type": "report",
- "is_query_report": True,
- "name": "Work Orders in Progress",
+ "name": "Work Order Summary",
"doctype": "Work Order"
},
{
@@ -138,12 +132,6 @@
{
"type": "report",
"is_query_report": True,
- "name": "Completed Work Orders",
- "doctype": "Work Order"
- },
- {
- "type": "report",
- "is_query_report": True,
"name": "Production Analytics",
"doctype": "Work Order"
},
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 6188652..44f87e0 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -49,12 +49,13 @@
if event == "created":
sys_lang = frappe.get_single("System Settings").language or 'en'
raw_billing_data = order.get("billing")
+ raw_shipping_data = order.get("shipping")
customer_name = raw_billing_data.get("first_name") + " " + raw_billing_data.get("last_name")
- link_customer_and_address(raw_billing_data, customer_name)
+ link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name)
link_items(order.get("line_items"), woocommerce_settings, sys_lang)
create_sales_order(order, woocommerce_settings, customer_name, sys_lang)
-def link_customer_and_address(raw_billing_data, customer_name):
+def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name):
customer_woo_com_email = raw_billing_data.get("email")
customer_exists = frappe.get_value("Customer", {"woocommerce_email": customer_woo_com_email})
if not customer_exists:
@@ -68,38 +69,74 @@
customer.customer_name = customer_name
customer.woocommerce_email = customer_woo_com_email
customer.flags.ignore_mandatory = True
- customer.save()
+ customer.save()
if customer_exists:
frappe.rename_doc("Customer", old_name, customer_name)
- address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email})
+ billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
+ shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
+ rename_address(billing_address, customer)
+ rename_address(shipping_address, customer)
else:
- address = frappe.new_doc("Address")
+ create_address(raw_billing_data, customer, "Billing")
+ create_address(raw_shipping_data, customer, "Shipping")
+ create_contact(raw_billing_data, customer)
- address.address_line1 = raw_billing_data.get("address_1", "Not Provided")
- address.address_line2 = raw_billing_data.get("address_2", "Not Provided")
- address.city = raw_billing_data.get("city", "Not Provided")
- address.woocommerce_email = customer_woo_com_email
- address.address_type = "Billing"
- address.country = frappe.get_value("Country", {"code": raw_billing_data.get("country", "IN").lower()})
- address.state = raw_billing_data.get("state")
- address.pincode = raw_billing_data.get("postcode")
- address.phone = raw_billing_data.get("phone")
- address.email_id = customer_woo_com_email
+def create_contact(data, customer):
+ email = data.get("email", None)
+ phone = data.get("phone", None)
+
+ if not email and not phone:
+ return
+
+ contact = frappe.new_doc("Contact")
+ contact.first_name = data.get("first_name")
+ contact.last_name = data.get("last_name")
+ contact.is_primary_contact = 1
+ contact.is_billing_contact = 1
+
+ if phone:
+ contact.add_phone(phone, is_primary_mobile_no=1, is_primary_phone=1)
+
+ if email:
+ contact.add_email(email, is_primary=1)
+
+ contact.append("links", {
+ "link_doctype": "Customer",
+ "link_name": customer.name
+ })
+
+ contact.flags.ignore_mandatory = True
+ contact.save()
+
+def create_address(raw_data, customer, address_type):
+ address = frappe.new_doc("Address")
+
+ address.address_line1 = raw_data.get("address_1", "Not Provided")
+ address.address_line2 = raw_data.get("address_2", "Not Provided")
+ address.city = raw_data.get("city", "Not Provided")
+ address.woocommerce_email = customer.woocommerce_email
+ address.address_type = address_type
+ address.country = frappe.get_value("Country", {"code": raw_data.get("country", "IN").lower()})
+ address.state = raw_data.get("state")
+ address.pincode = raw_data.get("postcode")
+ address.phone = raw_data.get("phone")
+ address.email_id = customer.woocommerce_email
address.append("links", {
"link_doctype": "Customer",
- "link_name": customer.customer_name
+ "link_name": customer.name
})
+
address.flags.ignore_mandatory = True
- address = address.save()
+ address.save()
- if customer_exists:
- old_address_title = address.name
- new_address_title = customer.customer_name + "-billing"
- address.address_title = customer.customer_name
- address.save()
+def rename_address(address, customer):
+ old_address_title = address.name
+ new_address_title = customer.name + "-" + address.address_type
+ address.address_title = customer.customer_name
+ address.save()
- frappe.rename_doc("Address", old_address_title, new_address_title)
+ frappe.rename_doc("Address", old_address_title, new_address_title)
def link_items(items_list, woocommerce_settings, sys_lang):
for item_data in items_list:
@@ -111,7 +148,7 @@
else:
#Create Item
item = frappe.new_doc("Item")
-
+
item.item_name = item_data.get("name")
item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
item.woocommerce_id = item_data.get("product_id")
@@ -171,7 +208,7 @@
add_tax_details(new_sales_order, order.get("shipping_tax"), "Shipping Tax", woocommerce_settings.f_n_f_account)
add_tax_details(new_sales_order, order.get("shipping_total"), "Shipping Total", woocommerce_settings.f_n_f_account)
-
+
def add_tax_details(sales_order, price, desc, tax_account_head):
sales_order.append("taxes", {
"charge_type":"Actual",
diff --git a/erpnext/healthcare/dashboard_chart_source/__init__.py b/erpnext/healthcare/dashboard_chart_source/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/__init__.py
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js
new file mode 100644
index 0000000..dd6dc66
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Department wise Patient Appointments"] = {
+ method: "erpnext.healthcare.dashboard_chart_source.department_wise_patient_appointments.department_wise_patient_appointments.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json
new file mode 100644
index 0000000..00301ef
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2020-05-18 19:18:42.571045",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2020-05-18 19:18:42.571045",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Department wise Patient Appointments",
+ "owner": "Administrator",
+ "source_name": "Department wise Patient Appointments",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py
new file mode 100644
index 0000000..062da6e
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils.dashboard import cache_source
+
+@frappe.whitelist()
+@cache_source
+def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ if chart_name:
+ chart = frappe.get_doc('Dashboard Chart', chart_name)
+ else:
+ chart = frappe._dict(frappe.parse_json(chart))
+
+ filters = frappe.parse_json(filters)
+
+ data = frappe.db.get_list('Medical Department', fields=['name'])
+ if not filters:
+ filters = {}
+
+ status = ['Open', 'Scheduled', 'Closed', 'Cancelled']
+ for department in data:
+ filters['department'] = department.name
+ department['total_appointments'] = frappe.db.count('Patient Appointment', filters=filters)
+
+ for entry in status:
+ filters['status'] = entry
+ department[frappe.scrub(entry)] = frappe.db.count('Patient Appointment', filters=filters)
+ filters.pop('status')
+
+ sorted_department_map = sorted(data, key = lambda i: i['total_appointments'], reverse=True)
+
+ if len(sorted_department_map) > 10:
+ sorted_department_map = sorted_department_map[:10]
+
+ labels = []
+ open_appointments = []
+ scheduled = []
+ closed = []
+ cancelled = []
+
+ for department in sorted_department_map:
+ labels.append(department.name)
+ open_appointments.append(department.open)
+ scheduled.append(department.scheduled)
+ closed.append(department.closed)
+ cancelled.append(department.cancelled)
+
+ return {
+ 'labels': labels,
+ 'datasets': [
+ {
+ 'name': 'Open',
+ 'values': open_appointments
+ },
+ {
+ 'name': 'Scheduled',
+ 'values': scheduled
+ },
+ {
+ 'name': 'Closed',
+ 'values': closed
+ },
+ {
+ 'name': 'Cancelled',
+ 'values': cancelled
+ }
+ ],
+ 'type': 'bar'
+ }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py
index fc3d62f..59da71a 100644
--- a/erpnext/healthcare/dashboard_fixtures.py
+++ b/erpnext/healthcare/dashboard_fixtures.py
@@ -3,33 +3,60 @@
import frappe
import json
-
+from frappe import _
def get_data():
return frappe._dict({
"dashboards": get_dashboards(),
"charts": get_charts(),
+ "number_cards": get_number_cards(),
})
+def get_company():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company = frappe.get_list("Company", limit=1)
+ if company:
+ return company.name
+ return None
+
def get_dashboards():
return [{
"name": "Healthcare",
"dashboard_name": "Healthcare",
"charts": [
- { "chart": "Patient Appointments" }
+ { "chart": "Patient Appointments", "width": "Full"},
+ { "chart": "In-Patient Status", "width": "Half"},
+ { "chart": "Clinical Procedures Status", "width": "Half"},
+ { "chart": "Lab Tests", "width": "Half"},
+ { "chart": "Clinical Procedures", "width": "Half"},
+ { "chart": "Symptoms", "width": "Half"},
+ { "chart": "Diagnoses", "width": "Half"},
+ { "chart": "Department wise Patient Appointments", "width": "Full"}
+ ],
+ "cards": [
+ { "card": "Total Patients" },
+ { "card": "Total Patient Admitted" },
+ { "card": "Open Appointments" },
+ { "card": "Appointments to Bill" }
]
}]
def get_charts():
+ company = get_company()
return [
{
"doctype": "Dashboard Chart",
"time_interval": "Daily",
"name": "Patient Appointments",
- "chart_name": "Patient Appointments",
+ "chart_name": _("Patient Appointments"),
"timespan": "Last Month",
- "color": "#77ecca",
- "filters_json": json.dumps({}),
+ "filters_json": json.dumps([
+ ["Patient Appointment", "company", "=", company, False],
+ ["Patient Appointment", "status", "!=", "Cancelled"]
+ ]),
"chart_type": "Count",
"timeseries": 1,
"based_on": "appointment_datetime",
@@ -37,5 +64,182 @@
"document_type": "Patient Appointment",
"type": "Line",
"width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Department wise Patient Appointments",
+ "chart_name": _("Department wise Patient Appointments"),
+ "chart_type": "Custom",
+ "source": "Department wise Patient Appointments",
+ "filters_json": json.dumps({}),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Bar",
+ "width": "Full",
+ "custom_options": json.dumps({
+ "colors": ["#7CD5FA", "#5F62F6", "#7544E2", "#EE5555"],
+ "barOptions":{
+ "stacked":1
+ },
+ "height": 300
+ })
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Lab Tests",
+ "chart_name": _("Lab Tests"),
+ "chart_type": "Group By",
+ "document_type": "Lab Test",
+ "group_by_type": "Count",
+ "group_by_based_on": "template",
+ "filters_json": json.dumps([
+ ["Lab Test", "company", "=", company, False],
+ ["Lab Test", "docstatus", "=", 1]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Clinical Procedures",
+ "chart_name": _("Clinical Procedures"),
+ "chart_type": "Group By",
+ "document_type": "Clinical Procedure",
+ "group_by_type": "Count",
+ "group_by_based_on": "procedure_template",
+ "filters_json": json.dumps([
+ ["Clinical Procedure", "company", "=", company, False],
+ ["Clinical Procedure", "docstatus", "=", 1]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "In-Patient Status",
+ "chart_name": _("In-Patient Status"),
+ "chart_type": "Group By",
+ "document_type": "Inpatient Record",
+ "group_by_type": "Count",
+ "group_by_based_on": "status",
+ "filters_json": json.dumps([
+ ["Inpatient Record", "company", "=", company, False]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Bar",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Clinical Procedures Status",
+ "chart_name": _("Clinical Procedure Status"),
+ "chart_type": "Group By",
+ "document_type": "Clinical Procedure",
+ "group_by_type": "Count",
+ "group_by_based_on": "status",
+ "filters_json": json.dumps([
+ ["Clinical Procedure", "company", "=", company, False],
+ ["Clinical Procedure", "docstatus", "=", 1]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Pie",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Symptoms",
+ "chart_name": _("Symptoms"),
+ "chart_type": "Group By",
+ "document_type": "Patient Encounter Symptom",
+ "group_by_type": "Count",
+ "group_by_based_on": "complaint",
+ "filters_json": json.dumps({}),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Diagnoses",
+ "chart_name": _("Diagnoses"),
+ "chart_type": "Group By",
+ "document_type": "Patient Encounter Diagnosis",
+ "group_by_type": "Count",
+ "group_by_based_on": "diagnosis",
+ "filters_json": json.dumps({}),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
}
]
+
+def get_number_cards():
+ company = get_company()
+ return [
+ {
+ "name": "Total Patients",
+ "label": _("Total Patients"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient",
+ "filters_json": json.dumps(
+ [["Patient","status","=","Active",False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "name": "Total Patients Admitted",
+ "label": _("Total Patients Admitted"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient",
+ "filters_json": json.dumps(
+ [["Patient","inpatient_status","=","Admitted",False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "name": "Open Appointments",
+ "label": _("Open Appointments"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient Appointment",
+ "filters_json": json.dumps(
+ [["Patient Appointment","company","=",company,False],
+ ["Patient Appointment","status","=","Open",False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "name": "Appointments to Bill",
+ "label": _("Appointments to Bill"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient Appointment",
+ "filters_json": json.dumps(
+ [["Patient Appointment","company","=",company,False],
+ ["Patient Appointment","invoiced","=",0,False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json
index 5cf09b3..60b5313 100644
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json
@@ -60,26 +60,30 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
- "modified": "2020-04-25 22:31:36.576444",
+ "modified": "2020-05-19 20:57:22.797267",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",
+ "onboarding": "Healthcare",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"restrict_to_domain": "Healthcare",
"shortcuts": [
{
+ "color": "#ffe8cd",
"format": "{} Open",
"label": "Patient Appointment",
"link_to": "Patient Appointment",
- "stats_filter": "{\n \"status\": \"Open\"\n}",
+ "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}",
"type": "DocType"
},
{
+ "color": "#ffe8cd",
"format": "{} Active",
"label": "Patient",
"link_to": "Patient",
@@ -87,10 +91,11 @@
"type": "DocType"
},
{
+ "color": "#cef6d1",
"format": "{} Vacant",
"label": "Healthcare Service Unit",
"link_to": "Healthcare Service Unit",
- "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}",
+ "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}",
"type": "DocType"
},
{
@@ -102,6 +107,11 @@
"label": "Patient History",
"link_to": "patient_history",
"type": "Page"
+ },
+ {
+ "label": "Healthcare Dashboard",
+ "link_to": "Healthcare",
+ "type": "Dashboard"
}
]
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
index 57f4cdf..16d4540 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
@@ -144,3 +144,38 @@
}
};
});
+
+frappe.tour['Clinical Procedure Template'] = [
+ {
+ fieldname: 'template',
+ title: __('Template Name'),
+ description: __('Enter a name for the Clinical Procedure Template')
+ },
+ {
+ fieldname: 'item_code',
+ title: __('Item Code'),
+ description: __('Set the Item Code which will be used for billing the Clinical Procedure.')
+ },
+ {
+ fieldname: 'item_group',
+ title: __('Item Group'),
+ description: __('Select an Item Group for the Clinical Procedure Item.')
+ },
+ {
+ fieldname: 'is_billable',
+ title: __('Clinical Procedure Rate'),
+ description: __('Check this if the Clinical Procedure is billable and also set the rate.')
+ },
+ {
+ fieldname: 'consume_stock',
+ title: __('Allow Stock Consumption'),
+ description: __('Check this if the Clinical Procedure utilises consumables. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/clinical_procedure_template#22-manage-procedure-consumables' target='_blank'>here</a>" + __(' to know more')
+
+ },
+ {
+ fieldname: 'medical_department',
+ title: __('Medical Department'),
+ description: __('You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.')
+ }
+];
+
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
index 4ab3b6e..fc0b241 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
@@ -108,3 +108,38 @@
});
}
});
+
+frappe.tour['Healthcare Practitioner'] = [
+ {
+ fieldname: 'employee',
+ title: __('Employee'),
+ description: __('If you want to track Payroll and other HRMS operations for a Practitoner, create an Employee and link it here.')
+ },
+ {
+ fieldname: 'practitioner_schedules',
+ title: __('Practitioner Schedules'),
+ description: __('Set the Practitioner Schedule you just created. This will be used while booking appointments.')
+ },
+ {
+ fieldname: 'op_consulting_charge_item',
+ title: __('Out Patient Consulting Charge Item'),
+ description: __('Create a service item for Out Patient Consulting.')
+ },
+ {
+ fieldname: 'inpatient_visit_charge_item',
+ title: __('Inpatient Visit Charge Item'),
+ description: __('If this Healthcare Practitioner works for the In-Patient Department, create a service item for Inpatient Visits.')
+ },
+ {
+ fieldname: 'op_consulting_charge',
+ title: __('Out Patient Consulting Charge'),
+ description: __('Set the Out Patient Consulting Charge for this Practitioner.')
+
+ },
+ {
+ fieldname: 'inpatient_visit_charge',
+ title: __('Inpatient Visit Charge'),
+ description: __('If this Healthcare Practitioner also works for the In-Patient Department, set the inpatient visit charge for this Practitioner.')
+ }
+];
+
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
index 22fbf50..c266ba8 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
@@ -39,3 +39,37 @@
};
});
};
+
+frappe.tour['Healthcare Settings'] = [
+ {
+ fieldname: 'link_customer_to_patient',
+ title: __('Link Customer to Patient'),
+ description: __('If checked, a customer will be created for every Patient. Patient Invoices will be created against this Customer. You can also select existing Customer while creating a Patient. This field is checked by default.')
+ },
+ {
+ fieldname: 'collect_registration_fee',
+ title: __('Collect Registration Fee'),
+ description: __('If your Healthcare facility bills registrations of Patients, you can check this and set the Registration Fee in the field below. Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.')
+ },
+ {
+ fieldname: 'automate_appointment_invoicing',
+ title: __('Automate Appointment Invoicing'),
+ description: __('Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.')
+ },
+ {
+ fieldname: 'healthcare_service_items',
+ title: __('Healthcare Service Items'),
+ description: __('Set up the Healthcare Service Items for billing. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/healthcare_settings#2-default-healthcare-service-items' target='_blank'>here</a>" + __(' to know more')
+ },
+ {
+ fieldname: 'sb_in_ac',
+ title: __('Set up default Accounts for the Healthcare Facility'),
+ description: __('If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.')
+
+ },
+ {
+ fieldname: 'out_patient_sms_alerts',
+ title: __('Out Patient SMS alerts'),
+ description: __('You can set up Out Patient SMS alerts here. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/healthcare_settings#4-out-patient-sms-alerts' target='_blank'>here</a>" + __(' to know more')
+ }
+];
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
index e2b47b4..3521561 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
@@ -115,7 +115,7 @@
"price_list": price_list_name,
"item_code": item,
"price_list_rate": item_price
- }).insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc):
diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
new file mode 100644
index 0000000..db35149
--- /dev/null
+++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
@@ -0,0 +1,42 @@
+{
+ "allow_roles": [
+ {
+ "role": "Healthcare Administrator"
+ }
+ ],
+ "creation": "2020-05-19 10:32:43.025852",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 12:52:09.757729",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Healthcare",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Create Patient"
+ },
+ {
+ "step": "Create Practitioner"
+ },
+ {
+ "step": "Create Practitioner Schedule"
+ },
+ {
+ "step": "Setup Schedule and Employee for Healthcare Practitioner"
+ },
+ {
+ "step": "Explore Healthcare Settings"
+ },
+ {
+ "step": "Explore Clinical Procedure Templates"
+ }
+ ],
+ "subtitle": "Patients, Practitioner Schedules, Settings and more.",
+ "success_message": "Yayy! The Healthcare Module is all set up!",
+ "title": "Let's Setup the Healthcare Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
new file mode 100644
index 0000000..77bc5bd
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:32:27.648902",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:24.023418",
+ "modified_by": "Administrator",
+ "name": "Create Patient",
+ "owner": "Administrator",
+ "reference_document": "Patient",
+ "show_full_form": 1,
+ "title": "Create Patient",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json b/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json
new file mode 100644
index 0000000..614b201
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:39:55.728057",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:27:39.851375",
+ "modified_by": "Administrator",
+ "name": "Create Practitioner",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Practitioner",
+ "show_full_form": 1,
+ "title": "Create Practitioner",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
new file mode 100644
index 0000000..65980ef
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:41:19.065753",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:27:09.437825",
+ "modified_by": "Administrator",
+ "name": "Create Practitioner Schedule",
+ "owner": "Administrator",
+ "reference_document": "Practitioner Schedule",
+ "show_full_form": 1,
+ "title": "Create Practitioner Schedule",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
new file mode 100644
index 0000000..f0c0f61
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:40:51.963741",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 11:46:35.085270",
+ "modified_by": "Administrator",
+ "name": "Explore Clinical Procedure Templates",
+ "owner": "Administrator",
+ "reference_document": "Clinical Procedure Template",
+ "show_full_form": 0,
+ "title": "Explore Clinical Procedure Templates",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
new file mode 100644
index 0000000..2bdab69
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:14:33.044989",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:48.682673",
+ "modified_by": "Administrator",
+ "name": "Explore Healthcare Settings",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Settings",
+ "show_full_form": 0,
+ "title": "Explore Healthcare Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json
new file mode 100644
index 0000000..c5af177
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json
@@ -0,0 +1,20 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 10:43:56.231679",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "field": "schedule",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:42.492734",
+ "modified_by": "Administrator",
+ "name": "Setup Schedule and Employee for Healthcare Practitioner",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Practitioner",
+ "show_full_form": 0,
+ "title": "Setup Schedule and Employee for Healthcare Practitioner",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
new file mode 100644
index 0000000..ef61f23
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe, erpnext, json
+from frappe import _
+from frappe.utils import nowdate, get_date_str
+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_dashboards():
+ return [{
+ "name": "Manufacturing",
+ "dashboard_name": "Manufacturing",
+ "charts": [
+ { "chart": "Produced Quantity", "width": "Half" },
+ { "chart": "Completed Operation", "width": "Half" },
+ { "chart": "Work Order Analysis", "width": "Half" },
+ { "chart": "Quality Inspection Analysis", "width": "Half" },
+ { "chart": "Pending Work Order", "width": "Half" },
+ { "chart": "Last Month Downtime Analysis", "width": "Half" },
+ { "chart": "Work Order Qty Analysis", "width": "Full" },
+ { "chart": "Job Card Analysis", "width": "Full" }
+ ],
+ "cards": [
+ { "card": "Total Work Order" },
+ { "card": "Completed Work Order" },
+ { "card": "Ongoing Job Card" },
+ { "card": "Total Quality Inspection"}
+ ]
+ }]
+
+def get_charts():
+ company = erpnext.get_default_company()
+
+ if not company:
+ company = frappe.db.get_value("Company", {"is_group": 0}, "name")
+
+ return [{
+ "doctype": "Dashboard Chart",
+ "based_on": "modified",
+ "time_interval": "Yearly",
+ "chart_type": "Sum",
+ "chart_name": _("Produced Quantity"),
+ "name": "Produced Quantity",
+ "document_type": "Work Order",
+ "filters_json": json.dumps([['Work Order', 'docstatus', '=', 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Monthly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "produced_qty",
+ "is_public": 1,
+ "timeseries": 1
+ }, {
+ "doctype": "Dashboard Chart",
+ "based_on": "creation",
+ "time_interval": "Yearly",
+ "chart_type": "Sum",
+ "chart_name": _("Completed Operation"),
+ "name": "Completed Operation",
+ "document_type": "Work Order Operation",
+ "filters_json": json.dumps([['Work Order Operation', 'docstatus', '=', 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Quarterly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "completed_qty",
+ "is_public": 1,
+ "timeseries": 1
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Analysis"),
+ "name": "Work Order Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Quality Inspection Analysis"),
+ "name": "Quality Inspection Analysis",
+ "timespan": "Last Year",
+ "report_name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Pending Work Order"),
+ "name": "Pending Work Order",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
+ "owner": "Administrator",
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Last Month Downtime Analysis"),
+ "name": "Last Month Downtime Analysis",
+ "timespan": "Last Year",
+ "filters_json": json.dumps({}),
+ "report_name": "Downtime Analysis",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "type": "Bar"
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Qty Analysis"),
+ "name": "Work Order Qty Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
+ "owner": "Administrator",
+ "type": "Bar",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "barOptions": { "stacked": 1 }
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Job Card Analysis"),
+ "name": "Job Card Analysis",
+ "timespan": "Last Year",
+ "report_name": "Job Card Summary",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "filters_json": json.dumps({"company": company, "docstatus": 1, "range":"Monthly"}),
+ "custom_options": json.dumps({
+ "barOptions": { "stacked": 1 }
+ }),
+ "type": "Bar"
+ }]
+
+def get_number_cards():
+ fiscal_year = get_fiscal_year(date=nowdate())
+ year_start_date = get_date_str(fiscal_year[1])
+ year_end_date = get_date_str(fiscal_year[2])
+
+ return [{
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Total Work Order",
+ "filters_json": json.dumps([
+ ['Work Order', 'docstatus', '=', 1],
+ ['Work Order', 'creation', 'between', [year_start_date, year_end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Total Work Order"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Completed Work Order",
+ "filters_json": json.dumps([
+ ['Work Order', 'status', '=', 'Completed'],
+ ['Work Order', 'docstatus', '=', 1],
+ ['Work Order', 'creation', 'between', [year_start_date, year_end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Completed Work Order"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Job Card",
+ "name": "Ongoing Job Card",
+ "filters_json": json.dumps([
+ ['Job Card', 'status','!=','Completed'],
+ ['Job Card', 'docstatus', '=', 1]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Ongoing Job Card"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Quality Inspection",
+ "name": "Total Quality Inspection",
+ "filters_json": json.dumps([['Quality Inspection', 'docstatus', '=', 1]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Total Quality Inspection"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ }]
\ No newline at end of file
diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
index 18604e2..ecd2dc9 100644
--- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
@@ -3,7 +3,7 @@
{
"hidden": 0,
"label": "Production",
- "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -13,7 +13,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Open Work Orders\",\n \"name\": \"Open Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Orders in Progress\",\n \"name\": \"Work Orders in Progress\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Completed Work Orders\",\n \"name\": \"Completed Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]"
+ "links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Planning Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Planning Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Operations Time\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Operations Time\"\n}]"
},
{
"hidden": 0,
@@ -32,7 +32,11 @@
}
],
"category": "Domains",
- "charts": [],
+ "charts": [
+ {
+ "chart_name": "Produced Quantity"
+ }
+ ],
"creation": "2020-03-02 17:11:37.032604",
"developer_mode_only": 0,
"disable_user_customization": 0,
@@ -42,13 +46,59 @@
"idx": 0,
"is_standard": 1,
"label": "Manufacturing",
- "modified": "2020-04-01 11:28:50.979358",
+ "modified": "2020-05-19 14:05:59.100891",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
+ "onboarding": "Manufacturing",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"restrict_to_domain": "Manufacturing",
- "shortcuts": []
+ "shortcuts": [
+ {
+ "format": "{} Active",
+ "label": "Item",
+ "link_to": "Item",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{\n \"disabled\": 0\n}",
+ "type": "DocType"
+ },
+ {
+ "format": "{} Active",
+ "label": "BOM",
+ "link_to": "BOM",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{\n \"is_active\": 1\n}",
+ "type": "DocType"
+ },
+ {
+ "format": "{} Open",
+ "label": "Work Order",
+ "link_to": "Work Order",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}",
+ "type": "DocType"
+ },
+ {
+ "format": "{} Open",
+ "label": "Production Plan",
+ "link_to": "Production Plan",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}",
+ "type": "DocType"
+ },
+ {
+ "label": "Work Order Summary",
+ "link_to": "Work Order Summary",
+ "restrict_to_domain": "Manufacturing",
+ "type": "Report"
+ },
+ {
+ "label": "Dashboard",
+ "link_to": "Manufacturing",
+ "restrict_to_domain": "Manufacturing",
+ "type": "Dashboard"
+ }
+ ]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/downtime_entry/__init__.py b/erpnext/manufacturing/doctype/downtime_entry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/__init__.py
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js
new file mode 100644
index 0000000..3b7f5ba
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Downtime Entry', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
new file mode 100644
index 0000000..9acb4f0
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
@@ -0,0 +1,132 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "creation": "2020-04-18 04:50:46.187638",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "workstation",
+ "operator",
+ "column_break_4",
+ "from_time",
+ "to_time",
+ "downtime",
+ "downtime_reason_section",
+ "stop_reason",
+ "column_break_9",
+ "remarks"
+ ],
+ "fields": [
+ {
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "label": "Workstation / Machine",
+ "options": "Workstation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "From Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "To Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "operator",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Operator",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fieldname": "downtime_reason_section",
+ "fieldtype": "Section Break",
+ "label": "Downtime Reason"
+ },
+ {
+ "description": "In Mins",
+ "fieldname": "downtime",
+ "fieldtype": "Float",
+ "label": "Downtime",
+ "read_only": 1
+ },
+ {
+ "fieldname": "stop_reason",
+ "fieldtype": "Select",
+ "label": "Stop Reason",
+ "options": "\nExcessive machine set up time\nUnplanned machine maintenance\nOn-machine press checks\nMachine operator errors\nMachine malfunction\nElectricity down\nOther",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "remarks",
+ "fieldtype": "Text",
+ "label": "Remarks"
+ }
+ ],
+ "links": [],
+ "modified": "2020-05-19 12:59:37.358483",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Downtime Entry",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "workstation",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py
new file mode 100644
index 0000000..56ec435
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import time_diff_in_hours
+from frappe.model.document import Document
+
+class DowntimeEntry(Document):
+ def validate(self):
+ if self.from_time and self.to_time:
+ self.downtime = time_diff_in_hours(self.to_time, self.from_time) * 60
diff --git a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py
new file mode 100644
index 0000000..8b2a8d3
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestDowntimeEntry(unittest.TestCase):
+ pass
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 7661fff..fba670c 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2018-07-09 17:23:29.518745",
"doctype": "DocType",
@@ -264,8 +265,10 @@
{
"fetch_from": "work_order.production_item",
"fieldname": "production_item",
- "fieldtype": "Read Only",
- "label": "Production Item"
+ "fieldtype": "Link",
+ "label": "Production Item",
+ "options": "Item",
+ "read_only": 1
},
{
"fieldname": "barcode",
@@ -274,7 +277,8 @@
"read_only": 1
},
{
- "fetch_from": "work_order.item_name",
+ "fetch_from": "production_item.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Read Only",
"label": "Item Name"
@@ -290,7 +294,8 @@
}
],
"is_submittable": 1,
- "modified": "2020-03-27 13:36:35.417502",
+ "links": [],
+ "modified": "2020-04-20 15:14:00.273441",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 00a67a0..585a09d 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -38,11 +38,12 @@
"required_items",
"time",
"planned_start_date",
- "actual_start_date",
- "column_break_13",
"planned_end_date",
- "actual_end_date",
"expected_delivery_date",
+ "column_break_13",
+ "actual_start_date",
+ "actual_end_date",
+ "lead_time",
"operations_section",
"transfer_material_against",
"operations",
@@ -108,6 +109,8 @@
},
{
"depends_on": "eval:doc.production_item",
+ "fetch_from": "production_item.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
@@ -281,27 +284,30 @@
"reqd": 1
},
{
+ "allow_on_submit": 1,
"fieldname": "actual_start_date",
"fieldtype": "Datetime",
"label": "Actual Start Date",
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"fieldname": "planned_end_date",
"fieldtype": "Datetime",
"label": "Planned End Date",
"no_copy": 1,
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
+ "allow_on_submit": 1,
"fieldname": "actual_end_date",
"fieldtype": "Datetime",
"label": "Actual End Date",
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
"allow_on_submit": 1,
@@ -476,6 +482,13 @@
"fieldtype": "Link",
"label": "Source Warehouse",
"options": "Warehouse"
+ },
+ {
+ "description": "In Mins",
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "label": "Lead Time",
+ "read_only": 1
}
],
"icon": "fa fa-cogs",
@@ -483,7 +496,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-04-24 19:32:43.323054",
+ "modified": "2020-05-05 19:32:43.323054",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 8301f30..c278955 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -6,7 +6,7 @@
import json
import math
from frappe import _
-from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form
+from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
from dateutil.relativedelta import relativedelta
@@ -279,7 +279,7 @@
if enable_capacity_planning and job_card_doc:
row.planned_start_time = job_card_doc.time_logs[-1].from_time
row.planned_end_time = job_card_doc.time_logs[-1].to_time
- print(row.planned_start_time, original_start_time, plan_days)
+
if date_diff(row.planned_start_time, original_start_time) > plan_days:
frappe.message_log.pop()
frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
@@ -437,8 +437,6 @@
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
def set_actual_dates(self):
- self.actual_start_date = None
- self.actual_end_date = None
if self.get("operations"):
actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
if actual_start_dates:
@@ -447,6 +445,27 @@
actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time]
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
+ else:
+ data = frappe.get_all("Stock Entry",
+ fields = ["timestamp(posting_date, posting_time) as posting_datetime"],
+ filters = {
+ "work_order": self.name,
+ "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"])
+ }
+ )
+
+ if data and len(data):
+ dates = [d.posting_datetime for d in data]
+ self.actual_start_date = min(dates)
+
+ if self.status == "Completed":
+ self.actual_end_date = max(dates)
+
+ self.set_lead_time()
+
+ def set_lead_time(self):
+ if self.actual_start_date and self.actual_end_date:
+ self.lead_time = flt(time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60)
def delete_job_card(self):
for d in frappe.get_all("Job Card", ["name"], {"work_order": self.name}):
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
new file mode 100644
index 0000000..952d1f0
--- /dev/null
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -0,0 +1,57 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Item Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-05 16:37:08.238935",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 12:51:42.744570",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Warehouse"
+ },
+ {
+ "step": "Workstation"
+ },
+ {
+ "step": "Operation"
+ },
+ {
+ "step": "Create Product"
+ },
+ {
+ "step": "Create Raw Materials"
+ },
+ {
+ "step": "Create BOM"
+ },
+ {
+ "step": "Work Order"
+ },
+ {
+ "step": "Explore Manufacturing Settings"
+ }
+ ],
+ "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
+ "success_message": "Manufacturing module is all setup!",
+ "title": "Let's Setup Manufacturing Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json
new file mode 100644
index 0000000..50584e1
--- /dev/null
+++ b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Item Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-05 16:37:08.238935",
+ "docstatus": 0,
+ "doctype": "Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-12 16:22:07.050224",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to Manufacturing"
+ },
+ {
+ "step": "Warehouse"
+ },
+ {
+ "step": "Workstation"
+ },
+ {
+ "step": "Operation"
+ },
+ {
+ "step": "Create Product"
+ },
+ {
+ "step": "Create BOM"
+ },
+ {
+ "step": "Work Order"
+ }
+ ],
+ "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
+ "success_message": "Manufacturing module is all setup!",
+ "title": "Let's Setup Manufacturing Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
new file mode 100644
index 0000000..84b4088
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-05 16:41:20.239696",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:51:31.315686",
+ "modified_by": "Administrator",
+ "name": "Create BOM",
+ "owner": "Administrator",
+ "reference_document": "BOM",
+ "show_full_form": 1,
+ "title": "Create a BOM (Bill of Material)",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
new file mode 100644
index 0000000..0ffa301
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-05 16:42:31.476275",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:59.010439",
+ "modified_by": "Administrator",
+ "name": "Create Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Finished Good",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
new file mode 100644
index 0000000..0764f2e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 11:53:17.295372",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 11:53:25.147837",
+ "modified_by": "Administrator",
+ "name": "Create Raw Materials",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create Raw Materials",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
new file mode 100644
index 0000000..582aba4
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
@@ -0,0 +1,20 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-19 11:55:11.378374",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:12:28.145366",
+ "modified_by": "Administrator",
+ "name": "Explore Manufacturing Settings",
+ "owner": "Administrator",
+ "reference_document": "Manufacturing Settings",
+ "show_full_form": 0,
+ "title": "Explore Manufacturing Settings",
+ "validate_action": 0,
+ "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json
new file mode 100644
index 0000000..eb7ab3a
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json
@@ -0,0 +1,20 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-05 16:40:23.676406",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 19:11:57.152883",
+ "modified_by": "Administrator",
+ "name": "Introduction to Manufacturing",
+ "owner": "Administrator",
+ "reference_document": "Manufacturing Settings",
+ "show_full_form": 0,
+ "title": "Manufacturing Settings",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json
new file mode 100644
index 0000000..b532e67
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/operation/operation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:15:31.706756",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:41.642754",
+ "modified_by": "Administrator",
+ "name": "Operation",
+ "owner": "Administrator",
+ "reference_document": "Operation",
+ "show_full_form": 0,
+ "title": "Create a Operation",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json
new file mode 100644
index 0000000..e23bd33
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:13:34.014554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:13.766712",
+ "modified_by": "Administrator",
+ "name": "Warehouse",
+ "owner": "Administrator",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Create a Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
new file mode 100644
index 0000000..c63363e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:15:56.084682",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:51:38.133150",
+ "modified_by": "Administrator",
+ "name": "Work Order",
+ "owner": "Administrator",
+ "reference_document": "Work Order",
+ "show_full_form": 1,
+ "title": "Create a Work Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
new file mode 100644
index 0000000..df244bb
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:14:14.930214",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:33.938176",
+ "modified_by": "Administrator",
+ "name": "Workstation",
+ "owner": "Administrator",
+ "reference_document": "Workstation",
+ "show_full_form": 0,
+ "title": "Create a Workstation / Machine",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_operations_time/__init__.py b/erpnext/manufacturing/report/bom_operations_time/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/__init__.py
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
new file mode 100644
index 0000000..7468e34
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["BOM Operations Time"] = {
+ "filters": [
+
+ ]
+};
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
new file mode 100644
index 0000000..665c5b9
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-03 01:41:20.862521",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-03 01:41:20.862521",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Operations Time",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "BOM",
+ "report_name": "BOM Operations Time",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
new file mode 100644
index 0000000..1279011
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ data = get_data(filters)
+ columns = get_columns(filters)
+ return columns, data
+
+def get_data(filters):
+ data = []
+
+ bom_data = []
+ for d in frappe.db.sql("""
+ SELECT
+ bom.name, bom.item, bom.item_name, bom.uom,
+ bomps.operation, bomps.workstation, bomps.time_in_mins
+ FROM `tabBOM` bom, `tabBOM Operation` bomps
+ WHERE
+ bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
+ """, as_dict=1):
+ row = get_args()
+ if d.name not in bom_data:
+ bom_data.append(d.name)
+ row.update(d)
+ else:
+ row.update({
+ "operation": d.operation,
+ "workstation": d.workstation,
+ "time_in_mins": d.time_in_mins
+ })
+
+ data.append(row)
+
+ used_as_subassembly_items = get_bom_count(bom_data)
+
+ for d in data:
+ d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
+
+ return data
+
+def get_bom_count(bom_data):
+ data = frappe.get_all("BOM Item",
+ fields=["count(name) as count", "bom_no"],
+ filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
+
+ bom_count = {}
+ for d in data:
+ bom_count.setdefault(d.bom_no, d.count)
+
+ return bom_count
+
+def get_args():
+ return frappe._dict({
+ "name": "",
+ "item": "",
+ "item_name": "",
+ "uom": ""
+ })
+
+def get_columns(filters):
+ return [{
+ "label": _("BOM ID"),
+ "options": "BOM",
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("BOM Item Code"),
+ "options": "Item",
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 110
+ }, {
+ "label": _("UOM"),
+ "options": "UOM",
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("Operation"),
+ "options": "Operation",
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "width": 120
+ }, {
+ "label": _("Workstation"),
+ "options": "Workstation",
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "width": 110
+ }, {
+ "label": _("Time (In Mins)"),
+ "fieldname": "time_in_mins",
+ "fieldtype": "Int",
+ "width": 140
+ }, {
+ "label": _("Subassembly BOM Count"),
+ "fieldname": "used_as_subassembly_items",
+ "fieldtype": "Int",
+ "width": 180
+ }]
+
+
diff --git a/erpnext/manufacturing/report/downtime_analysis/__init__.py b/erpnext/manufacturing/report/downtime_analysis/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/__init__.py
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
new file mode 100644
index 0000000..ff32dbe
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Downtime Analysis"] = {
+ "filters": [
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Datetime",
+ default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Datetime",
+ default: frappe.datetime.now_datetime(),
+ reqd: 1,
+ },
+ {
+ label: __("Machine"),
+ fieldname: "workstation",
+ fieldtype: "Link",
+ options: "Workstation"
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json
new file mode 100644
index 0000000..5edc778
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-04-20 18:26:04.345289",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-20 18:26:04.345289",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Downtime Analysis",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Downtime Entry",
+ "report_name": "Downtime Analysis",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
new file mode 100644
index 0000000..2b2be4f
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
@@ -0,0 +1,108 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import flt
+from frappe import _
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {}
+
+ fields = ["name", "workstation", "operator", "from_time", "to_time", "downtime", "stop_reason", "remarks"]
+
+ query_filters["from_time"] = (">=", filters.get("from_date"))
+ query_filters["to_time"] = ("<=", filters.get("to_date"))
+
+ if filters.get("workstation"):
+ query_filters["workstation"] = filters.get("workstation")
+
+ return frappe.get_all("Downtime Entry", fields= fields, filters=query_filters)
+
+def get_chart_data(data, columns):
+ labels = sorted(list(set([d.workstation for d in data])))
+
+ workstation_wise_data = {}
+ for d in data:
+ if d.workstation not in workstation_wise_data:
+ workstation_wise_data[d.workstation] = 0
+
+ workstation_wise_data[d.workstation] += flt(d.downtime, 2)
+
+ datasets = []
+ for label in labels:
+ datasets.append(workstation_wise_data.get(label, 0))
+
+ chart = {
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {"name": "Dataset 1", "values": datasets}
+ ]
+ },
+ "type": "bar"
+ }
+
+ return chart
+
+def get_columns(filters):
+ return [
+ {
+ "label": _("ID"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Downtime Entry",
+ "width": 100
+ },
+ {
+ "label": _("Machine"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 100
+ },
+ {
+ "label": _("Operator"),
+ "fieldname": "operator",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 130
+ },
+ {
+ "label": _("From Time"),
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "width": 160
+ },
+ {
+ "label": _("To Time"),
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "width": 160
+ },
+ {
+ "label": _("Downtime (In Mins)"),
+ "fieldname": "downtime",
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "label": _("Stop Reason"),
+ "fieldname": "stop_reason",
+ "fieldtype": "Data",
+ "width": 220
+ },
+ {
+ "label": _("Remarks"),
+ "fieldname": "remarks",
+ "fieldtype": "Text",
+ "width": 100
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/job_card_summary/__init__.py b/erpnext/manufacturing/report/job_card_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/__init__.py
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
new file mode 100644
index 0000000..b7e3071
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Job Card Summary"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
+ },
+ {
+ label: __("Sales Orders"),
+ fieldname: "sales_order",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Sales Order', txt);
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Item', txt);
+ }
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
new file mode 100644
index 0000000..9f08fc3
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
@@ -0,0 +1,34 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-20 12:00:21.436619",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-20 12:00:21.436619",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Job Card",
+ "report_name": "Job Card Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
new file mode 100644
index 0000000..ae1e4f3
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
@@ -0,0 +1,186 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import getdate, flt
+from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period)
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": ("<", 2)}
+
+ fields = ["name", "status", "work_order", "production_item", "item_name",
+ "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"]
+
+ for field in ["work_order", "workstation", "operation", "company"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ data = frappe.get_all("Job Card",
+ fields= fields, filters=query_filters)
+
+ if not data: return []
+
+ job_cards = [d.name for d in data]
+ job_card_time_details = {}
+ for job_card_data in frappe.get_all("Job Card Time Log",
+ fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"],
+ filters={"docstatus": ("<", 2), "parent": ("in", job_cards)}, group_by="parent"):
+ job_card_time_details[job_card_data.parent] = job_card_data
+
+ for d in data:
+ if d.status == "Material Transferred":
+ d.status = "Open"
+
+ if job_card_time_details.get(d.name):
+ d.from_time = job_card_time_details.get(d.name).from_time
+ d.to_time = job_card_time_details.get(d.name).to_time
+
+ return data
+
+def get_chart_data(job_card_details, filters):
+ labels, periodic_data = prepare_chart_data(job_card_details, filters)
+
+ pending, completed = [], []
+ datasets = []
+
+ for d in labels:
+ pending.append(periodic_data.get("Pending").get(d))
+ completed.append(periodic_data.get("Completed").get(d))
+
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "bar"
+ }
+
+ return chart
+
+def prepare_chart_data(job_card_details, filters):
+ labels = []
+
+ periodic_data = {
+ "Pending": {},
+ "Completed": {}
+ }
+
+ filters.range = "Monthly"
+
+ ranges = get_period_date_ranges(filters)
+ for from_date, end_date in ranges:
+ period = get_period(end_date, filters)
+ if period not in labels:
+ labels.append(period)
+
+ for d in job_card_details:
+ if getdate(d.from_time) >= from_date and getdate(d.to_time) <= end_date:
+ status = "Completed" if d.status == "Completed" else "Pending"
+
+ if periodic_data.get(status) and periodic_data.get(status).get(period):
+ periodic_data[status][period] += 1
+ else:
+ periodic_data[status][period] = 1
+
+ return labels, periodic_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Job Card",
+ "width": 100
+ },
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Work Order"),
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Workstation"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 110
+ },
+ {
+ "label": _("Operation"),
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "options": "Operation",
+ "width": 110
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Total Completed Qty"),
+ "fieldname": "total_completed_qty",
+ "fieldtype": "Float",
+ "width": 120
+ },
+ {
+ "label": _("From Time"),
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "width": 120
+ },
+ {
+ "label": _("To Time"),
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "width": 120
+ },
+ {
+ "label": _("Time Required (In Mins)"),
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
+ "width": 100
+ }
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py
index 7447a1f..f62cd25 100644
--- a/erpnext/manufacturing/report/production_analytics/production_analytics.py
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py
@@ -61,7 +61,7 @@
elif getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
-
+
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
@@ -148,4 +148,3 @@
-
diff --git a/erpnext/manufacturing/report/production_planning_report/__init__.py b/erpnext/manufacturing/report/production_planning_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/__init__.py
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
new file mode 100644
index 0000000..675b8a1
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
@@ -0,0 +1,111 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Production Planning Report"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
+ "fieldname":"based_on",
+ "label": __("Based On"),
+ "fieldtype": "Select",
+ "options": ["Sales Order", "Material Request", "Work Order"],
+ "default": "Sales Order",
+ "reqd": 1,
+ on_change: function() {
+ let filters = frappe.query_report.filters;
+ let based_on = frappe.query_report.get_filter_value('based_on');
+ let options = {
+ "Sales Order": ["Delivery Date", "Total Amount"],
+ "Material Request": ["Required Date"],
+ "Work Order": ["Planned Start Date"]
+ }
+
+ filters.forEach(d => {
+ if (d.fieldname == "order_by") {
+ d.df.options = options[based_on];
+ d.set_input(d.df.options)
+ }
+ });
+
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"docnames",
+ "label": __("Document Name"),
+ "fieldtype": "MultiSelectList",
+ "options": "Sales Order",
+ "get_data": function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let based_on = frappe.query_report.get_filter_value('based_on');
+ if (!based_on) return;
+
+ return frappe.db.get_link_options(based_on, txt);
+ },
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ "docstatus": 1,
+ "company": company
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"raw_material_warehouse",
+ "label": __("Raw Material Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "depends_on": "eval: doc.based_on != 'Work Order'",
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ "company": company
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"order_by",
+ "label": __("Order By"),
+ "fieldtype": "Select",
+ "options": ["Delivery Date", "Total Amount"],
+ "default": "Delivery Date"
+ },
+ {
+ "fieldname":"include_subassembly_raw_materials",
+ "label": __("Include Sub-assembly Raw Materials"),
+ "fieldtype": "Check",
+ "depends_on": "eval: doc.based_on != 'Work Order'",
+ "default": 0
+ },
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "production_item_name" && data && data.qty_to_manufacture > data.available_qty ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ if (column.fieldname == "production_item" && !data.name ) {
+ value = "";
+ }
+
+ if (column.fieldname == "raw_material_name" && data && data.required_qty > data.allotted_qty ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ return value;
+ },
+};
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.json b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
new file mode 100644
index 0000000..f37dad3
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-06 11:37:43.180095",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-06 11:38:05.789851",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Planning Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Production Planning Report",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
new file mode 100644
index 0000000..b5e6c6f
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -0,0 +1,371 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+# and bom_no is not null and bom_no !=''
+
+mapper = {
+ "Sales Order": {
+ "fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+ stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
+ `tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
+ "filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
+ and `tabSales Order`.per_delivered < 100.0"""
+ },
+ "Material Request": {
+ "fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+ stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
+ `tabMaterial Request Item`.schedule_date """,
+ "filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
+ and `tabMaterial Request`.material_request_type = 'Manufacture' """
+ },
+ "Work Order": {
+ "fields": """ production_item, item_name as production_item_name, planned_start_date,
+ stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
+ "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
+ },
+}
+
+order_mapper = {
+ "Sales Order": {
+ "Delivery Date": "`tabSales Order Item`.delivery_date asc",
+ "Total Amount": "`tabSales Order`.base_grand_total desc"
+ },
+ "Material Request": {
+ "Required Date": "`tabMaterial Request Item`.schedule_date asc"
+ },
+ "Work Order": {
+ "Planned Start Date": "planned_start_date asc"
+ }
+}
+
+def execute(filters=None):
+ return ProductionPlanReport(filters).execute_report()
+
+class ProductionPlanReport(object):
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+ self.raw_materials_dict = {}
+ self.data = []
+
+ def execute_report(self):
+ self.get_open_orders()
+ self.get_raw_materials()
+ self.get_item_details()
+ self.get_bin_details()
+ self.get_purchase_details()
+ self.prepare_data()
+ self.get_columns()
+
+ return self.columns, self.data
+
+ def get_open_orders(self):
+ doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
+ else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
+
+ filters = mapper.get(self.filters.based_on)["filters"]
+ filters = self.prepare_other_conditions(filters, self.filters.based_on)
+ order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
+
+ self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
+ WHERE {filters} {order_by}""".format(
+ doctype = doctype,
+ filters = filters,
+ order_by = order_by,
+ fields = mapper.get(self.filters.based_on)["fields"]
+ ), tuple(self.filters.docnames), as_dict=1)
+
+ def prepare_other_conditions(self, filters, doctype):
+ if self.filters.docnames:
+ field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
+ filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
+
+ if doctype != "Work Order":
+ filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
+
+ if self.filters.company:
+ filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
+
+ return filters
+
+ def get_raw_materials(self):
+ if not self.orders: return
+ self.warehouses = [d.warehouse for d in self.orders]
+ self.item_codes = [d.production_item for d in self.orders]
+
+ if self.filters.based_on == "Work Order":
+ work_orders = [d.name for d in self.orders]
+
+ raw_materials = frappe.get_all("Work Order Item",
+ fields=["parent", "item_code", "item_name as raw_material_name",
+ "source_warehouse as warehouse", "required_qty"],
+ filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
+ self.warehouses.extend([d.source_warehouse for d in raw_materials])
+
+ else:
+ bom_nos = []
+
+ for d in self.orders:
+ bom_no = d.bom_no or frappe.get_cached_value("Item", d.production_item, "default_bom")
+
+ if not d.bom_no:
+ d.bom_no = bom_no
+
+ bom_nos.append(bom_no)
+
+ bom_doctype = ("BOM Explosion Item"
+ if self.filters.include_subassembly_raw_materials else "BOM Item")
+
+ qty_field = ("qty_consumed_per_unit"
+ if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
+
+ raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
+ bom_item.item_name as raw_material_name, {0} as required_qty
+ FROM
+ `tabBOM` as bom, `tab{1}` as bom_item
+ WHERE
+ bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
+ """.format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
+
+ if not raw_materials: return
+
+ self.item_codes.extend([d.item_code for d in raw_materials])
+
+ for d in raw_materials:
+ if d.parent not in self.raw_materials_dict:
+ self.raw_materials_dict.setdefault(d.parent, [])
+
+ rows = self.raw_materials_dict[d.parent]
+ rows.append(d)
+
+ def get_item_details(self):
+ if not (self.orders and self.item_codes): return
+
+ self.item_details = {}
+ for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
+ filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
+ self.item_details[d.parent] = d
+
+ def get_bin_details(self):
+ if not (self.orders and self.raw_materials_dict): return
+
+ self.bin_details = {}
+ self.mrp_warehouses = []
+ if self.filters.raw_material_warehouse:
+ self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
+ self.warehouses.extend(self.mrp_warehouses)
+
+ for d in frappe.get_all("Bin",
+ fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
+ filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
+ key = (d.item_code, d.warehouse)
+ if key not in self.bin_details:
+ self.bin_details.setdefault(key, d)
+
+ def get_purchase_details(self):
+ if not (self.orders and self.raw_materials_dict): return
+
+ self.purchase_details = {}
+
+ for d in frappe.get_all("Purchase Order Item",
+ fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
+ filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
+ group_by = "item_code, warehouse"):
+ key = (d.item_code, d.warehouse)
+ if key not in self.purchase_details:
+ self.purchase_details.setdefault(key, d)
+
+ def prepare_data(self):
+ if not self.orders: return
+
+ for d in self.orders:
+ key = d.name if self.filters.based_on == "Work Order" else d.bom_no
+
+ if not self.raw_materials_dict.get(key): continue
+
+ bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
+ d.update({
+ "for_warehouse": d.warehouse,
+ "available_qty": 0
+ })
+
+ if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
+ d.available_qty = (bin_data.get("actual_qty")
+ if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
+
+ bin_data["actual_qty"] -= d.available_qty
+
+ self.update_raw_materials(d, key)
+
+ def update_raw_materials(self, data, key):
+ self.index = 0
+ self.raw_materials_dict.get(key)
+
+ warehouses = self.mrp_warehouses or []
+ for d in self.raw_materials_dict.get(key):
+ if self.filters.based_on != "Work Order":
+ d.required_qty = d.required_qty * data.qty_to_manufacture
+
+ if not warehouses:
+ warehouses = [data.warehouse]
+
+ if self.filters.based_on == "Work Order" and d.warehouse:
+ warehouses = [d.warehouse]
+ else:
+ item_details = self.item_details.get(d.item_code)
+ if item_details:
+ warehouses = [item_details["default_warehouse"]]
+
+ d.remaining_qty = d.required_qty
+ self.pick_materials_from_warehouses(d, data, warehouses)
+
+ if (d.remaining_qty and self.filters.raw_material_warehouse
+ and d.remaining_qty != d.required_qty):
+ row = self.get_args()
+ d.warehouse = self.filters.raw_material_warehouse
+ d.required_qty = d.remaining_qty
+ d.allotted_qty = 0
+ row.update(d)
+ self.data.append(row)
+
+ def pick_materials_from_warehouses(self, args, order_data, warehouses):
+ for index, warehouse in enumerate(warehouses):
+ if not args.remaining_qty: return
+
+ row = self.get_args()
+
+ key = (args.item_code, warehouse)
+ bin_data = self.bin_details.get(key)
+
+ if bin_data:
+ row.update(bin_data)
+
+ args.allotted_qty = 0
+ if bin_data and bin_data.get("actual_qty") > 0:
+ args.allotted_qty = (bin_data.get("actual_qty")
+ if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
+
+ args.remaining_qty -= args.allotted_qty
+ bin_data["actual_qty"] -= args.allotted_qty
+
+ if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
+ or not self.mrp_warehouses):
+ if not self.index:
+ row.update(order_data)
+ self.index += 1
+
+ args.warehouse = warehouse
+ row.update(args)
+ if self.purchase_details.get(key):
+ row.update(self.purchase_details.get(key))
+
+ self.data.append(row)
+
+ def get_args(self):
+ return frappe._dict({
+ "work_order": "",
+ "sales_order": "",
+ "production_item": "",
+ "production_item_name": "",
+ "qty_to_manufacture": "",
+ "produced_qty": ""
+ })
+
+ def get_columns(self):
+ based_on = self.filters.based_on
+
+ self.columns = [{
+ "label": _("ID"),
+ "options": based_on,
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "width": 100
+ }, {
+ "label": _("Item Code"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ }, {
+ "label": _("Item Name"),
+ "fieldname": "production_item_name",
+ "fieldtype": "Data",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "for_warehouse",
+ "fieldtype": "Link",
+ "width": 100
+ }, {
+ "label": _("Order Qty"),
+ "fieldname": "qty_to_manufacture",
+ "fieldtype": "Float",
+ "width": 80
+ }, {
+ "label": _("Available"),
+ "fieldname": "available_qty",
+ "fieldtype": "Float",
+ "width": 80
+ }]
+
+ fieldname, fieldtype = "delivery_date", "Date"
+ if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
+ fieldname, fieldtype = "base_grand_total", "Currency"
+ elif self.filters.based_on == "Material Request":
+ fieldname = "schedule_date"
+ elif self.filters.based_on == "Work Order":
+ fieldname = "planned_start_date"
+
+ self.columns.append({
+ "label": _(self.filters.order_by),
+ "fieldname": fieldname,
+ "fieldtype": fieldtype,
+ "width": 100
+ })
+
+ self.columns.extend([{
+ "label": _("Raw Material Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ }, {
+ "label": _("Raw Material Name"),
+ "fieldname": "raw_material_name",
+ "fieldtype": "Data",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 110
+ }, {
+ "label": _("Required Qty"),
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }, {
+ "label": _("Allotted Qty"),
+ "fieldname": "allotted_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }, {
+ "label": _("Expected Arrival Date"),
+ "fieldname": "arrival_date",
+ "fieldtype": "Date",
+ "width": 160
+ }, {
+ "label": _("Arrival Quantity"),
+ "fieldname": "arrival_qty",
+ "fieldtype": "Float",
+ "width": 140
+ }])
+
+def document_query(doctype, txt, searchfield, start, page_len, filters):
+ pass
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/__init__.py b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js
new file mode 100644
index 0000000..d4587aa
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Quality Inspection Summary"] = {
+ "filters": [
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Accepted", "Rejected"]
+ },
+ {
+ label: __("Item Code"),
+ fieldname: "item_code",
+ fieldtype: "Link",
+ options: "Item"
+ },
+ {
+ label: __("Inspected By"),
+ fieldname: "inspected_by",
+ fieldtype: "Link",
+ options: "User"
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json
new file mode 100644
index 0000000..48226e6
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-26 18:23:53.475110",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-26 18:24:50.529940",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Quality Inspection",
+ "report_name": "Quality Inspection Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Quality Manager"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
new file mode 100644
index 0000000..6192632
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data , None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": ("<", 2)}
+
+ fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size",
+ "inspection_type", "reference_type", "reference_name", "inspected_by"]
+
+ for field in ["status", "item_code", "status", "inspected_by"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ query_filters["report_date"] = (">=", filters.get("from_date"))
+ query_filters["report_date"] = ("<=", filters.get("to_date"))
+
+ return frappe.get_all("Quality Inspection",
+ fields= fields, filters=query_filters, order_by="report_date asc")
+
+def get_chart_data(periodic_data, columns):
+ labels = ["Rejected", "Accepted"]
+
+ status_wise_data = {
+ "Accepted": 0,
+ "Rejected": 0
+ }
+
+ datasets = []
+
+ for d in periodic_data:
+ status_wise_data[d.status] += 1
+
+ datasets.append({'name':'Qty Wise Chart',
+ 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ {
+ "label": _("Report Date"),
+ "fieldname": "report_date",
+ "fieldtype": "Date",
+ "width": 150
+ }
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 130
+ },
+ {
+ "label": _("Sample Size"),
+ "fieldname": "sample_size",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Inspection Type"),
+ "fieldname": "inspection_type",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Document Type"),
+ "fieldname": "reference_type",
+ "fieldtype": "Data",
+ "width": 90
+ },
+ {
+ "label": _("Document Name"),
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "options": "reference_type",
+ "width": 150
+ },
+ {
+ "label": _("Inspected By"),
+ "fieldname": "inspected_by",
+ "fieldtype": "Link",
+ "options": "User",
+ "width": 150
+ }
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_summary/__init__.py b/erpnext/manufacturing/report/work_order_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/__init__.py
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
new file mode 100644
index 0000000..ec9fe35
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
@@ -0,0 +1,65 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Work Order Summary"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Not Started", "In Process", "Completed", "Stopped"]
+ },
+ {
+ label: __("Sales Orders"),
+ fieldname: "sales_order",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Sales Order', txt);
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Item', txt);
+ }
+ },
+ {
+ label: __("Age"),
+ fieldname:"age",
+ fieldtype: "Int",
+ default: "0"
+ },
+ {
+ label: __("Charts Based On"),
+ fieldname:"charts_based_on",
+ fieldtype: "Select",
+ options: ["Status", "Age", "Quantity"],
+ default: "Status"
+ },
+ ]
+};
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.json b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json
new file mode 100644
index 0000000..0d093e2
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-17 17:07:56.830358",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-19 16:59:47.979278",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Work Order Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
new file mode 100644
index 0000000..bc09ed4
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -0,0 +1,270 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import date_diff, today, getdate, flt
+from frappe import _
+from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period)
+
+def execute(filters=None):
+ columns, data = [], []
+
+ if not filters.get("age"):
+ filters["age"] = 0
+
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": 1}
+
+ fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
+ "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
+
+ for field in ["sales_order", "production_item", "status", "company"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ query_filters["planned_start_date"] = (">=", filters.get("from_date"))
+ query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
+
+ data = frappe.get_all("Work Order",
+ fields= fields, filters=query_filters, order_by="planned_start_date asc")
+
+ res = []
+ for d in data:
+ start_date = d.actual_start_date or d.planned_start_date
+ d.age = 0
+
+ if d.status != 'Completed':
+ d.age = date_diff(today(), start_date)
+
+ if filters.get("age") <= d.age:
+ res.append(d)
+
+ return res
+
+def get_chart_data(data, filters):
+ if filters.get("charts_based_on") == "Status":
+ return get_chart_based_on_status(data)
+ elif filters.get("charts_based_on") == "Age":
+ return get_chart_based_on_age(data)
+ else:
+ return get_chart_based_on_qty(data, filters)
+
+def get_chart_based_on_status(data):
+ labels = ["Not Started", "In Process", "Stopped", "Completed"]
+
+ status_wise_data = {
+ "Not Started": 0,
+ "In Process": 0,
+ "Stopped": 0,
+ "Completed": 0
+ }
+
+ for d in data:
+ if d.status == "In Process" and d.produced_qty:
+ status_wise_data["Completed"] += d.produced_qty
+
+ status_wise_data[d.status] += d.qty
+
+ values = [status_wise_data["Not Started"], status_wise_data["In Process"],
+ status_wise_data["Stopped"], status_wise_data["Completed"]]
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_chart_based_on_age(data):
+ labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"]
+
+ age_wise_data = {
+ "0-30 Days": 0,
+ "30-60 Days": 0,
+ "60-90 Days": 0,
+ "90 Above": 0
+ }
+
+ for d in data:
+ if d.age > 0 and d.age <= 30:
+ age_wise_data["0-30 Days"] += 1
+ elif d.age > 30 and d.age <= 60:
+ age_wise_data["30-60 Days"] += 1
+ elif d.age > 60 and d.age <= 90:
+ age_wise_data["60-90 Days"] += 1
+ else:
+ age_wise_data["90 Above"] += 1
+
+ values = [age_wise_data["0-30 Days"], age_wise_data["30-60 Days"],
+ age_wise_data["60-90 Days"], age_wise_data["90 Above"]]
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_chart_based_on_qty(data, filters):
+ labels, periodic_data = prepare_chart_data(data, filters)
+
+ pending, completed = [], []
+ datasets = []
+
+ for d in labels:
+ pending.append(periodic_data.get("Pending").get(d))
+ completed.append(periodic_data.get("Completed").get(d))
+
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ }
+ }
+
+ return chart
+
+def prepare_chart_data(data, filters):
+ labels = []
+
+ periodic_data = {
+ "Pending": {},
+ "Completed": {}
+ }
+
+ filters.range = "Monthly"
+
+ ranges = get_period_date_ranges(filters)
+ for from_date, end_date in ranges:
+ period = get_period(end_date, filters)
+ if period not in labels:
+ labels.append(period)
+
+ if period not in periodic_data["Pending"]:
+ periodic_data["Pending"][period] = 0
+
+ if period not in periodic_data["Completed"]:
+ periodic_data["Completed"][period] = 0
+
+ for d in data:
+ if getdate(d.planned_start_date) >= from_date and getdate(d.planned_start_date) <= end_date:
+ periodic_data["Pending"][period] += (flt(d.qty) - flt(d.produced_qty))
+ periodic_data["Completed"][period] += flt(d.produced_qty)
+
+ return labels, periodic_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Produce Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Produced Qty"),
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 90
+ },
+ {
+ "label": _("Planned Start Date"),
+ "fieldname": "planned_start_date",
+ "fieldtype": "Date",
+ "width": 150
+ },
+ {
+ "label": _("Planned End Date"),
+ "fieldname": "planned_end_date",
+ "fieldtype": "Date",
+ "width": 150
+ }
+ ])
+
+ if filters.get("status") != 'Not Started':
+ columns.extend([
+ {
+ "label": _("Actual Start Date"),
+ "fieldname": "actual_start_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Actual End Date"),
+ "fieldname": "actual_end_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Age"),
+ "fieldname": "age",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ ])
+
+ if filters.get("status") == 'Completed':
+ columns.extend([
+ {
+ "label": _("Lead Time (in mins)"),
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7b66a2d..bb9bb97 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -687,3 +687,6 @@
erpnext.patches.v12_0.set_serial_no_status
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
+erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
+erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes
+erpnext.patches.v12_0.update_bom_in_so_mr
diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py
new file mode 100644
index 0000000..309ae4c
--- /dev/null
+++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("stock", "doctype", "material_request_item")
+ frappe.reload_doc("selling", "doctype", "sales_order_item")
+
+ for doctype in ["Sales Order", "Material Request"]:
+ condition = " and child_doc.stock_qty > child_doc.produced_qty"
+ if doctype == "Material Request":
+ condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
+
+ frappe.db.sql(""" UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
+ SET
+ child_doc.bom_no = item.default_bom
+ WHERE
+ child_doc.item_code = item.name and child_doc.docstatus < 2
+ and item.default_bom is not null and item.default_bom != '' {cond}
+ """.format(doc = doctype, cond = condition))
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/__init__.py b/erpnext/patches/v13_0/__init__.py
index e69de29..baffc48 100644
--- a/erpnext/patches/v13_0/__init__.py
+++ b/erpnext/patches/v13_0/__init__.py
@@ -0,0 +1 @@
+from __future__ import unicode_literals
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
new file mode 100644
index 0000000..2c646b1
--- /dev/null
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ company = frappe.db.get_single_value('Global Defaults', 'default_company')
+ doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Patient Appointment', 'Patient Encounter', 'Vital Signs']
+ for entry in doctypes:
+ if frappe.db.exists('DocType', entry):
+ frappe.db.sql("update `tab{dt}` set company = '{company}' where ifnull(company, '') = ''".format(dt=entry, company=company))
diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
new file mode 100644
index 0000000..331c559
--- /dev/null
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -0,0 +1,42 @@
+
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import add_to_date
+from frappe.utils.dashboard import get_config, make_records
+
+def execute():
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+ frappe.reload_doc("manufacturing", "doctype", "job_card")
+
+ data = frappe.get_all("Work Order",
+ filters = {
+ "docstatus": 1,
+ "status": ("in", ["In Process", "Completed"])
+ })
+
+ for d in data:
+ doc = frappe.get_doc("Work Order", d.name)
+ doc.set_actual_dates()
+ doc.db_set("actual_start_date", doc.actual_start_date, update_modified=False)
+
+ if doc.status == "Completed":
+ frappe.db.set_value("Work Order", d.name, {
+ "actual_end_date": doc.actual_end_date,
+ "lead_time": doc.lead_time
+ }, update_modified=False)
+
+ if not doc.planned_end_date:
+ planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time)
+ doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False)
+
+ frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo
+ SET
+ jc.production_item = wo.production_item, jc.item_name = wo.item_name
+ WHERE
+ jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
+ """)
\ No newline at end of file
diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js
index cd767f5..c786a86 100644
--- a/erpnext/public/js/purchase_trends_filters.js
+++ b/erpnext/public/js/purchase_trends_filters.js
@@ -51,7 +51,10 @@
{ "value": "Supplier Group", "label": __("Supplier Group") },
{ "value": "Project", "label": __("Project") }
],
- "default": "Item"
+ "default": "Item",
+ "dashboard_config": {
+ "read_only": 1
+ }
},
{
"fieldname":"group_by",
diff --git a/erpnext/public/js/sales_trends_filters.js b/erpnext/public/js/sales_trends_filters.js
index b272fdd..b9c4dca 100644
--- a/erpnext/public/js/sales_trends_filters.js
+++ b/erpnext/public/js/sales_trends_filters.js
@@ -27,7 +27,10 @@
{ "value": "Territory", "label": __("Territory") },
{ "value": "Project", "label": __("Project") }
],
- "default": "Item"
+ "default": "Item",
+ "dashboard_config": {
+ "read_only": 1,
+ }
},
{
"fieldname":"group_by",
diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
index 0849fd7..7691fe3 100644
--- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
+++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
@@ -1,10 +1,12 @@
{
- "autoname": "format:MTNG-{date}",
+ "actions": [],
+ "autoname": "naming_series:",
"creation": "2018-10-15 16:25:41.548432",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "naming_series",
"date",
"cb_00",
"status",
@@ -53,9 +55,16 @@
"fieldname": "sb_01",
"fieldtype": "Section Break",
"label": "Minutes"
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "MTNG-.YYYY.-.MM.-.DD.-"
}
],
- "modified": "2019-07-13 19:57:40.500541",
+ "links": [],
+ "modified": "2020-05-19 13:18:59.821740",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Meeting",
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index fd1cc58..43b1ea8 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -117,12 +117,18 @@
else:
row.append(invoice_details.get(fieldname))
taxable_value = 0
+
+ if invoice in self.cgst_igst_invoices:
+ division_factor = 2
+ else:
+ division_factor = 1
+
for item_code, net_amount in self.invoice_items.get(invoice).items():
- if item_code in items:
- if self.item_tax_rate.get(invoice) and tax_rate in self.item_tax_rate.get(invoice, {}).get(item_code, []):
- taxable_value += abs(net_amount)
- elif not self.item_tax_rate.get(invoice):
- taxable_value += abs(net_amount)
+ if item_code in items:
+ if self.item_tax_rate.get(invoice) and tax_rate/division_factor in self.item_tax_rate.get(invoice, {}).get(item_code, []):
+ taxable_value += abs(net_amount)
+ elif not self.item_tax_rate.get(invoice):
+ taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value]
@@ -196,7 +202,7 @@
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
sum(i.get('base_net_amount', 0) for i in items
- if i.item_code == d.item_code and i.parent == d.parent))
+ if i.item_code == d.item_code and i.parent == d.parent))
item_tax_rate = {}
@@ -221,6 +227,8 @@
self.items_based_on_tax_rate = {}
self.invoice_cess = frappe._dict()
+ self.cgst_igst_invoices = []
+
unidentified_gst_accounts = []
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
if account in self.gst_accounts.cess_account:
@@ -243,6 +251,8 @@
tax_rate = tax_amounts[0]
if cgst_or_sgst:
tax_rate *= 2
+ if parent not in self.cgst_igst_invoices:
+ self.cgst_igst_invoices.append(parent)
rate_based_dict = self.items_based_on_tax_rate\
.setdefault(parent, {}).setdefault(tax_rate, [])
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 45a43c5..705dcb8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -34,6 +34,15 @@
}
};
})
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ "item": row.item_code
+ }
+ }
+ });
},
refresh: function(frm) {
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 73f233c..e593499 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -72,6 +72,8 @@
"against_blanket_order",
"blanket_order",
"blanket_order_rate",
+ "manufacturing_section_section",
+ "bom_no",
"planning_section",
"projected_qty",
"actual_qty",
@@ -212,6 +214,7 @@
"fieldtype": "Link",
"label": "UOM",
"options": "UOM",
+ "print_hide": 0,
"reqd": 1
},
{
@@ -764,12 +767,24 @@
"fieldname": "against_blanket_order",
"fieldtype": "Check",
"label": "Against Blanket Order"
- }
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "manufacturing_section_section",
+ "fieldtype": "Section Break",
+ "label": "Manufacturing Section"
+ }
],
"idx": 1,
"istable": 1,
- "links": [],
- "modified": "2020-03-05 14:20:28.085117",
+ "modified": "2020-05-15 18:13:43.006493",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/stock/dashboard_chart_source/__init__.py b/erpnext/stock/dashboard_chart_source/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/__init__.py
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js
new file mode 100644
index 0000000..a413754
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Warehouse wise Stock Value"] = {
+ method: "erpnext.stock.dashboard_chart_source.warehouse_wise_stock_value.warehouse_wise_stock_value.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json
new file mode 100644
index 0000000..6d967c0
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2020-05-14 14:27:44.108017",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2020-05-14 14:27:44.108017",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Warehouse wise Stock Value",
+ "owner": "Administrator",
+ "source_name": "Warehouse wise Stock Value ",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
new file mode 100644
index 0000000..374a34e
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe, json
+from frappe import _
+from frappe.utils.dashboard import cache_source
+from erpnext.stock.utils import get_stock_value_from_bin
+
+@frappe.whitelist()
+@cache_source
+def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ labels, datapoints = [], []
+ filters = frappe.parse_json(filters)
+
+ warehouse_filters = [['is_group', '=', 0]]
+ if filters and filters.get("company"):
+ warehouse_filters.append(['company', '=', filters.get("company")])
+
+ warehouses = frappe.get_list("Warehouse", fields=['name'], filters=warehouse_filters, order_by='name')
+
+ for wh in warehouses:
+ balance = get_stock_value_from_bin(warehouse=wh.name)
+ wh["balance"] = balance[0][0]
+
+ warehouses = [x for x in warehouses if not (x.get('balance') == None)]
+
+ if not warehouses:
+ return []
+
+ sorted_warehouse_map = sorted(warehouses, key = lambda i: i['balance'], reverse=True)
+
+ if len(sorted_warehouse_map) > 10:
+ sorted_warehouse_map = sorted_warehouse_map[:10]
+
+ for warehouse in sorted_warehouse_map:
+ labels.append(_(warehouse.get("name")))
+ datapoints.append(warehouse.get("balance"))
+
+ return{
+ "labels": labels,
+ "datasets": [{
+ "name": _("Stock Value"),
+ "values": datapoints
+ }],
+ "type": "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_fixtures.py b/erpnext/stock/dashboard_fixtures.py
new file mode 100644
index 0000000..0f1fd12
--- /dev/null
+++ b/erpnext/stock/dashboard_fixtures.py
@@ -0,0 +1,175 @@
+# 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": "Stock",
+ "dashboard_name": "Stock",
+ "charts": [
+ { "chart": "Warehouse wise Stock Value", "width": "Full"},
+ { "chart": "Purchase Receipt Trends", "width": "Half"},
+ { "chart": "Delivery Trends", "width": "Half"},
+ { "chart": "Oldest Items", "width": "Half"},
+ { "chart": "Item Shortage Summary", "width": "Half"}
+ ],
+ "cards": [
+ { "card": "Total Active Items"},
+ { "card": "Total Warehouses"},
+ { "card": "Total Stock Value"}
+ ]
+ }]
+
+def get_charts():
+ return [
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Purchase Receipt Trends",
+ "time_interval": "Monthly",
+ "chart_name": _("Purchase Receipt Trends"),
+ "timespan": "Last Year",
+ "color": "#7b933d",
+ "value_based_on": "base_net_total",
+ "filters_json": json.dumps([["Purchase Receipt", "docstatus", "=", 1]]),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Purchase Receipt",
+ "type": "Bar",
+ "width": "Half",
+ "is_public": 1
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Delivery Trends",
+ "time_interval": "Monthly",
+ "chart_name": _("Delivery Trends"),
+ "timespan": "Last Year",
+ "color": "#7b933d",
+ "value_based_on": "base_net_total",
+ "filters_json": json.dumps([["Delivery Note", "docstatus", "=", 1]]),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Delivery Note",
+ "type": "Bar",
+ "width": "Half",
+ "is_public": 1
+ },
+ {
+ "name": "Warehouse wise Stock Value",
+ "chart_name": _("Warehouse wise Stock Value"),
+ "chart_type": "Custom",
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({}),
+ "is_custom": 0,
+ "is_public": 1,
+ "owner": "Administrator",
+ "source": "Warehouse wise Stock Value",
+ "type": "Bar"
+ },
+ {
+ "name": "Oldest Items",
+ "chart_name": _("Oldest Items"),
+ "chart_type": "Report",
+ "custom_options": json.dumps({
+ "colors": ["#5e64ff"]
+ }),
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "to_date": nowdate(),
+ "show_warehouse_wise_stock": 0
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Stock Ageing",
+ "type": "Bar"
+ },
+ {
+ "name": "Item Shortage Summary",
+ "chart_name": _("Item Shortage Summary"),
+ "chart_type": "Report",
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Item Shortage Report",
+ "type": "Bar"
+ }
+ ]
+
+def get_number_cards():
+ return [
+ {
+ "name": "Total Active Items",
+ "label": _("Total Active Items"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Item",
+ "filters_json": json.dumps([["Item", "disabled", "=", 0]]),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "name": "Total Warehouses",
+ "label": _("Total Warehouses"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Warehouse",
+ "filters_json": json.dumps([["Warehouse", "disabled", "=", 0]]),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "name": "Total Stock Value",
+ "label": _("Total Stock Value"),
+ "function": "Sum",
+ "aggregate_function_based_on": "stock_value",
+ "doctype": "Number Card",
+ "document_type": "Bin",
+ "filters_json": json.dumps([]),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 38475a6..4506664 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -2,8 +2,13 @@
"cards": [
{
"hidden": 0,
+ "label": "Items and Pricing",
+ "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Alternative\",\n \"name\": \"Item Alternative\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Manufacturer\",\n \"name\": \"Item Manufacturer\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
"label": "Stock Transactions",
- "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -13,12 +18,7 @@
{
"hidden": 0,
"label": "Settings",
- "links": "[\n {\n \"label\": \"Stock Settings\",\n \"name\": \"Stock Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Warehouse\",\n \"name\": \"Warehouse\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Unit of Measure (UOM)\",\n \"name\": \"UOM\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Brand\",\n \"name\": \"Brand\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Attribute\",\n \"name\": \"Item Attribute\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Items and Pricing",
- "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Alternative\",\n \"name\": \"Item Alternative\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Manufacturer\",\n \"name\": \"Item Manufacturer\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"label\": \"Stock Settings\",\n \"name\": \"Stock Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Warehouse\",\n \"name\": \"Warehouse\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Unit of Measure (UOM)\",\n \"name\": \"UOM\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Brand\",\n \"name\": \"Brand\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Attribute\",\n \"name\": \"Item Attribute\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -41,34 +41,46 @@
"links": "[\n {\n \"dependencies\": [\n \"Material Request\"\n ],\n \"doctype\": \"Material Request\",\n \"is_query_report\": true,\n \"label\": \"Requested Items To Be Transferred\",\n \"name\": \"Requested Items To Be Transferred\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Ledger Entry\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Batch Item Expiry Status\",\n \"name\": \"Batch Item Expiry Status\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Price List\"\n ],\n \"doctype\": \"Price List\",\n \"is_query_report\": true,\n \"label\": \"Item Prices\",\n \"name\": \"Item Prices\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Itemwise Recommended Reorder Level\",\n \"name\": \"Itemwise Recommended Reorder Level\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item Variant Details\",\n \"name\": \"Item Variant Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Ledger Entry\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock and Account Value Comparison\",\n \"name\": \"Stock and Account Value Comparison\",\n \"type\": \"report\"\n }\n]"
}
],
+ "cards_label": "Masters & Reports",
"category": "Modules",
- "charts": [],
+ "charts": [
+ {
+ "chart_name": "Warehouse wise Stock Value"
+ }
+ ],
"creation": "2020-03-02 15:43:10.096528",
"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": "Stock",
- "modified": "2020-04-01 11:28:51.148421",
+ "modified": "2020-05-19 17:36:08.185652",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
+ "onboarding": "Stock",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#cef6d1",
+ "format": "{} available",
"label": "Item",
"link_to": "Item",
+ "stats_filter": "{\n \"disabled\" : 0\n}",
"type": "DocType"
},
{
- "label": "Pricing Rule",
- "link_to": "Pricing Rule",
+ "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"
},
{
@@ -77,6 +89,22 @@
"type": "DocType"
},
{
+ "color": "#ffe8cd",
+ "format": "{} to Bill",
+ "label": "Purchase Receipt",
+ "link_to": "Purchase Receipt",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#ffe8cd",
+ "format": "{} to Bill",
+ "label": "Delivery Note",
+ "link_to": "Delivery Note",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
+ "type": "DocType"
+ },
+ {
"label": "Stock Ledger",
"link_to": "Stock Ledger",
"type": "Report"
@@ -85,6 +113,12 @@
"label": "Stock Balance",
"link_to": "Stock Balance",
"type": "Report"
+ },
+ {
+ "label": "Stock Dashboard",
+ "link_to": "Stock",
+ "type": "Dashboard"
}
- ]
+ ],
+ "shortcuts_label": "Quick Access"
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 9f5dee9..84d2057 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -24,10 +24,10 @@
"return_against",
"customer_po_details",
"po_no",
- "section_break_18",
- "pick_list",
"column_break_17",
"po_date",
+ "section_break_18",
+ "pick_list",
"contact_info",
"shipping_address_name",
"shipping_address",
@@ -296,7 +296,6 @@
},
{
"collapsible": 1,
- "collapsible_depends_on": "po_no",
"fieldname": "customer_po_details",
"fieldtype": "Section Break",
"label": "Customer PO Details"
@@ -304,7 +303,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order No",
"no_copy": 1,
"oldfieldname": "po_no",
@@ -318,7 +317,6 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval:doc.po_no",
"fieldname": "po_date",
"fieldtype": "Date",
"label": "Customer's Purchase Order Date",
@@ -326,7 +324,6 @@
"oldfieldtype": "Data",
"print_hide": 1,
"print_width": "100px",
- "read_only": 1,
"width": "100px"
},
{
@@ -1256,7 +1253,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-17 12:51:41.288600",
+ "modified": "2020-05-19 17:03:45.880106",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index db8bffd..3562181 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -30,7 +30,16 @@
return {
filters: {'company': doc.company}
};
- })
+ });
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ "item": row.item_code
+ }
+ }
+ });
},
onload: function(frm) {
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index df140ff..32bd4a0 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -53,6 +53,8 @@
"dimension_col_break",
"cost_center",
"section_break_37",
+ "bom_no",
+ "section_break_46",
"page_break"
],
"fields": [
@@ -371,8 +373,10 @@
"label": "Image"
},
{
+ "depends_on": "eval:parent.material_request_type == \"Manufacture\"",
"fieldname": "section_break_37",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Manufacturing"
},
{
"fieldname": "received_qty",
@@ -428,12 +432,24 @@
"fieldtype": "Link",
"label": "Source Warehouse (Material Transfer)",
"options": "Warehouse"
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_46",
+ "fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-05-01 09:00:00.992835",
+ "modified": "2020-05-15 09:00:00.992835",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index a9f3cd0..c951066 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2013-04-30 13:13:03",
"doctype": "DocType",
@@ -8,6 +9,7 @@
"field_order": [
"naming_series",
"report_date",
+ "status",
"column_break_4",
"inspection_type",
"reference_type",
@@ -20,17 +22,16 @@
"column_break1",
"item_name",
"description",
- "status",
+ "bom_no",
+ "specification_details",
+ "quality_inspection_template",
+ "readings",
"section_break_14",
"inspected_by",
"verified_by",
- "bom_no",
"column_break_17",
"remarks",
- "amended_from",
- "specification_details",
- "quality_inspection_template",
- "readings"
+ "amended_from"
],
"fields": [
{
@@ -231,7 +232,8 @@
"icon": "fa fa-search",
"idx": 1,
"is_submittable": 1,
- "modified": "2019-07-12 12:07:23.153698",
+ "links": [],
+ "modified": "2020-04-26 17:50:25.068222",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index be2dd52..18d6853 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -732,11 +732,15 @@
pro_doc = frappe.get_doc("Work Order", self.work_order)
_validate_work_order(pro_doc)
pro_doc.run_method("update_status")
+
if self.fg_completed_qty:
pro_doc.run_method("update_work_order_qty")
if self.purpose == "Manufacture":
pro_doc.run_method("update_planned_qty")
+ if not pro_doc.operations:
+ pro_doc.set_actual_dates()
+
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
i.has_batch_no, i.sample_quantity, i.has_serial_no,
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js
index cc0e2cf..6f97572 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.js
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.js
@@ -15,3 +15,37 @@
frm.set_query("sample_retention_warehouse", filters);
}
});
+
+frappe.tour['Stock Settings'] = [
+ {
+ fieldname: "item_naming_by",
+ title: __("Item 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: "default_warehouse",
+ title: __("Default Warehouse"),
+ description: __("Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.")
+ },
+ {
+ fieldname: "allow_negative_stock",
+ title: __("Allow Negative Stock"),
+ description: __("This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.")
+
+ },
+ {
+ fieldname: "valuation_method",
+ title: __("Valuation Method"),
+ description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/stock/articles/item-valuation-fifo-and-moving-average'>here</a>" + __(" to know more about them.")
+ },
+ {
+ fieldname: "show_barcode_field",
+ title: __("Show Barcode Field"),
+ description: __("Show 'Scan Barcode' field above every child table to insert Items with ease.")
+ },
+ {
+ fieldname: "automatically_set_serial_nos_based_on_fifo",
+ title: __("Automatically Set Serial Nos based on FIFO"),
+ description: __("Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.")
+ }
+];
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 11b6403..0ed3b27 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -305,7 +305,8 @@
"weight_uom":item.weight_uom,
"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
"transaction_date": args.get("transaction_date"),
- "against_blanket_order": args.get("against_blanket_order")
+ "against_blanket_order": args.get("against_blanket_order"),
+ "bom_no": item.get("default_bom")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json
new file mode 100644
index 0000000..de24575
--- /dev/null
+++ b/erpnext/stock/module_onboarding/stock/stock.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-15 03:18:44.400108",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 19:03:23.602423",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Setup your Warehouse"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Introduction to Stock Entry"
+ },
+ {
+ "step": "Create a Stock Entry"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Create a Purchase Receipt"
+ },
+ {
+ "step": "Stock Settings"
+ }
+ ],
+ "subtitle": "Inventory, Warehouses, Analysis and more.",
+ "success_message": "The Stock Module is all set up!",
+ "title": "Let's Setup the Stock Module.",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/buying_settings/buying_settings.json b/erpnext/stock/onboarding_step/buying_settings/buying_settings.json
new file mode 100644
index 0000000..a788ccd
--- /dev/null
+++ b/erpnext/stock/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/stock/onboarding_step/create_a_product/create_a_product.json b/erpnext/stock/onboarding_step/create_a_product/create_a_product.json
new file mode 100644
index 0000000..d2068e1
--- /dev/null
+++ b/erpnext/stock/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/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
new file mode 100644
index 0000000..b7811a4
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 18:59:13.266713",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:59:13.266713",
+ "modified_by": "Administrator",
+ "name": "Create a Purchase Receipt",
+ "owner": "Administrator",
+ "reference_document": "Purchase Receipt",
+ "show_full_form": 1,
+ "title": "Create a Purchase Receipt",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
new file mode 100644
index 0000000..2b83f65
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-15 03:20:16.277043",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-15 03:30:58.047696",
+ "modified_by": "Administrator",
+ "name": "Create a Stock Entry",
+ "owner": "Administrator",
+ "reference_document": "Stock Entry",
+ "show_full_form": 1,
+ "title": "Create a Stock Entry",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/stock/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/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
new file mode 100644
index 0000000..447611f
--- /dev/null
+++ b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
@@ -0,0 +1,19 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-15 02:47:17.958806",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:55:41.457289",
+ "modified_by": "Administrator",
+ "name": "Introduction to Stock Entry",
+ "owner": "Administrator",
+ "show_full_form": 0,
+ "title": "Introduction to the multi-purpose stock transaction",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=Njt107hlY3I"
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
new file mode 100644
index 0000000..557c905
--- /dev/null
+++ b/erpnext/stock/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/stock/onboarding_step/stock_settings/stock_settings.json b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
new file mode 100644
index 0000000..7591bff
--- /dev/null
+++ b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-15 02:53:57.209967",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-15 03:55:15.444151",
+ "modified_by": "Administrator",
+ "name": "Stock Settings",
+ "owner": "Administrator",
+ "reference_document": "Stock Settings",
+ "show_full_form": 0,
+ "title": "Explore Stock Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
index 27cf6b6..5a931e7 100644
--- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
+++ b/erpnext/stock/report/delivery_note_trends/delivery_note_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,39 @@
data = []
conditions = get_columns(filters, "Delivery Note")
data = get_data(filters, conditions)
-
- return conditions["columns"], data
\ No newline at end of file
+
+ chart_data = get_chart_data(data, filters)
+
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("group_by"):
+ # consider only consolidated row
+ data = [row for row in data if row[0]]
+
+ 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:
+ labels.append(row[0])
+ datapoints.append(row[-1])
+
+ return {
+ "data": {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name": _("Total Delivered Amount"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.js b/erpnext/stock/report/item_shortage_report/item_shortage_report.js
new file mode 100644
index 0000000..ca42a33
--- /dev/null
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Item Shortage Report"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname": "warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "MultiSelectList",
+ "width": "100",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Warehouse', txt);
+ }
+ }
+ ]
+};
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.json b/erpnext/stock/report/item_shortage_report/item_shortage_report.json
index 577a853..17285c0 100644
--- a/erpnext/stock/report/item_shortage_report/item_shortage_report.json
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.json
@@ -1,29 +1,30 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-08-20 13:43:30",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "json": "{\"add_total_row\": 0, \"sort_by\": \"Bin.projected_qty\", \"sort_order\": \"asc\", \"sort_by_next\": \"\", \"filters\": [[\"Bin\", \"projected_qty\", \"<\", \"0\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"warehouse\", \"Bin\"], [\"item_code\", \"Bin\"], [\"actual_qty\", \"Bin\"], [\"ordered_qty\", \"Bin\"], [\"planned_qty\", \"Bin\"], [\"reserved_qty\", \"Bin\"], [\"projected_qty\", \"Bin\"]]}",
- "modified": "2017-02-24 20:00:46.439935",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Item Shortage Report",
- "owner": "Administrator",
- "query": "SELECT bin.warehouse as \"Warehouse:Link/Warehouse:150\",\n\tbin.item_code as \"Item Code:Link/Item:100\",\n\tbin.actual_qty as \"Actual Quantity:Float:120\",\n\tbin.ordered_qty as \"Ordered Quantity:Float:120\",\n\tbin.planned_qty as \"Planned Quantity:Float:120\",\n\tbin.reserved_qty as \"Reserved Quantity:Float:120\",\n\tbin.projected_qty as \"Project Quantity:Float:120\",\n\titem.item_name as \"Item Name:Data:150\",\n\titem.description as \"Description::200\"\nFROM tabBin as bin\nINNER JOIN tabItem as item\nON bin.item_code=item.name\nWHERE bin.projected_qty<0\nORDER BY bin.projected_qty;",
- "ref_doctype": "Bin",
- "report_name": "Item Shortage Report",
- "report_type": "Query Report",
+ "add_total_row": 0,
+ "creation": "2013-08-20 13:43:30",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "json": "{\"add_total_row\": 0, \"sort_by\": \"Bin.projected_qty\", \"sort_order\": \"asc\", \"sort_by_next\": \"\", \"filters\": [[\"Bin\", \"projected_qty\", \"<\", \"0\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"warehouse\", \"Bin\"], [\"item_code\", \"Bin\"], [\"actual_qty\", \"Bin\"], [\"ordered_qty\", \"Bin\"], [\"planned_qty\", \"Bin\"], [\"reserved_qty\", \"Bin\"], [\"projected_qty\", \"Bin\"]]}",
+ "modified": "2020-05-14 12:32:07.158991",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Shortage Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Bin",
+ "report_name": "Item Shortage Report",
+ "report_type": "Script Report",
"roles": [
{
"role": "Sales User"
- },
+ },
{
"role": "Purchase User"
- },
+ },
{
"role": "Stock User"
}
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
new file mode 100644
index 0000000..086d833
--- /dev/null
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ columns = get_columns()
+ conditions = get_conditions(filters)
+ data = get_data(conditions, filters)
+
+ if not data:
+ return [], [], None, []
+
+ chart_data = get_chart_data(data)
+
+ return columns, data, None, chart_data
+
+def get_conditions(filters):
+ conditions = ""
+
+ if filters.get("warehouse"):
+ conditions += "AND warehouse in %(warehouse)s"
+ if filters.get("company"):
+ conditions += "AND company = %(company)s"
+
+ return conditions
+
+def get_data(conditions, filters):
+ data = frappe.db.sql("""
+ SELECT
+ bin.warehouse,
+ bin.item_code,
+ bin.actual_qty ,
+ bin.ordered_qty ,
+ bin.planned_qty ,
+ bin.reserved_qty ,
+ bin.reserved_qty_for_production,
+ bin.projected_qty ,
+ warehouse.company,
+ item.item_name ,
+ item.description
+ FROM
+ `tabBin` bin,
+ `tabWarehouse` warehouse,
+ `tabItem` item
+ WHERE
+ bin.projected_qty<0
+ AND warehouse.name = bin.warehouse
+ AND bin.item_code=item.name
+ {0}
+ ORDER BY bin.projected_qty;""".format(conditions), filters, as_dict=1)
+
+ return data
+
+def get_chart_data(data):
+ labels, datapoints = [], []
+
+ for row in data:
+ labels.append(row.get("item_code"))
+ datapoints.append(row.get("projected_qty"))
+
+ if len(data) > 10:
+ labels = labels[:10]
+ datapoints = datapoints[:10]
+
+ return {
+ "data": {
+ "labels": labels,
+ "datasets":[
+ {
+ "name": _("Projected Qty"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type": "bar"
+ }
+
+def get_columns():
+ columns = [
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 150
+ },
+ {
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150
+ },
+ {
+ "label": _("Actual Quantity"),
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Ordered Quantity"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Planned Quantity"),
+ "fieldname": "planned_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Reserved Quantity"),
+ "fieldname": "reserved_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Reserved Quantity for Production"),
+ "fieldname": "reserved_qty_for_production",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Projected Quantity"),
+ "fieldname": "projected_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 120
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 120
+ }
+ ]
+
+ return columns
+
+
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 0e58920..8227f15 100644
--- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
+++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_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):
@@ -11,4 +12,40 @@
conditions = get_columns(filters, "Purchase Receipt")
data = get_data(filters, conditions)
- return conditions["columns"], data
\ No newline at end of file
+ chart_data = get_chart_data(data, filters)
+
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("group_by"):
+ # 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 = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[-1])
+
+ return {
+ "data": {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name": _("Total Received Amount"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar",
+ "colors":["#5e64ff"]
+ }
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 803a5c8..af99780 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -37,7 +37,9 @@
data.append(row)
- return columns, data
+ chart_data = get_chart_data(data, filters)
+
+ return columns, data, None, chart_data
def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
@@ -51,7 +53,7 @@
age_qty += batch_age * 1
total_qty += 1
- return (age_qty / total_qty) if total_qty else 0.0
+ return flt(age_qty / total_qty, 2) if total_qty else 0.0
def get_columns(filters):
columns = [
@@ -230,3 +232,34 @@
where wh.lft >= {0} and rgt <= {1})""".format(lft, rgt))
return "and {}".format(" and ".join(conditions)) if conditions else ""
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("show_warehouse_wise_stock"):
+ return {}
+
+ data.sort(key = lambda row: row[6], reverse=True)
+
+ if len(data) > 10:
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[6])
+
+ return {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {
+ "name": _("Average Age"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 0f15e47..1df9fb7 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2020-03-06 05:24:05.749664",
+ "modified": "2020-05-19 13:01:10.729088",
"modified_by": "Administrator",
"module": "Support",
"name": "issues",
@@ -76,7 +76,7 @@
{
"allow_read_on_all_link_options": 0,
"fieldname": "description",
- "fieldtype": "Text",
+ "fieldtype": "Text Editor",
"hidden": 0,
"label": "Description",
"max_length": 0,
diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py
index ce0f47d..df715ab 100644
--- a/erpnext/tests/test_woocommerce.py
+++ b/erpnext/tests/test_woocommerce.py
@@ -24,7 +24,7 @@
woo_settings.creation_user = "Administrator"
woo_settings.save(ignore_permissions=True)
- def test_sales_order_for_woocommerece(self):
+ def test_sales_order_for_woocommerce(self):
frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]}
order()