[enhancement] heatmaps on item and notifications for item

This commit is contained in:
Rushabh Mehta 2016-04-14 17:30:40 +05:30
parent 6fb803e181
commit 15a7f215b9
14 changed files with 149 additions and 13 deletions

View File

@ -324,3 +324,10 @@ def validate_party_frozen_disabled(party_type, party_name):
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if not frozen_accounts_modifier in frappe.get_roles(): if not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) 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)

View File

@ -8,7 +8,7 @@ from frappe import msgprint, _
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from erpnext.utilities.address_and_contact import load_address_and_contact from erpnext.utilities.address_and_contact import load_address_and_contact
from erpnext.utilities.transaction_base import TransactionBase 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 from erpnext.accounts.party_status import get_party_status
class Supplier(TransactionBase): class Supplier(TransactionBase):
@ -84,3 +84,14 @@ class Supplier(TransactionBase):
frappe.db.sql("""update `tabAddress` set address_title=%(newdn)s frappe.db.sql("""update `tabAddress` set address_title=%(newdn)s
{set_field} where supplier=%(newdn)s"""\ {set_field} where supplier=%(newdn)s"""\
.format(set_field=set_field), ({"newdn": newdn})) .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),
}

View File

@ -157,6 +157,28 @@ class Employee(Document):
def on_trash(self): def on_trash(self):
delete_events(self.doctype, self.name) 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() @frappe.whitelist()
def get_retirement_date(date_of_birth=None): def get_retirement_date(date_of_birth=None):

View File

@ -261,3 +261,4 @@ erpnext.patches.v6_27.fix_recurring_order_status
erpnext.patches.v6_20x.remove_customer_supplier_roles erpnext.patches.v6_20x.remove_customer_supplier_roles
erpnext.patches.v6_24.rename_item_field erpnext.patches.v6_24.rename_item_field
erpnext.patches.v7_0.update_party_status erpnext.patches.v7_0.update_party_status
erpnext.patches.v7_0.update_item_projected

View File

@ -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)

View File

@ -10,7 +10,7 @@ from frappe.utils import flt, cint, cstr
from frappe.desk.reportview import build_match_conditions from frappe.desk.reportview import build_match_conditions
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
from erpnext.utilities.address_and_contact import load_address_and_contact 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 from erpnext.accounts.party_status import get_party_status
class Customer(TransactionBase): class Customer(TransactionBase):
@ -128,6 +128,18 @@ class Customer(TransactionBase):
{set_field} where customer=%(newdn)s"""\ {set_field} where customer=%(newdn)s"""\
.format(set_field=set_field), ({"newdn": newdn})) .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): def get_customer_list(doctype, txt, searchfield, start, page_len, filters):
if frappe.db.get_default("cust_master_name") == "Customer Name": if frappe.db.get_default("cust_master_name") == "Customer Name":
fields = ["name", "customer_group", "territory"] fields = ["name", "customer_group", "territory"]

View File

@ -10,6 +10,7 @@ def get_notification_config():
"Warranty Claim": {"status": "Open"}, "Warranty Claim": {"status": "Open"},
"Task": {"status": "Overdue"}, "Task": {"status": "Overdue"},
"Project": {"status": "Open"}, "Project": {"status": "Open"},
"Item": {"total_projected_qty": ("<", 0)},
"Customer": {"status": "Open"}, "Customer": {"status": "Open"},
"Supplier": {"status": "Open"}, "Supplier": {"status": "Open"},
"Lead": {"status": "Open"}, "Lead": {"status": "Open"},
@ -40,7 +41,7 @@ def get_notification_config():
"docstatus": ("<", 2) "docstatus": ("<", 2)
}, },
"Purchase Receipt": {"docstatus": 0}, "Purchase Receipt": {"docstatus": 0},
"Production Order": { "status": "In Process" }, "Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) },
"BOM": {"docstatus": 0}, "BOM": {"docstatus": 0},
"Timesheet": {"docstatus": 0}, "Timesheet": {"docstatus": 0},
"Time Log": {"status": "Draft"}, "Time Log": {"status": "Draft"},

View File

@ -69,6 +69,7 @@ class Bin(Document):
flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty) flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
self.save() self.save()
update_item_projected_qty(self.item_code)
def get_first_sle(self): def get_first_sle(self):
sle = frappe.db.sql(""" sle = frappe.db.sql("""
@ -79,3 +80,9 @@ class Bin(Document):
limit 1 limit 1
""", (self.item_code, self.warehouse), as_dict=1) """, (self.item_code, self.warehouse), as_dict=1)
return sle and sle[0] or None 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))

View File

@ -16,6 +16,12 @@ frappe.ui.form.on("Item", {
}, },
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) { refresh: function(frm) {
if(frm.doc.is_stock_item) { if(frm.doc.is_stock_item) {

View File

@ -2285,6 +2285,31 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "hide_heading": 0,
@ -2298,7 +2323,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2016-04-11 09:15:30.911215", "modified": "2016-04-14 07:51:07.058298",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -583,6 +583,30 @@ class Item(WebsiteGenerator):
if self.is_fixed_asset and self.is_stock_item: if self.is_fixed_asset and self.is_stock_item:
frappe.throw(_("Fixed Asset Item must be a non-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): def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
if (not end_of_life) or (disabled is None): if (not end_of_life) or (disabled is None):
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"]) end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])

View File

@ -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>

View File

@ -4,7 +4,8 @@ links = {
'fieldname': 'item_code', 'fieldname': 'item_code',
'non_standard_fieldnames': { 'non_standard_fieldnames': {
'Production Order': 'production_item', 'Production Order': 'production_item',
'Product Bundle': 'new_item_code' 'Product Bundle': 'new_item_code',
'Batch': 'item'
}, },
'transactions': [ 'transactions': [
{ {

View File

@ -1,10 +1,12 @@
frappe.listview_settings['Item'] = { frappe.listview_settings['Item'] = {
add_fields: ["item_name", "stock_uom", "item_group", "image", "variant_of", 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"]], filters: [["disabled", "=", "0"]],
get_indicator: function(doc) { 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"]; return [__("Disabled"), "grey", "disabled,=,Yes"];
} else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) { } else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
return [__("Expired"), "grey", "end_of_life,<,Today"]; return [__("Expired"), "grey", "end_of_life,<,Today"];
@ -12,8 +14,6 @@ frappe.listview_settings['Item'] = {
return [__("Template"), "blue", "has_variants,=,Yes"]; return [__("Template"), "blue", "has_variants,=,Yes"];
} else if (doc.variant_of) { } else if (doc.variant_of) {
return [__("Variant"), "green", "variant_of,=," + doc.variant_of]; return [__("Variant"), "green", "variant_of,=," + doc.variant_of];
} else {
return [__("Active"), "blue", "end_of_life,>=,Today"];
} }
} }
}; };