From 96381da547665f92dd0e1d4a7cbcca68f3d60e33 Mon Sep 17 00:00:00 2001 From: bcornwellmott Date: Mon, 24 Jul 2017 10:12:30 -0700 Subject: [PATCH] Supplier Scorecard (#9294) * Initial start of scorecard docs * Got basic functionality working * Fixed doc names and added key functions * Basic functional version minus Actions * Hide scorecard docs until functional * Created supplier scorecard documentation * Added default variables and standings. Added restrictions for PO + RFQ * Automatic daily scorecard creation + on save * Added warning for PO nd RFQs * Minor fixes for codepy, automatically add variables for criteria, fix hooks.py typo * Added tests, fixed codacy formatting, small improvements * Fixed test bug w/ criteria. Codacy cleanup * Fixed codacy issues. Fixed sticky criteria * Fixed bug with period search. Remove blank variable child. * Updated docs, automatically add criteria and standings, clean up period create message * Uncommented test, set docs to beta * Fix for nabinhait review * Fix codacy issue. Fix dict assignment for records --- erpnext/buying/__init__.py | 1 + .../doctype/purchase_order/purchase_order.py | 13 + .../request_for_quotation.py | 12 + erpnext/buying/doctype/supplier/supplier.json | 122 ++- .../doctype/supplier_scorecard/__init__.py | 0 .../supplier_scorecard/supplier_scorecard.js | 146 ++++ .../supplier_scorecard.json | 701 ++++++++++++++++++ .../supplier_scorecard/supplier_scorecard.py | 262 +++++++ .../supplier_scorecard_dashboard.py | 15 + .../supplier_scorecard_list.js | 17 + .../test_supplier_scorecard.py | 190 +++++ .../supplier_scorecard_criteria/__init__.py | 0 .../supplier_scorecard_criteria.js | 8 + .../supplier_scorecard_criteria.json | 184 +++++ .../supplier_scorecard_criteria.py | 89 +++ .../test_supplier_scorecard_criteria.py | 75 ++ .../supplier_scorecard_period/__init__.py | 0 .../supplier_scorecard_period.js | 14 + .../supplier_scorecard_period.json | 397 ++++++++++ .../supplier_scorecard_period.py | 133 ++++ .../test_supplier_scorecard_period.py | 9 + .../__init__.py | 0 .../supplier_scorecard_scoring_criteria.json | 280 +++++++ .../supplier_scorecard_scoring_criteria.py | 9 + .../__init__.py | 0 .../supplier_scorecard_scoring_standing.json | 491 ++++++++++++ .../supplier_scorecard_scoring_standing.py | 9 + .../__init__.py | 0 .../supplier_scorecard_scoring_variable.json | 222 ++++++ .../supplier_scorecard_scoring_variable.py | 9 + .../supplier_scorecard_standing/__init__.py | 0 .../supplier_scorecard_standing.js | 10 + .../supplier_scorecard_standing.json | 424 +++++++++++ .../supplier_scorecard_standing.py | 29 + .../test_supplier_scorecard_standing.py | 9 + .../supplier_scorecard_variable/__init__.py | 0 .../supplier_scorecard_variable.js | 10 + .../supplier_scorecard_variable.json | 242 ++++++ .../supplier_scorecard_variable.py | 503 +++++++++++++ .../test_supplier_scorecard_variable.py | 57 ++ erpnext/config/buying.py | 26 + .../buying/supplier-scorecard-criteria.png | Bin 0 -> 22343 bytes .../buying/supplier-scorecard-standing.png | Bin 0 -> 20653 bytes .../buying/supplier-scorecard-weighing.png | Bin 0 -> 23290 bytes .../assets/img/buying/supplier-scorecard.png | Bin 0 -> 54354 bytes erpnext/docs/user/manual/en/buying/index.txt | 1 + .../manual/en/buying/supplier-scorecard.md | 76 ++ erpnext/hooks.py | 1 + erpnext/patches.txt | 3 +- erpnext/patches/v8_4/__init__.py | 1 + .../patches/v8_4/make_scorecard_records.py | 9 + .../setup/setup_wizard/install_fixtures.py | 4 + 52 files changed, 4811 insertions(+), 2 deletions(-) create mode 100644 erpnext/buying/doctype/supplier_scorecard/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js create mode 100644 erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.json create mode 100644 erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py create mode 100644 erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py create mode 100644 erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js create mode 100644 erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_criteria/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js create mode 100644 erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_period/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js create mode 100644 erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_criteria/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_standing/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_variable/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_standing/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js create mode 100644 erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_variable/__init__.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js create mode 100644 erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.json create mode 100644 erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py create mode 100644 erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py create mode 100644 erpnext/docs/assets/img/buying/supplier-scorecard-criteria.png create mode 100644 erpnext/docs/assets/img/buying/supplier-scorecard-standing.png create mode 100644 erpnext/docs/assets/img/buying/supplier-scorecard-weighing.png create mode 100644 erpnext/docs/assets/img/buying/supplier-scorecard.png create mode 100644 erpnext/docs/user/manual/en/buying/supplier-scorecard.md create mode 100644 erpnext/patches/v8_4/__init__.py create mode 100644 erpnext/patches/v8_4/make_scorecard_records.py diff --git a/erpnext/buying/__init__.py b/erpnext/buying/__init__.py index e69de29bb2..baffc48825 100644 --- a/erpnext/buying/__init__.py +++ b/erpnext/buying/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 7e5020a08d..26c8c61167 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -39,6 +39,8 @@ class PurchaseOrder(BuyingController): super(PurchaseOrder, self).validate() self.set_status() + + self.validate_supplier() validate_for_items(self) self.check_for_closed_status() @@ -65,6 +67,17 @@ class PurchaseOrder(BuyingController): } }) + def validate_supplier(self): + prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos') + if prevent_po: + standing = frappe.db.get_value("Supplier Scorecard",self.supplier, 'status') + frappe.throw(_("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.").format(self.supplier, standing)) + + warn_po = frappe.db.get_value("Supplier", self.supplier, 'warn_pos') + if warn_po: + standing = frappe.db.get_value("Supplier Scorecard",self.supplier, 'status') + frappe.msgprint(_("{0} currently has a {1} Supplier Scorecard standing, and Purchase Orders to this supplier should be issued with caution.").format(self.supplier, standing), title=_("Caution"), indicator='orange') + def validate_minimum_order_qty(self): items = list(set([d.item_code for d in self.get("items")])) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 9109239e93..666a1c6e8a 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -21,6 +21,7 @@ STANDARD_USERS = ("Guest", "Administrator") class RequestforQuotation(BuyingController): def validate(self): self.validate_duplicate_supplier() + self.validate_supplier_list() validate_for_items(self) self.update_email_id() @@ -29,6 +30,17 @@ class RequestforQuotation(BuyingController): if len(supplier_list) != len(set(supplier_list)): frappe.throw(_("Same supplier has been entered multiple times")) + def validate_supplier_list(self): + for d in self.suppliers: + prevent_rfqs = frappe.db.get_value("Supplier", d.supplier, 'prevent_rfqs') + if prevent_rfqs: + standing = frappe.db.get_value("Supplier Scorecard",d.supplier, 'status') + frappe.throw(_("RFQs are not allowed for {0} due to a scorecard standing of {1}").format(d.supplier, standing)) + warn_rfqs = frappe.db.get_value("Supplier", d.supplier, 'warn_rfqs') + if warn_rfqs: + standing = frappe.db.get_value("Supplier Scorecard",d.supplier, 'status') + frappe.msgprint(_("{0} currently has a {1} Supplier Scorecard standing, and RFQs to this supplier should be issued with caution.").format(d.supplier, standing), title=_("Caution"), indicator='orange') + def update_email_id(self): for rfq_supplier in self.suppliers: if not rfq_supplier.email_id: diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 58a1fc7464..711e05d913 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -322,6 +322,126 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn POs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent POs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -850,7 +970,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-13 14:29:16.310834", + "modified": "2017-07-06 16:40:46.935608", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier_scorecard/__init__.py b/erpnext/buying/doctype/supplier_scorecard/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js new file mode 100644 index 0000000000..a3a14147f2 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js @@ -0,0 +1,146 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +/* global frappe, refresh_field */ + +frappe.ui.form.on("Supplier Scorecard", { + + onload: function(frm) { + + if (frm.doc.indicator_color !== "") { + frm.set_indicator_formatter("status", function(doc) { + return doc.indicator_color.toLowerCase(); + }); + } + if (frm.doc.__unsaved == 1) { + loadAllCriteria(frm); + loadAllStandings(frm); + } + + }, + refresh: function(frm) { + if (frm.dashboard.hasOwnProperty('heatmap')) { + frm.dashboard.heatmap.setLegend([0,20,40,60,80,101],["#991600","#169900"]); + } + } + +}); + +frappe.ui.form.on("Supplier Scorecard Scoring Standing", { + + standing_name: function(frm, cdt, cdn) { + if (frm.doc.standing_name != undefined) { + var d = frappe.get_doc(cdt, cdn); + return frm.call({ + method: "erpnext.buying.doctype.supplier_scorecard_standing.supplier_scorecard_standing.get_scoring_standing", + child: d, + args: { + standing_name: d.standing_name + } + }); + } + } +}); + +frappe.ui.form.on("Supplier Scorecard Scoring Variable", { + + variable_label: function(frm, cdt, cdn) { + if (frm.doc.variable_label != undefined) { + var d = frappe.get_doc(cdt, cdn); + return frm.call({ + method: "erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable.get_scoring_variable", + child: d, + args: { + variable_label: d.variable_label + } + }); + } + } +}); + +frappe.ui.form.on("Supplier Scorecard Scoring Criteria", { + + criteria_name: function(frm, cdt, cdn) { + if (frm.doc.criteria_name != undefined) { + var d = frappe.get_doc(cdt, cdn); + frm.call({ + method: "erpnext.buying.doctype.supplier_scorecard_criteria.supplier_scorecard_criteria.get_variables", + args: { + criteria_name: d.criteria_name + }, + callback: function(r) { + for (var i = 0; i < r.message.length; i++) + { + var exists = false; + for (var j = 0; j < frm.doc.variables.length; j++) + { + if(!frm.doc.variables[j].hasOwnProperty("variable_label")) { + frm.get_field("variables").grid.grid_rows[j].remove(); + } + else if(frm.doc.variables[j].variable_label === r.message[i]) { + exists = true; + } + } + if (!exists){ + var new_row = frm.add_child("variables"); + new_row.variable_label = r.message[i]; + frm.script_manager.trigger("variable_label", new_row.doctype, new_row.name); + } + + } + refresh_field("variables"); + } + }); + return frm.call({ + method: "erpnext.buying.doctype.supplier_scorecard_criteria.supplier_scorecard_criteria.get_scoring_criteria", + child: d, + args: { + criteria_name: d.criteria_name + } + }); + } + } +}); + +var loadAllCriteria = function(frm) { + frappe.call({ + method: "erpnext.buying.doctype.supplier_scorecard_criteria.supplier_scorecard_criteria.get_criteria_list", + callback: function(r) { + for (var j = 0; j < frm.doc.criteria.length; j++) + { + if(!frm.doc.criteria[j].hasOwnProperty("criteria_name")) { + frm.get_field("criteria").grid.grid_rows[j].remove(); + } + } + for (var i = 0; i < r.message.length; i++) + { + var new_row = frm.add_child("criteria"); + new_row.criteria_name = r.message[i].name; + frm.script_manager.trigger("criteria_name", new_row.doctype, new_row.name); + } + refresh_field("criteria"); + } + }); +}; +var loadAllStandings = function(frm) { + frappe.call({ + method: "erpnext.buying.doctype.supplier_scorecard_standing.supplier_scorecard_standing.get_standings_list", + callback: function(r) { + for (var j = 0; j < frm.doc.standings.length; j++) + { + if(!frm.doc.standings[j].hasOwnProperty("standing_name")) { + frm.get_field("standings").grid.grid_rows[j].remove(); + } + } + for (var i = 0; i < r.message.length; i++) + { + var new_row = frm.add_child("standings"); + new_row.standing_name = r.message[i].name; + frm.script_manager.trigger("standing_name", new_row.doctype, new_row.name); + } + refresh_field("standings"); + } + }); +}; + + diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.json b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.json new file mode 100644 index 0000000000..d7f24c9082 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.json @@ -0,0 +1,701 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:supplier", + "beta": 1, + "creation": "2017-05-29 01:40:54.786555", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "supplier", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Supplier", + "length": 0, + "no_copy": 0, + "options": "Supplier", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "supplier_score", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Supplier Score", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "indicator_color", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Indicator Color", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Per Month", + "fieldname": "period", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Evaluation Period", + "length": 0, + "no_copy": 0, + "options": "Per Month\nPer Week\nPer Year", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "scoring_setup", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Scoring Setup", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )", + "description": "Scorecard variables can be used, as well as:\n{total_score} (the total score from that period),\n{period_number} (the number of periods to present day)\n", + "fieldname": "weighting_function", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Weighting Function", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "standings", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Scoring Standings", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Scoring Standing", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "criteria_setup", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Criteria Setup", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "criteria", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Scoring Criteria", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Scoring Criteria", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "variables", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Supplier Variables", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Scoring Variable", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "eval: doc.status != 'Unknown'", + "columns": 0, + "fieldname": "scorecard_actions", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Scorecard Actions", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn for new Request for Quotations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn for new Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent POs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_supplier", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify Supplier", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_employee", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify Employee", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-12 07:33:11.874949", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py new file mode 100644 index 0000000000..e13d22ab57 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import throw, _ +from frappe.model.document import Document +import time +from datetime import timedelta +from frappe.utils import nowdate, get_last_day, getdate, add_days, add_years +from erpnext.buying.doctype.supplier_scorecard_period.supplier_scorecard_period import make_supplier_scorecard + +class SupplierScorecard(Document): + + def validate(self): + self.validate_standings() + self.validate_criteria_weights() + self.calculate_total_score() + self.update_standing() + + def on_update(self): + score = make_all_scorecards(self.name) + if score > 0: + self.save() + + def validate_standings(self): + # Check that there are no overlapping scores and check that there are no missing scores + score = 0 + for c1 in self.standings: + for c2 in self.standings: + if c1 != c2: + if (c1.max_grade > c2.min_grade and c1.min_grade < c2.max_grade): + throw(_('Overlap in scoring between {0} and {1}').format(c1.standing_name,c2.standing_name)) + if c2.min_grade == score: + score = c2.max_grade + if score < 100: + throw(_('Unable to find score starting at {0}. You need to have standing scores covering 0 to 100').format(score)) + + def validate_criteria_weights(self): + + weight = 0 + for c in self.criteria: + weight += c.weight + + if weight != 100: + throw(_('Criteria weights must add up to 100%')) + + def calculate_total_score(self): + scorecards = frappe.db.sql(""" + SELECT + scp.name + FROM + `tabSupplier Scorecard Period` scp + WHERE + scp.scorecard = %(sc)s + ORDER BY + scp.end_date DESC""", + {"sc": self.name}, as_dict=1) + + period = 0 + total_score = 0 + total_max_score = 0 + for scp in scorecards: + my_sc = frappe.get_doc('Supplier Scorecard Period', scp.name) + my_scp_weight = self.weighting_function + my_scp_weight = my_scp_weight.replace('{period_number}', str(period)) + + my_scp_maxweight = my_scp_weight.replace('{total_score}', '100') + my_scp_weight = my_scp_weight.replace('{total_score}', str(my_sc.total_score)) + + max_score = my_sc.calculate_weighted_score(my_scp_maxweight) + score = my_sc.calculate_weighted_score(my_scp_weight) + + total_score += score + total_max_score += max_score + period += 1 + if total_max_score > 0: + self.supplier_score = round(100.0 * (total_score / total_max_score) ,1) + else: + self.supplier_score = 100 + + def update_standing(self): + # Get the setup document + + for standing in self.standings: + if (not standing.min_grade or (standing.min_grade <= self.supplier_score)) and \ + (not standing.max_grade or (standing.max_grade > self.supplier_score)): + self.status = standing.standing_name + self.indicator_color = standing.standing_color + self.notify_supplier = standing.notify_supplier + self.notify_employee = standing.notify_employee + self.employee_link = standing.employee_link + + #Update supplier standing info + for fieldname in ('prevent_pos', 'prevent_rfqs','warn_rfqs','warn_pos'): + self.set(fieldname, standing.get(fieldname)) + frappe.db.set_value("Supplier", self.supplier, fieldname, self.get(fieldname)) + + +@frappe.whitelist() +def get_timeline_data(doctype, name): + # Get a list of all the associated scorecards + scs = frappe.get_doc(doctype, name) + out = {} + timeline_data = {} + scorecards = frappe.db.sql(""" + SELECT + sc.name + FROM + `tabSupplier Scorecard Period` sc + WHERE + sc.scorecard = %(scs)s""", + {"scs": scs.name}, as_dict=1) + + for sc in scorecards: + start_date, end_date, total_score = frappe.db.get_value('Supplier Scorecard Period', sc.name, ['start_date', 'end_date', 'total_score']) + for single_date in daterange(start_date, end_date): + timeline_data[time.mktime(single_date.timetuple())] = total_score + + out['timeline_data'] = timeline_data + return out + +def daterange(start_date, end_date): + for n in range(int ((end_date - start_date).days)+1): + yield start_date + timedelta(n) + +def refresh_scorecards(): + scorecards = frappe.db.sql(""" + SELECT + sc.name + FROM + `tabSupplier Scorecard` sc""", + {}, as_dict=1) + for sc in scorecards: + # Check to see if any new scorecard periods are created + if make_all_scorecards(sc.name) > 0: + # Save the scorecard to update the score and standings + sc.save() + + +@frappe.whitelist() +def make_all_scorecards(docname): + + sc = frappe.get_doc('Supplier Scorecard', docname) + supplier = frappe.get_doc('Supplier',sc.supplier) + + start_date = getdate(supplier.creation) + end_date = get_scorecard_date(sc.period, start_date) + todays = getdate(nowdate()) + + scp_count = 0 + first_start_date = todays + last_end_date = todays + + while (start_date < todays) and (end_date <= todays): + # check to make sure there is no scorecard period already created + scorecards = frappe.db.sql(""" + SELECT + scp.name + FROM + `tabSupplier Scorecard Period` scp + WHERE + scp.scorecard = %(sc)s + AND ( + (scp.start_date > %(end_date)s + AND scp.end_date < %(start_date)s) + OR + (scp.start_date < %(end_date)s + AND scp.end_date > %(start_date)s)) + ORDER BY + scp.end_date DESC""", + {"sc": docname, "start_date": start_date, "end_date": end_date, "supplier": supplier}, as_dict=1) + if len(scorecards) == 0: + period_card = make_supplier_scorecard(docname, None) + period_card.start_date = start_date + period_card.end_date = end_date + period_card.save() + scp_count = scp_count + 1 + if start_date < first_start_date: + first_start_date = start_date + last_end_date = end_date + + start_date = getdate(add_days(end_date,1)) + end_date = get_scorecard_date(sc.period, start_date) + if scp_count > 0: + frappe.msgprint(_("Created {0} scorecards for {1} between: ").format(scp_count, sc.supplier) + str(first_start_date) + " - " + str(last_end_date)) + return scp_count + +def get_scorecard_date(period, start_date): + if period == 'Per Week': + end_date = getdate(add_days(start_date,7)) + elif period == 'Per Month': + end_date = get_last_day(start_date) + elif period == 'Per Year': + end_date = add_days(add_years(start_date,1), -1) + return end_date + +def make_default_records(): + install_variable_docs = [ + {"param_name": "total_accepted_items", "variable_label": "Total Accepted Items", \ + "path": "get_total_accepted_items"}, + {"param_name": "total_accepted_amount", "variable_label": "Total Accepted Amount", \ + "path": "get_total_accepted_amount"}, + {"param_name": "total_rejected_items", "variable_label": "Total Rejected Items", \ + "path": "get_total_rejected_items"}, + {"param_name": "total_rejected_amount", "variable_label": "Total Rejected Amount", \ + "path": "get_total_rejected_amount"}, + {"param_name": "total_received_items", "variable_label": "Total Received Items", \ + "path": "get_total_received_items"}, + {"param_name": "total_received_amount", "variable_label": "Total Received Amount", \ + "path": "get_total_received_amount"}, + {"param_name": "rfq_response_days", "variable_label": "RFQ Response Days", \ + "path": "get_rfq_response_days"}, + {"param_name": "sq_total_items", "variable_label": "SQ Total Items", \ + "path": "get_sq_total_items"}, + {"param_name": "sq_total_number", "variable_label": "SQ Total Number", \ + "path": "get_sq_total_number"}, + {"param_name": "rfq_total_number", "variable_label": "RFQ Total Number", \ + "path": "get_rfq_total_number"}, + {"param_name": "rfq_total_items", "variable_label": "RFQ Total Items", \ + "path": "get_rfq_total_items"}, + {"param_name": "tot_item_days", "variable_label": "Total Item Days", \ + "path": "get_item_workdays"}, + {"param_name": "on_time_shipment_num", "variable_label": "# of On Time Shipments", "path": \ + "get_on_time_shipments"}, + {"param_name": "cost_of_delayed_shipments", "variable_label": "Cost of Delayed Shipments", \ + "path": "get_cost_of_delayed_shipments"}, + {"param_name": "cost_of_on_time_shipments", "variable_label": "Cost of On Time Shipments", \ + "path": "get_cost_of_on_time_shipments"}, + {"param_name": "total_working_days", "variable_label": "Total Working Days", \ + "path": "get_total_workdays"}, + {"param_name": "tot_cost_shipments", "variable_label": "Total Cost of Shipments", \ + "path": "get_total_cost_of_shipments"}, + {"param_name": "tot_days_late", "variable_label": "Total Days Late", \ + "path": "get_total_days_late"}, + {"param_name": "total_shipments", "variable_label": "Total Shipments", \ + "path": "get_total_shipments"} + ] + install_standing_docs = [ + {"min_grade": 0.0, "prevent_rfqs": 1, "notify_supplier": 0, "max_grade": 30.0, "prevent_pos": 1, \ + "standing_color": "Red", "notify_employee": 0, "standing_name": "Very Poor"}, + {"min_grade": 30.0, "prevent_rfqs": 1, "notify_supplier": 0, "max_grade": 50.0, "prevent_pos": 0, \ + "standing_color": "Red", "notify_employee": 0, "standing_name": "Poor"}, + {"min_grade": 50.0, "prevent_rfqs": 0, "notify_supplier": 0, "max_grade": 80.0, "prevent_pos": 0, \ + "standing_color": "Green", "notify_employee": 0, "standing_name": "Average"}, + {"min_grade": 80.0, "prevent_rfqs": 0, "notify_supplier": 0, "max_grade": 100.0, "prevent_pos": 0, \ + "standing_color": "Blue", "notify_employee": 0, "standing_name": "Excellent"}, + ] + + for d in install_variable_docs: + try: + d['doctype'] = "Supplier Scorecard Variable" + frappe.get_doc(d).insert() + except frappe.NameError: + pass + for d in install_standing_docs: + try: + d['doctype'] = "Supplier Scorecard Standing" + frappe.get_doc(d).insert() + except frappe.NameError: + pass diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py new file mode 100644 index 0000000000..ff7f119253 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py @@ -0,0 +1,15 @@ +from frappe import _ + +def get_data(): + return { + 'heatmap': True, + 'heatmap_message': _('This covers all scorecards tied to this Setup'), + 'fieldname': 'supplier', + 'method' : 'erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.get_timeline_data', + 'transactions': [ + { + 'label': _('Scorecards'), + 'items': ['Supplier Scorecard Period'] + } + ] + } \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js new file mode 100644 index 0000000000..c50916e4fa --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js @@ -0,0 +1,17 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +/* global frappe, __ */ + +frappe.listview_settings["Supplier Scorecard"] = { + add_fields: ["indicator_color", "status"], + get_indicator: function(doc) { + + if (doc.indicator_color) { + return [__(doc.status), doc.indicator_color.toLowerCase(), "status,=," + doc.status]; + } else { + return [__("Unknown"), "darkgrey", "status,=,''"]; + } + }, + +}; diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py new file mode 100644 index 0000000000..d64d3f683f --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestSupplierScorecard(unittest.TestCase): + + def test_create_scorecard(self): + delete_test_scorecards() + my_doc = make_supplier_scorecard() + doc = my_doc.insert() + self.assertEqual(doc.name, valid_scorecard[0].get("supplier")) + + def test_criteria_weight(self): + delete_test_scorecards() + my_doc = make_supplier_scorecard() + for d in my_doc.criteria: + d.weight = 0 + self.assertRaises(frappe.ValidationError,my_doc.insert) + + def test_missing_variable(self): + delete_test_scorecards() + my_doc = make_supplier_scorecard() + del my_doc.variables + self.assertRaises(frappe.ValidationError,my_doc.insert) + +def make_supplier_scorecard(): + my_doc = frappe.get_doc(valid_scorecard[0]) + + # Make sure the criteria exist (making them) + for d in valid_scorecard[0].get("criteria"): + if not frappe.db.exists("Supplier Scorecard Criteria", d.get("criteria_name")): + d["doctype"] = "Supplier Scorecard Criteria" + d["name"] = d.get("criteria_name") + my_criteria = frappe.get_doc(d) + my_criteria.insert() + return my_doc + + +def delete_test_scorecards(): + my_doc = make_supplier_scorecard() + if frappe.db.exists("Supplier Scorecard", my_doc.name): + # Delete all the periods, then delete the scorecard + frappe.db.sql("""delete from `tabSupplier Scorecard Period` where scorecard = %(scorecard)s""", {'scorecard': my_doc.name}) + frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Criteria` where parenttype = 'Supplier Scorecard Period'""") + frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Standing` where parenttype = 'Supplier Scorecard Period'""") + frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Variable` where parenttype = 'Supplier Scorecard Period'""") + frappe.delete_doc(my_doc.doctype, my_doc.name) + +valid_scorecard = [ + { + "standings":[ + { + "min_grade":0.0,"name":"Very Poor", + "prevent_rfqs":1, + "notify_supplier":0, + "doctype":"Supplier Scorecard Standing", + "max_grade":30.0, + "prevent_pos":1, + "warn_pos":0, + "warn_rfqs":0, + "standing_color":"Red", + "notify_employee":0, + "standing_name":"Very Poor", + "parenttype":"Supplier Scorecard", + "parentfield":"standings" + }, + { + "min_grade":30.0, + "name":"Poor", + "prevent_rfqs":1, + "notify_supplier":0, + "doctype":"Supplier Scorecard Standing", + "max_grade":50.0, + "prevent_pos":0, + "warn_pos":0, + "warn_rfqs":0, + "standing_color":"Red", + "notify_employee":0, + "standing_name":"Poor", + "parenttype":"Supplier Scorecard", + "parentfield":"standings" + }, + { + "min_grade":50.0, + "name":"Average", + "prevent_rfqs":0, + "notify_supplier":0, + "doctype":"Supplier Scorecard Standing", + "max_grade":80.0, + "prevent_pos":0, + "warn_pos":0, + "warn_rfqs":0, + "standing_color":"Green", + "notify_employee":0, + "standing_name":"Average", + "parenttype":"Supplier Scorecard", + "parentfield":"standings" + }, + { + "min_grade":80.0, + "name":"Excellent", + "prevent_rfqs":0, + "notify_supplier":0, + "doctype":"Supplier Scorecard Standing", + "max_grade":100.0, + "prevent_pos":0, + "warn_pos":0, + "warn_rfqs":0, + "standing_color":"Blue", + "notify_employee":0, + "standing_name":"Excellent", + "parenttype":"Supplier Scorecard", + "parentfield":"standings" + } + ], + "prevent_pos":0, + "variables": [ + { + "param_name":"cost_of_on_time_shipments", + "doctype":"Supplier Scorecard Scoring Variable", + "parenttype":"Supplier Scorecard", + "variable_label":"Cost of On Time Shipments", + "path":"get_cost_of_on_time_shipments", + "parentfield":"variables" + }, + { + "param_name":"tot_cost_shipments", + "doctype":"Supplier Scorecard Scoring Variable", + "parenttype":"Supplier Scorecard", + "variable_label":"Total Cost of Shipments", + "path":"get_total_cost_of_shipments", + "parentfield":"variables" + }, + { + "param_name":"tot_days_late", + "doctype":"Supplier Scorecard Scoring Variable", + "parenttype":"Supplier Scorecard", + "variable_label":"Total Days Late", + "path":"get_total_days_late", + "parentfield":"variables" + }, + { + "param_name":"total_working_days", + "doctype":"Supplier Scorecard Scoring Variable", + "parenttype":"Supplier Scorecard", + "variable_label":"Total Working Days", + "path":"get_total_workdays", + "parentfield":"variables" + }, + { + "param_name":"on_time_shipment_num", + "doctype":"Supplier Scorecard Scoring Variable", + "parenttype":"Supplier Scorecard", + "variable_label":"# of On Time Shipments", + "path":"get_on_time_shipments", + "parentfield":"variables" + }, + { + "param_name":"total_shipments", + "doctype":"Supplier Scorecard Scoring Variable", + "parenttype":"Supplier Scorecard", + "variable_label":"Total Shipments", + "path":"get_total_shipments", + "parentfield":"variables" + } + ], + "period":"Per Month", + "doctype":"Supplier Scorecard", + "warn_pos":0, + "warn_rfqs":0, + "notify_supplier":0, + "criteria":[ + { + "weight":100.0, + "doctype":"Supplier Scorecard Scoring Criteria", + "formula":"(({cost_of_on_time_shipments} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100 ", + "criteria_name":"Delivery", + "max_score":100.0, + } + ], + "supplier":"_Test Supplier", + "name":"_Test Supplier", + "weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )", + } +] + diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/__init__.py b/erpnext/buying/doctype/supplier_scorecard_criteria/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js new file mode 100644 index 0000000000..9f8a2dee81 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +/* global frappe */ + +frappe.ui.form.on("Supplier Scorecard Criteria", { + refresh: function() {} +}); diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json new file mode 100644 index 0000000000..229c386120 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json @@ -0,0 +1,184 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:criteria_name", + "beta": 1, + "creation": "2017-05-29 01:32:43.064891", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "criteria_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Criteria Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "weight", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Criteria Weight", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "100", + "fieldname": "max_score", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Max Score", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "formula", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Criteria Formula", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-17 10:30:47.458285", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Criteria", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py new file mode 100644 index 0000000000..8514022b78 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +import re +from frappe.model.document import Document + +class InvalidFormulaVariable(frappe.ValidationError): pass + +class SupplierScorecardCriteria(Document): + def validate(self): + self.validate_variables() + self.validate_formula() + + def validate_variables(self): + # make sure all the variables exist + _get_variables(self) + + def validate_formula(self): + # evaluate the formula with 0's to make sure it is valid + test_formula = self.formula.replace("\r", "").replace("\n", "") + + regex = r"\{(.*?)\}" + + mylist = re.finditer(regex, test_formula, re.MULTILINE | re.DOTALL) + for dummy1, match in enumerate(mylist): + for dummy2 in range(0, len(match.groups())): + test_formula = test_formula.replace('{' + match.group(1) + '}', "0") + + test_formula = test_formula.replace('<','<').replace('>','>') + try: + frappe.safe_eval(test_formula, None, {'max':max, 'min': min}) + except Exception: + frappe.throw(_("Error evaluating the criteria formula")) + + + +@frappe.whitelist() +def get_scoring_criteria(criteria_name): + criteria = frappe.get_doc("Supplier Scorecard Criteria", criteria_name) + + return criteria + + +@frappe.whitelist() +def get_criteria_list(): + criteria = frappe.db.sql(""" + SELECT + scs.name + FROM + `tabSupplier Scorecard Criteria` scs""", + {}, as_dict=1) + + return criteria + +@frappe.whitelist() +def get_variables(criteria_name): + criteria = frappe.get_doc("Supplier Scorecard Criteria", criteria_name) + return _get_variables(criteria) + +def _get_variables(criteria): + my_variables = [] + regex = r"\{(.*?)\}" + + mylist = re.finditer(regex, criteria.formula, re.MULTILINE | re.DOTALL) + for dummy1, match in enumerate(mylist): + for dummy2 in range(0, len(match.groups())): + try: + #var = frappe.get_doc("Supplier Scorecard Variable", {'param_name' : d}) + var = frappe.db.sql(""" + SELECT + scv.name + FROM + `tabSupplier Scorecard Variable` scv + WHERE + param_name=%(param)s""", + {'param':match.group(1)},)[0][0] + my_variables.append(var) + except Exception: + # Ignore the ones where the variable can't be found + frappe.throw(_('Unable to find variable: ') + str(match.group(1)), InvalidFormulaVariable) + #pass + + + #frappe.msgprint(str(my_variables)) + return my_variables diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py new file mode 100644 index 0000000000..4eef4b4e03 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestSupplierScorecardCriteria(unittest.TestCase): + def test_variables_exist(self): + delete_test_scorecards() + for d in test_good_criteria: + frappe.get_doc(d).insert() + + self.assertRaises(frappe.ValidationError,frappe.get_doc(test_bad_criteria[0]).insert) + + def test_formula_validate(self): + delete_test_scorecards() + self.assertRaises(frappe.ValidationError,frappe.get_doc(test_bad_criteria[1]).insert) + self.assertRaises(frappe.ValidationError,frappe.get_doc(test_bad_criteria[2]).insert) + +def delete_test_scorecards(): + # Delete all the periods so we can delete all the criteria + frappe.db.sql("""delete from `tabSupplier Scorecard Period`""") + frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Criteria` where parenttype = 'Supplier Scorecard Period'""") + frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Standing` where parenttype = 'Supplier Scorecard Period'""") + frappe.db.sql("""delete from `tabSupplier Scorecard Scoring Variable` where parenttype = 'Supplier Scorecard Period'""") + + for d in test_good_criteria: + if frappe.db.exists("Supplier Scorecard Criteria", d.get("name")): + # Delete all the periods, then delete the scorecard + frappe.delete_doc(d.get("doctype"), d.get("name")) + + for d in test_bad_criteria: + if frappe.db.exists("Supplier Scorecard Criteria", d.get("name")): + # Delete all the periods, then delete the scorecard + frappe.delete_doc(d.get("doctype"), d.get("name")) + +test_good_criteria = [ + { + "name":"Delivery", + "weight":40.0, + "doctype":"Supplier Scorecard Criteria", + "formula":"(({cost_of_on_time_shipments} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100", + "criteria_name":"Delivery", + "max_score":100.0 + }, +] + +test_bad_criteria = [ + { + "name":"Fake Criteria 1", + "weight":40.0, + "doctype":"Supplier Scorecard Criteria", + "formula":"(({fake_variable} / {tot_cost_shipments}) if {tot_cost_shipments} > 0 else 1 )* 100", # Invalid variable name + "criteria_name":"Fake Criteria 1", + "max_score":100.0 + }, + { + "name":"Fake Criteria 2", + "weight":40.0, + "doctype":"Supplier Scorecard Criteria", + "formula":"(({cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Force 0 divided by 0 + "criteria_name":"Fake Criteria 2", + "max_score":100.0 + }, + { + "name":"Fake Criteria 3", + "weight":40.0, + "doctype":"Supplier Scorecard Criteria", + "formula":"(({cost_of_on_time_shipments} {cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Two variables beside eachother + "criteria_name":"Fake Criteria 3", + "max_score":100.0 + }, +] \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_period/__init__.py b/erpnext/buying/doctype/supplier_scorecard_period/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js new file mode 100644 index 0000000000..c51e8ab2df --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +/* global frappe */ + + +frappe.ui.form.on("Supplier Scorecard Period", { + onload: function(frm) { + frm.get_field("variables").grid.toggle_display("value", true); + frm.get_field("criteria").grid.toggle_display("score", true); + + + } +}); diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.json b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.json new file mode 100644 index 0000000000..0cf651454b --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.json @@ -0,0 +1,397 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 1, + "creation": "2017-05-30 00:38:18.773013", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "supplier", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Supplier", + "length": 0, + "no_copy": 0, + "options": "Supplier", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series", + "length": 0, + "no_copy": 0, + "options": "SSC-", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_score", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Period Score", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "start_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "end_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Calculations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "criteria", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Criteria", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Scoring Criteria", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "variables", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Variables", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Scoring Variable", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "sec_ref", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "scorecard", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Supplier Scorecard Setup", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-12 07:33:26.130861", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Period", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py new file mode 100644 index 0000000000..90b65bd35a --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import throw, _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +import erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable as variable_functions + +class SupplierScorecardPeriod(Document): + + def validate(self): + self.validate_criteria_weights() + self.calculate_variables() + self.calculate_criteria() + self.calculate_score() + + def validate_criteria_weights(self): + + weight = 0 + for c in self.criteria: + weight += c.weight + + if weight != 100: + throw(_('Criteria weights must add up to 100%')) + + def calculate_variables(self): + for var in self.variables: + + if '.' in var.path: + method_to_call = import_string_path(var.path) + var.value = method_to_call(self) + else: + method_to_call = getattr(variable_functions, var.path) + var.value = method_to_call(self) + + + + def calculate_criteria(self): + #Get the criteria + for crit in self.criteria: + + #me = "" + my_eval_statement = crit.formula.replace("\r", "").replace("\n", "") + #for let in my_eval_statement: + # me += let.encode('hex') + " " + #frappe.msgprint(me) + + for var in self.variables: + if var.value: + if var.param_name in my_eval_statement: + my_eval_statement = my_eval_statement.replace('{' + var.param_name + '}', "{:.2f}".format(var.value)) + else: + if var.param_name in my_eval_statement: + my_eval_statement = my_eval_statement.replace('{' + var.param_name + '}', '0.0') + + #frappe.msgprint(my_eval_statement ) + + my_eval_statement = my_eval_statement.replace('<','<').replace('>','>') + + try: + crit.score = min(crit.max_score, max( 0 ,frappe.safe_eval(my_eval_statement, None, {'max':max, 'min': min}))) + except Exception: + frappe.throw(_("Could not solve criteria score function for {0}. Make sure the formula is valid.".format(crit.criteria_name)),frappe.ValidationError) + crit.score = 0 + + def calculate_score(self): + myscore = 0 + for crit in self.criteria: + myscore += crit.score * crit.weight/100.0 + self.total_score = myscore + + def calculate_weighted_score(self, weighing_function): + my_eval_statement = weighing_function.replace("\r", "").replace("\n", "") + + for var in self.variables: + if var.value: + if var.param_name in my_eval_statement: + my_eval_statement = my_eval_statement.replace('{' + var.param_name + '}', "{:.2f}".format(var.value)) + else: + if var.param_name in my_eval_statement: + my_eval_statement = my_eval_statement.replace('{' + var.param_name + '}', '0.0') + + my_eval_statement = my_eval_statement.replace('<','<').replace('>','>') + + try: + weighed_score = frappe.safe_eval(my_eval_statement, None, {'max':max, 'min': min}) + except Exception: + frappe.throw(_("Could not solve weighted score function. Make sure the formula is valid."),frappe.ValidationError) + weighed_score = 0 + return weighed_score + + + +def import_string_path(path): + components = path.split('.') + mod = __import__(components[0]) + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + +def post_process(source, target): + pass + + +@frappe.whitelist() +def make_supplier_scorecard(source_name, target_doc=None): + #def update_item(obj, target, source_parent): + # target.qty = flt(obj.qty) - flt(obj.received_qty) + # target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor) + # target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) + # target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ + # flt(obj.rate) * flt(source_parent.conversion_rate) + + doc = get_mapped_doc("Supplier Scorecard", source_name, { + "Supplier Scorecard": { + "doctype": "Supplier Scorecard Period" + }, + "Supplier Scorecard Scoring Variable": { + "doctype": "Supplier Scorecard Scoring Variable", + "add_if_empty": True + }, + "Supplier Scorecard Scoring Constraint": { + "doctype": "Supplier Scorecard Scoring Constraint", + "add_if_empty": True + } + }, target_doc, post_process) + + return doc + diff --git a/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py new file mode 100644 index 0000000000..8baa3185ba --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestSupplierScorecardPeriod(unittest.TestCase): + pass diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/__init__.py b/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.json b/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.json new file mode 100644 index 0000000000..567738a6d0 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.json @@ -0,0 +1,280 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-05-29 01:32:17.988454", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 3, + "fieldname": "criteria_name", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Criteria Name", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Criteria", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "weight", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Criteria Weight", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "100", + "fieldname": "max_score", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Max Score", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "formula", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Criteria Formula", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "score", + "fieldtype": "Percent", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Score", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-07-12 07:33:41.532361", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Scoring Criteria", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.py b/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.py new file mode 100644 index 0000000000..b64abed8a6 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class SupplierScorecardScoringCriteria(Document): + pass diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_standing/__init__.py b/erpnext/buying/doctype/supplier_scorecard_scoring_standing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.json b/erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.json new file mode 100644 index 0000000000..1fc04bb120 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.json @@ -0,0 +1,491 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-05-29 01:36:22.697234", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 3, + "fieldname": "standing_name", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Standing Name", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Standing", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "standing_color", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Color", + "length": 0, + "no_copy": 0, + "options": "Blue\nPurple\nGreen\nYellow\nOrange\nRed", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "min_grade", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Min Grade", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "max_grade", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Max Grade", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "actions", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Actions", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_supplier", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify Supplier", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_employee", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify Employee", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_link", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee ", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-07-12 07:33:20.615684", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Scoring Standing", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.py b/erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.py new file mode 100644 index 0000000000..e8ad79f33d --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class SupplierScorecardScoringStanding(Document): + pass diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_variable/__init__.py b/erpnext/buying/doctype/supplier_scorecard_scoring_variable/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.json b/erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.json new file mode 100644 index 0000000000..f0e043e47a --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.json @@ -0,0 +1,222 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-05-29 01:30:06.105240", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 3, + "fieldname": "variable_label", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Variable Name", + "length": 0, + "no_copy": 0, + "options": "Supplier Scorecard Variable", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_custom", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Custom?", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "param_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Parameter Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "path", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Path", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "value", + "fieldtype": "Float", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Value", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-07-12 07:33:36.671502", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Scoring Variable", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.py b/erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.py new file mode 100644 index 0000000000..58a8a99a09 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class SupplierScorecardScoringVariable(Document): + pass diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/__init__.py b/erpnext/buying/doctype/supplier_scorecard_standing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js new file mode 100644 index 0000000000..dccfcc34bb --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js @@ -0,0 +1,10 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +/* global frappe */ + +frappe.ui.form.on("Supplier Scorecard Standing", { + refresh: function() { + + } +}); diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.json b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.json new file mode 100644 index 0000000000..b61b4edd72 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.json @@ -0,0 +1,424 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:standing_name", + "beta": 1, + "creation": "2017-05-29 01:36:47.893639", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "standing_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Standing Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "standing_color", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Color", + "length": 0, + "no_copy": 0, + "options": "Blue\nPurple\nGreen\nYellow\nOrange\nRed", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "min_grade", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Min Grade", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "max_grade", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Max Grade", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warn_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Warn Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_rfqs", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent RFQs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "prevent_pos", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Prevent Purchase Orders", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_supplier", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify Supplier", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_employee", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify Other", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_link", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Other", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-12 07:33:16.560273", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Standing", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py new file mode 100644 index 0000000000..1ba5d06c53 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class SupplierScorecardStanding(Document): + pass + + +@frappe.whitelist() +def get_scoring_standing(standing_name): + standing = frappe.get_doc("Supplier Scorecard Standing", standing_name) + + return standing + + +@frappe.whitelist() +def get_standings_list(): + standings = frappe.db.sql(""" + SELECT + scs.name + FROM + `tabSupplier Scorecard Standing` scs""", + {}, as_dict=1) + + return standings \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py b/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py new file mode 100644 index 0000000000..4d96651313 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +class TestSupplierScorecardStanding(unittest.TestCase): + pass diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/__init__.py b/erpnext/buying/doctype/supplier_scorecard_variable/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js new file mode 100644 index 0000000000..2d74fdd190 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js @@ -0,0 +1,10 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +/* global frappe */ + +frappe.ui.form.on("Supplier Scorecard Variable", { + refresh: function() { + + } +}); diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.json b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.json new file mode 100644 index 0000000000..d24484025c --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.json @@ -0,0 +1,242 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:variable_label", + "beta": 1, + "creation": "2017-05-29 01:30:34.688389", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "variable_label", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Variable Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_custom", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Custom?", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "param_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Parameter Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "path", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Path", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-12 07:33:31.395262", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Scorecard Variable", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py new file mode 100644 index 0000000000..17c911a000 --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py @@ -0,0 +1,503 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import sys +from frappe import _ +from frappe.model.document import Document +from frappe.utils import getdate + +class VariablePathNotFound(frappe.ValidationError): pass + +class SupplierScorecardVariable(Document): + def validate(self): + self.validate_path_exists() + + def validate_path_exists(self): + if '.' in self.path: + try: + from erpnext.buying.doctype.supplier_scorecard_period.supplier_scorecard_period import import_string_path + import_string_path(self.path) + except AttributeError: + frappe.throw(_("Could not find path for " + self.path), VariablePathNotFound) + + else: + if not hasattr(sys.modules[__name__], self.path): + frappe.throw(_("Could not find path for " + self.path), VariablePathNotFound) + + +@frappe.whitelist() +def get_scoring_variable(variable_label): + variable = frappe.get_doc("Supplier Scorecard Variable", variable_label) + + return variable + +def get_total_workdays(scorecard): + """ Gets the number of days in this period""" + delta = getdate(scorecard.end_date) - getdate(scorecard.start_date) + return delta.days + +def get_item_workdays(scorecard): + """ Gets the number of days in this period""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + total_item_days = frappe.db.sql(""" + SELECT + SUM(DATEDIFF( %(end_date)s, po_item.schedule_date) * (po_item.qty)) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Order` po + WHERE + po.supplier = %(supplier)s + AND po_item.received_qty < po_item.qty + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.parent = po.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not total_item_days: + total_item_days = 0 + return total_item_days + + + +def get_total_cost_of_shipments(scorecard): + """ Gets the total cost of all shipments in the period (based on Purchase Orders)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(po_item.base_amount) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Order` po + WHERE + po.supplier = %(supplier)s + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.docstatus = 1 + AND po_item.parent = po.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if data: + return data + else: + return 0 + +def get_cost_of_delayed_shipments(scorecard): + """ Gets the total cost of all delayed shipments in the period (based on Purchase Receipts - POs)""" + return get_total_cost_of_shipments(scorecard) - get_cost_of_on_time_shipments(scorecard) + +def get_cost_of_on_time_shipments(scorecard): + """ Gets the total cost of all on_time shipments in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + + total_delivered_on_time_costs = frappe.db.sql(""" + SELECT + SUM(pr_item.base_amount) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Receipt Item` pr_item, + `tabPurchase Order` po, + `tabPurchase Receipt` pr + WHERE + po.supplier = %(supplier)s + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.schedule_date >= pr.posting_date + AND pr_item.docstatus = 1 + AND pr_item.purchase_order_item = po_item.name + AND po_item.parent = po.name + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if total_delivered_on_time_costs: + return total_delivered_on_time_costs + else: + return 0 + + +def get_total_days_late(scorecard): + """ Gets the number of item days late in the period (based on Purchase Receipts vs POs)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + total_delivered_late_days = frappe.db.sql(""" + SELECT + SUM(DATEDIFF(pr.posting_date,po_item.schedule_date)* pr_item.qty) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Receipt Item` pr_item, + `tabPurchase Order` po, + `tabPurchase Receipt` pr + WHERE + po.supplier = %(supplier)s + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.schedule_date < pr.posting_date + AND pr_item.docstatus = 1 + AND pr_item.purchase_order_item = po_item.name + AND po_item.parent = po.name + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + if not total_delivered_late_days: + total_delivered_late_days = 0 + + total_missed_late_days = frappe.db.sql(""" + SELECT + SUM(DATEDIFF( %(end_date)s, po_item.schedule_date) * (po_item.qty - po_item.received_qty)) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Order` po + WHERE + po.supplier = %(supplier)s + AND po_item.received_qty < po_item.qty + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.parent = po.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not total_missed_late_days: + total_missed_late_days = 0 + return total_missed_late_days + total_delivered_late_days + +def get_on_time_shipments(scorecard): + """ Gets the number of late shipments (counting each item) in the period (based on Purchase Receipts vs POs)""" + + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + total_items_delivered_on_time = frappe.db.sql(""" + SELECT + COUNT(pr_item.qty) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Receipt Item` pr_item, + `tabPurchase Order` po, + `tabPurchase Receipt` pr + WHERE + po.supplier = %(supplier)s + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.schedule_date <= pr.posting_date + AND po_item.qty = pr_item.qty + AND pr_item.docstatus = 1 + AND pr_item.purchase_order_item = po_item.name + AND po_item.parent = po.name + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not total_items_delivered_on_time: + total_items_delivered_on_time = 0 + return total_items_delivered_on_time + +def get_late_shipments(scorecard): + """ Gets the number of late shipments (counting each item) in the period (based on Purchase Receipts vs POs)""" + return get_total_shipments(scorecard) - get_on_time_shipments(scorecard) + +def get_total_received(scorecard): + """ Gets the total number of received shipments in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + COUNT(pr_item.base_amount) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_received_amount(scorecard): + """ Gets the total amount (in company currency) received in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(pr_item.received_qty * pr_item.base_rate) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_received_items(scorecard): + """ Gets the total number of received shipments in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(pr_item.received_qty) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_rejected_amount(scorecard): + """ Gets the total amount (in company currency) rejected in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(pr_item.rejected_qty * pr_item.base_rate) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_rejected_items(scorecard): + """ Gets the total number of rejected items in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(pr_item.rejected_qty) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_accepted_amount(scorecard): + """ Gets the total amount (in company currency) accepted in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(pr_item.qty * pr_item.base_rate) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_accepted_items(scorecard): + """ Gets the total number of rejected items in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + SUM(pr_item.qty) + FROM + `tabPurchase Receipt Item` pr_item, + `tabPurchase Receipt` pr + WHERE + pr.supplier = %(supplier)s + AND pr.posting_date BETWEEN %(start_date)s AND %(end_date)s + AND pr_item.docstatus = 1 + AND pr_item.parent = pr.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_total_shipments(scorecard): + """ Gets the total number of ordered shipments to arrive in the period (based on Purchase Receipts)""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + COUNT(po_item.base_amount) + FROM + `tabPurchase Order Item` po_item, + `tabPurchase Order` po + WHERE + po.supplier = %(supplier)s + AND po_item.schedule_date BETWEEN %(start_date)s AND %(end_date)s + AND po_item.docstatus = 1 + AND po_item.parent = po.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_rfq_total_number(scorecard): + """ Gets the total number of RFQs sent to supplier""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + COUNT(rfq.name) as total_rfqs + FROM + `tabRequest for Quotation Item` rfq_item, + `tabRequest for Quotation Supplier` rfq_sup, + `tabRequest for Quotation` rfq + WHERE + rfq_sup.supplier = %(supplier)s + AND rfq.transaction_date BETWEEN %(start_date)s AND %(end_date)s + AND rfq_item.docstatus = 1 + AND rfq_item.parent = rfq.name + AND rfq_sup.parent = rfq.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + + if not data: + data = 0 + return data + +def get_rfq_total_items(scorecard): + """ Gets the total number of RFQ items sent to supplier""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + COUNT(rfq_item.name) as total_rfqs + FROM + `tabRequest for Quotation Item` rfq_item, + `tabRequest for Quotation Supplier` rfq_sup, + `tabRequest for Quotation` rfq + WHERE + rfq_sup.supplier = %(supplier)s + AND rfq.transaction_date BETWEEN %(start_date)s AND %(end_date)s + AND rfq_item.docstatus = 1 + AND rfq_item.parent = rfq.name + AND rfq_sup.parent = rfq.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + if not data: + data = 0 + return data + + +def get_sq_total_number(scorecard): + """ Gets the total number of RFQ items sent to supplier""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + COUNT(sq.name) as total_sqs + FROM + `tabRequest for Quotation Item` rfq_item, + `tabSupplier Quotation Item` sq_item, + `tabRequest for Quotation Supplier` rfq_sup, + `tabRequest for Quotation` rfq, + `tabSupplier Quotation` sq + WHERE + rfq_sup.supplier = %(supplier)s + AND rfq.transaction_date BETWEEN %(start_date)s AND %(end_date)s + AND sq_item.request_for_quotation_item = rfq_item.name + AND sq_item.docstatus = 1 + AND rfq_item.docstatus = 1 + AND sq.supplier = %(supplier)s + AND sq_item.parent = sq.name + AND rfq_item.parent = rfq.name + AND rfq_sup.parent = rfq.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + if not data: + data = 0 + return data + +def get_sq_total_items(scorecard): + """ Gets the total number of RFQ items sent to supplier""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + + # Look up all PO Items with delivery dates between our dates + data = frappe.db.sql(""" + SELECT + COUNT(sq_item.name) as total_sqs + FROM + `tabRequest for Quotation Item` rfq_item, + `tabSupplier Quotation Item` sq_item, + `tabSupplier Quotation` sq, + `tabRequest for Quotation Supplier` rfq_sup, + `tabRequest for Quotation` rfq + WHERE + rfq_sup.supplier = %(supplier)s + AND rfq.transaction_date BETWEEN %(start_date)s AND %(end_date)s + AND sq_item.request_for_quotation_item = rfq_item.name + AND sq_item.docstatus = 1 + AND sq.supplier = %(supplier)s + AND sq_item.parent = sq.name + AND rfq_item.docstatus = 1 + AND rfq_item.parent = rfq.name + AND rfq_sup.parent = rfq.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + if not data: + data = 0 + return data + +def get_rfq_response_days(scorecard): + """ Gets the total number of days it has taken a supplier to respond to rfqs in the period""" + supplier = frappe.get_doc('Supplier', scorecard.supplier) + total_sq_days = frappe.db.sql(""" + SELECT + SUM(DATEDIFF(sq.transaction_date, rfq.transaction_date)) + FROM + `tabRequest for Quotation Item` rfq_item, + `tabSupplier Quotation Item` sq_item, + `tabSupplier Quotation` sq, + `tabRequest for Quotation Supplier` rfq_sup, + `tabRequest for Quotation` rfq + WHERE + rfq_sup.supplier = %(supplier)s + AND rfq.transaction_date BETWEEN %(start_date)s AND %(end_date)s + AND sq_item.request_for_quotation_item = rfq_item.name + AND sq_item.docstatus = 1 + AND sq.supplier = %(supplier)s + AND sq_item.parent = sq.name + AND rfq_item.docstatus = 1 + AND rfq_item.parent = rfq.name + AND rfq_sup.parent = rfq.name""", + {"supplier": supplier.name, "start_date": scorecard.start_date, "end_date": scorecard.end_date}, as_dict=0)[0][0] + if not total_sq_days: + total_sq_days = 0 + + + return total_sq_days \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py new file mode 100644 index 0000000000..45a2c6250a --- /dev/null +++ b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +from erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable import VariablePathNotFound + + +class TestSupplierScorecardVariable(unittest.TestCase): + def test_variable_exist(self): + for d in test_existing_variables: + my_doc = frappe.get_doc("Supplier Scorecard Variable", d.get("name")) + self.assertEquals(my_doc.param_name, d.get('param_name')) + self.assertEquals(my_doc.variable_label, d.get('variable_label')) + self.assertEquals(my_doc.path, d.get('path')) + + def test_path_exists(self): + for d in test_good_variables: + if frappe.db.exists(d): + frappe.delete_doc(d.get("doctype"), d.get("name")) + frappe.get_doc(d).insert() + + for d in test_bad_variables: + self.assertRaises(VariablePathNotFound,frappe.get_doc(d).insert) + +test_existing_variables = [ + { + "param_name":"total_accepted_items", + "name":"Total Accepted Items", + "doctype":"Supplier Scorecard Variable", + "variable_label":"Total Accepted Items", + "path":"get_total_accepted_items" + }, +] + +test_good_variables = [ + { + "param_name":"good_variable1", + "name":"Good Variable 1", + "doctype":"Supplier Scorecard Variable", + "variable_label":"Good Variable 1", + "path":"get_total_accepted_items" + }, +] + +test_bad_variables = [ + { + "param_name":"fake_variable1", + "name":"Fake Variable 1", + "doctype":"Supplier Scorecard Variable", + "variable_label":"Fake Variable 1", + "path":"get_fake_variable1" + }, +] \ No newline at end of file diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py index 990ca7a8ba..ba29125ca0 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -141,6 +141,32 @@ def get_data(): }, ] }, + { + "label": _("Supplier Scorecard"), + "items": [ + { + "type": "doctype", + "name": "Supplier Scorecard", + "description": _("All Supplier scorecards."), + }, + { + "type": "doctype", + "name": "Supplier Scorecard Variable", + "description": _("Templates of supplier scorecard variables.") + }, + { + "type": "doctype", + "name": "Supplier Scorecard Criteria", + "description": _("Templates of supplier scorecard criteria."), + }, + { + "type": "doctype", + "name": "Supplier Scorecard Standing", + "description": _("Templates of supplier standings."), + }, + + ] + }, { "label": _("Other Reports"), "icon": "fa fa-list", diff --git a/erpnext/docs/assets/img/buying/supplier-scorecard-criteria.png b/erpnext/docs/assets/img/buying/supplier-scorecard-criteria.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc73c81eee61deee61fbcdfd0876edafb33086b GIT binary patch literal 22343 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�e^uWnRk@LEakIGV~t1Q}Ol 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

@fZZ!DfY(1H@>(bdX@}dK!Zk4X=Je792)A8KO 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 Th&U2x0*ABD)7PVZH#+S%d9H1> literal 0 HcmV?d00001 diff --git a/erpnext/docs/assets/img/buying/supplier-scorecard.png b/erpnext/docs/assets/img/buying/supplier-scorecard.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8de1736de42bc0eea96f4d644071acdbab363a GIT binary patch literal 54354 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 b=S@@%W%xl_t;!5=F_DYs?S8HVxc`3w7pXjW literal 0 HcmV?d00001 diff --git a/erpnext/docs/user/manual/en/buying/index.txt b/erpnext/docs/user/manual/en/buying/index.txt index 25c8797e18..4bd75f1047 100644 --- a/erpnext/docs/user/manual/en/buying/index.txt +++ b/erpnext/docs/user/manual/en/buying/index.txt @@ -5,3 +5,4 @@ purchase-order setup articles purchase-taxes +supplier-scorecard \ No newline at end of file diff --git a/erpnext/docs/user/manual/en/buying/supplier-scorecard.md b/erpnext/docs/user/manual/en/buying/supplier-scorecard.md new file mode 100644 index 0000000000..cecdf9cd21 --- /dev/null +++ b/erpnext/docs/user/manual/en/buying/supplier-scorecard.md @@ -0,0 +1,76 @@ +A Supplier Scorecard is an evaluation tool used to assess the performance of +suppliers. Supplier scorecards can be used to keep track of item quality, +delivery and responsiveness of suppliers across long periods of time. This data +is typically used to help in purchasing decisions. + +A Supplier Scorecard is manually created for each supplier. + +In ERPNext, you can create a supplier scorecard by going to: + +> Buying > Documents > Supplier Scorecard > New Supplier Scorecard + +### Create Supplier Scorecard +A supplier scorecard is created for each supplier individually. Only one supplier scorecard can be created for each +supplier. +Purchase Order + +#### Final Score and Standings +The supplier scorecard consists of a set evaluation periods, during which the performance of a supplier is +evaluated. This period can be daily, monthly or yearly. The current score is calculated from the score of each evaluation +period based on the weighting function. The default formula is linearly weight over the previous 12 scoring periods. +Purchase Order +This formula is customizable. + +The supplier standing is used to quickly sort suppliers based on their performance. These are customizable for each supplier. +The scorecard standing of a supplier can also be used to restrict suppliers from being included in Request for Quotations or +being issued Purchase Orders. +Purchase Order + +#### Evaluation Criteria and Variables +A supplier can be evaluated on several individual evaluation criteria, including (but not limited to) quotation response time, +delivered item quality, and delivery timeliness. These criteria are weighed to determine the final period score. +Purchase Order +The method for calculating each criteria is determined through the criteria formula field, which can use a number of pre-established variables. +The value of each of these variables is calculated over the scoring period for each supplier. Examples of such variables include: + - The total number of items received from the supplier + - The total number of accepted items from the supplier + - The total number of rejected items from the supplier + - The total number of deliveries from the supplier + - The total amount (in dollars) received from a supplier +Additional variables can be added through server-side customizations. + +The criteria formula should be customized to evaluate the suppliers in each criteria in a way that best fits the Company requirements. + +##### Evaluation Formulas +The evaluation formula uses the pre-established or custom variables to evaluate an aspect of supplier performance over the scoring period. Formulas can use the following mathematical functions: + +* addition: + +* subtraction: - +* multiplication: * +* division: / +* min: min(x,y) +* max: max(x,y) +* if/else: (x) if (formula) else (y) +* less than: < +* greated than: > +* variables: {variable_name} + +It is crucial that the formula be solvable for all variable values. This is most often an issue if the value resolves to 0. For example: +``` +{total_accepted_items} / {total_received_items} +``` + +This example would resolve to 0 / 0 in periods where there are no received items, and therefore should have a check to protect in this case: +``` +({total_accepted_items} / {total_received_items}) if {total_received_items} > 0 else 1. +``` + +### Evaluating the Supplier +An evaluation is generated for each Supplier Scorecard Period by clicking the "Generate Missing Scorecard Periods" button. The supplier +current score can be seen, as well as a visual graphic showing the performance of the supplier over time. Any actions against the supplier +are also noted here, including warnings when create RFQs and POs or locking out those features for this supplier altogether. + + + + +{next} diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5f0b19ebd7..860aac2ee7 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -191,6 +191,7 @@ scheduler_events = { "erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries", "erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary", "erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status", + "erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards", "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history" ] } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 834ed2fdf4..00f395fe8c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -422,6 +422,7 @@ erpnext.patches.v8_1.add_indexes_in_transaction_doctypes erpnext.patches.v8_3.set_restrict_to_domain_for_module_def erpnext.patches.v8_1.update_expense_claim_status erpnext.patches.v8_3.update_company_total_sales +erpnext.patches.v8_4.make_scorecard_records erpnext.patches.v8_1.set_delivery_date_in_so_item erpnext.patches.v8_5.fix_tax_breakup_for_non_invoice_docs -erpnext.patches.v8_5.remove_quotations_route_in_sidebar \ No newline at end of file +erpnext.patches.v8_5.remove_quotations_route_in_sidebar diff --git a/erpnext/patches/v8_4/__init__.py b/erpnext/patches/v8_4/__init__.py new file mode 100644 index 0000000000..baffc48825 --- /dev/null +++ b/erpnext/patches/v8_4/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/erpnext/patches/v8_4/make_scorecard_records.py b/erpnext/patches/v8_4/make_scorecard_records.py new file mode 100644 index 0000000000..37789d711a --- /dev/null +++ b/erpnext/patches/v8_4/make_scorecard_records.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records +def execute(): + + make_default_records() \ No newline at end of file diff --git a/erpnext/setup/setup_wizard/install_fixtures.py b/erpnext/setup/setup_wizard/install_fixtures.py index 43baf2f4fb..1301d33856 100644 --- a/erpnext/setup/setup_wizard/install_fixtures.py +++ b/erpnext/setup/setup_wizard/install_fixtures.py @@ -213,6 +213,10 @@ def install(country=None): records += [{'doctype': 'Lead Source', 'source_name': _(d)} for d in default_lead_sources] + # Records for the Supplier Scorecard + from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records + make_default_records() + from frappe.modules import scrub for r in records: doc = frappe.new_doc(r.get("doctype"))