[enhancement] heatmaps on item and notifications for item
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index e4cba75..02d4244 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -324,3 +324,10 @@
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
+
+def get_timeline_data(doctype, name):
+ '''returns timeline data for the past one year'''
+ from frappe.desk.form.load import get_communication_data
+ data = get_communication_data(doctype, name, fields = 'unix_timestamp(date(creation)), count(name)',
+ group_by='group by date(creation)', as_dict=False)
+ return dict(data)
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 4b384fa..9a3d58c 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -8,7 +8,7 @@
from frappe.model.naming import make_autoname
from erpnext.utilities.address_and_contact import load_address_and_contact
from erpnext.utilities.transaction_base import TransactionBase
-from erpnext.accounts.party import validate_party_accounts
+from erpnext.accounts.party import validate_party_accounts, get_timeline_data
from erpnext.accounts.party_status import get_party_status
class Supplier(TransactionBase):
@@ -84,3 +84,14 @@
frappe.db.sql("""update `tabAddress` set address_title=%(newdn)s
{set_field} where supplier=%(newdn)s"""\
.format(set_field=set_field), ({"newdn": newdn}))
+
+@frappe.whitelist()
+def get_dashboard_data(name):
+ '''load dashboard related data'''
+ frappe.has_permission(doc=frappe.get_doc('Supplier', name), throw=True)
+
+ from frappe.desk.notifications import get_open_count
+ return {
+ 'count': get_open_count('Supplier', name),
+ 'timeline_data': get_timeline_data('Supplier', name),
+ }
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index e769e3e..9e02baf 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -157,6 +157,28 @@
def on_trash(self):
delete_events(self.doctype, self.name)
+ def get_timeline_data(self):
+ '''returns timeline data based on attendance'''
+ return
+
+@frappe.whitelist()
+def get_dashboard_data(name):
+ '''load dashboard related data'''
+ frappe.has_permission(doc=frappe.get_doc('Employee', name), throw=True)
+
+ from frappe.desk.notifications import get_open_count
+ return {
+ 'count': get_open_count('Employee', name),
+ 'timeline_data': get_timeline_data(name),
+ }
+
+def get_timeline_data(name):
+ '''Return timeline for attendance'''
+ return dict(frappe.db.sql('''select unix_timestamp(att_date), count(*)
+ from `tabAttendance` where employee=%s
+ and att_date > date_sub(curdate(), interval 1 year)
+ and status in ('Present', 'Half Day')
+ group by att_date''', name))
@frappe.whitelist()
def get_retirement_date(date_of_birth=None):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 975d589..84a4e19 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -261,3 +261,4 @@
erpnext.patches.v6_20x.remove_customer_supplier_roles
erpnext.patches.v6_24.rename_item_field
erpnext.patches.v7_0.update_party_status
+erpnext.patches.v7_0.update_item_projected
diff --git a/erpnext/patches/v7_0/update_item_projected.py b/erpnext/patches/v7_0/update_item_projected.py
new file mode 100644
index 0000000..71b54af
--- /dev/null
+++ b/erpnext/patches/v7_0/update_item_projected.py
@@ -0,0 +1,7 @@
+import frappe
+
+def execute():
+ frappe.reload_doctype("Item")
+ from erpnext.stock.doctype.bin.bin import update_item_projected_qty
+ for item in frappe.get_all("Item", filters={"is_stock_item": 1}):
+ update_item_projected_qty(item.name)
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 51aedef..9902441 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -10,7 +10,7 @@
from frappe.desk.reportview import build_match_conditions
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.utilities.address_and_contact import load_address_and_contact
-from erpnext.accounts.party import validate_party_accounts
+from erpnext.accounts.party import validate_party_accounts, get_timeline_data
from erpnext.accounts.party_status import get_party_status
class Customer(TransactionBase):
@@ -128,6 +128,18 @@
{set_field} where customer=%(newdn)s"""\
.format(set_field=set_field), ({"newdn": newdn}))
+
+@frappe.whitelist()
+def get_dashboard_data(name):
+ '''load dashboard related data'''
+ frappe.has_permission(doc=frappe.get_doc('Customer', name), throw=True)
+
+ from frappe.desk.notifications import get_open_count
+ return {
+ 'count': get_open_count('Customer', name),
+ 'timeline_data': get_timeline_data('Customer', name),
+ }
+
def get_customer_list(doctype, txt, searchfield, start, page_len, filters):
if frappe.db.get_default("cust_master_name") == "Customer Name":
fields = ["name", "customer_group", "territory"]
diff --git a/erpnext/startup/notifications.py b/erpnext/startup/notifications.py
index 700ced2..335efbb 100644
--- a/erpnext/startup/notifications.py
+++ b/erpnext/startup/notifications.py
@@ -10,6 +10,7 @@
"Warranty Claim": {"status": "Open"},
"Task": {"status": "Overdue"},
"Project": {"status": "Open"},
+ "Item": {"total_projected_qty": ("<", 0)},
"Customer": {"status": "Open"},
"Supplier": {"status": "Open"},
"Lead": {"status": "Open"},
@@ -40,7 +41,7 @@
"docstatus": ("<", 2)
},
"Purchase Receipt": {"docstatus": 0},
- "Production Order": { "status": "In Process" },
+ "Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) },
"BOM": {"docstatus": 0},
"Timesheet": {"docstatus": 0},
"Time Log": {"status": "Draft"},
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 1682686..3d57b4d 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -69,6 +69,7 @@
flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
self.save()
+ update_item_projected_qty(self.item_code)
def get_first_sle(self):
sle = frappe.db.sql("""
@@ -79,3 +80,9 @@
limit 1
""", (self.item_code, self.warehouse), as_dict=1)
return sle and sle[0] or None
+
+def update_item_projected_qty(item_code):
+ '''Set Item project qty'''
+ frappe.db.sql('''update tabItem set
+ total_projected_qty = (select sum(projected_qty) from tabBin where item_code=%s)
+ where name=%s''', (item_code, item_code))
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 8a9d83c..900c40c 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -16,6 +16,12 @@
},
+ dashboard_update: function(frm) {
+ if(frm.dashboard_data.stock_data && frm.dashboard_data.stock_data.length) {
+ frm.dashboard.add_stats(frappe.render_template('item_dashboard', {data: frm.dashboard_data.stock_data}))
+ }
+ },
+
refresh: function(frm) {
if(frm.doc.is_stock_item) {
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 94d2e8d..43fccf6 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -2285,6 +2285,31 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "total_projected_qty",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Total Projected Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"hide_heading": 0,
@@ -2298,7 +2323,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
- "modified": "2016-04-11 09:15:30.911215",
+ "modified": "2016-04-14 07:51:07.058298",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 8286b5f..d43f68d 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -407,13 +407,13 @@
if self.check_if_linked_document_exists():
frappe.throw(_("As there are existing transactions for this item, \
you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'"))
-
+
def check_if_linked_document_exists(self):
- for doctype in ("Sales Order Item", "Delivery Note Item", "Sales Invoice Item",
- "Material Request Item", "Purchase Order Item", "Purchase Receipt Item",
+ for doctype in ("Sales Order Item", "Delivery Note Item", "Sales Invoice Item",
+ "Material Request Item", "Purchase Order Item", "Purchase Receipt Item",
"Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"):
if frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}) or \
- frappe.db.get_value("Production Order",
+ frappe.db.get_value("Production Order",
filters={"production_item": self.name, "docstatus": 1}):
return True
@@ -583,6 +583,30 @@
if self.is_fixed_asset and self.is_stock_item:
frappe.throw(_("Fixed Asset Item must be a non-stock item"))
+
+@frappe.whitelist()
+def get_dashboard_data(name):
+ '''load dashboard related data'''
+ frappe.has_permission(doc=frappe.get_doc('Item', name), throw=True)
+
+ from frappe.desk.notifications import get_open_count
+ return {
+ 'count': get_open_count('Item', name),
+ 'timeline_data': get_timeline_data(name),
+ 'stock_data': get_stock_data(name)
+ }
+
+def get_timeline_data(name):
+ '''returns timeline data based on stock ledger entry'''
+ return dict(frappe.db.sql('''select unix_timestamp(posting_date), count(*)
+ from `tabStock Ledger Entry` where item_code=%s
+ and posting_date > date_sub(curdate(), interval 1 year)
+ group by posting_date''', name))
+
+def get_stock_data(name):
+ return frappe.get_all('Bin', fields=['warehouse', 'actual_qty', 'projected_qty'],
+ filters={'item_code': name})
+
def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
if (not end_of_life) or (disabled is None):
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
diff --git a/erpnext/stock/doctype/item/item_dashboard.html b/erpnext/stock/doctype/item/item_dashboard.html
new file mode 100644
index 0000000..a002a50
--- /dev/null
+++ b/erpnext/stock/doctype/item/item_dashboard.html
@@ -0,0 +1,12 @@
+<div style="padding-left: 15px;">
+ <h5>Stock Levels</h5>
+ <div class="row">
+ <div class="col-md-6 col-xs-12">
+ <ul class="list-unstyled">
+ {% data.every(function(d) { %}
+ <li class="small">{{ d.warehouse }}: {{ d.actual_qty }} ({{ d.projected_qty }})</li>
+ {% }) %}
+ </ul>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item_links.py b/erpnext/stock/doctype/item/item_links.py
index c1fff8c..5586214 100644
--- a/erpnext/stock/doctype/item/item_links.py
+++ b/erpnext/stock/doctype/item/item_links.py
@@ -4,7 +4,8 @@
'fieldname': 'item_code',
'non_standard_fieldnames': {
'Production Order': 'production_item',
- 'Product Bundle': 'new_item_code'
+ 'Product Bundle': 'new_item_code',
+ 'Batch': 'item'
},
'transactions': [
{
diff --git a/erpnext/stock/doctype/item/item_list.js b/erpnext/stock/doctype/item/item_list.js
index fffc7fe..2fee589 100644
--- a/erpnext/stock/doctype/item/item_list.js
+++ b/erpnext/stock/doctype/item/item_list.js
@@ -1,10 +1,12 @@
frappe.listview_settings['Item'] = {
add_fields: ["item_name", "stock_uom", "item_group", "image", "variant_of",
- "has_variants", "end_of_life", "disabled", "is_sales_item"],
+ "has_variants", "end_of_life", "disabled", "total_projected_qty"],
filters: [["disabled", "=", "0"]],
get_indicator: function(doc) {
- if (doc.disabled) {
+ if(doc.total_projected_qty < 0) {
+ return [__("Shortage"), "red", "total_projected_qty,<,0"];
+ } else if (doc.disabled) {
return [__("Disabled"), "grey", "disabled,=,Yes"];
} else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
return [__("Expired"), "grey", "end_of_life,<,Today"];
@@ -12,8 +14,6 @@
return [__("Template"), "blue", "has_variants,=,Yes"];
} else if (doc.variant_of) {
return [__("Variant"), "green", "variant_of,=," + doc.variant_of];
- } else {
- return [__("Active"), "blue", "end_of_life,>=,Today"];
}
}
};