[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')
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)

View File

@ -8,7 +8,7 @@ from frappe import msgprint, _
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 @@ class Supplier(TransactionBase):
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),
}

View File

@ -157,6 +157,28 @@ class Employee(Document):
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):

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_24.rename_item_field
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 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 @@ class Customer(TransactionBase):
{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"]

View File

@ -10,6 +10,7 @@ def get_notification_config():
"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 @@ def get_notification_config():
"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"},

View File

@ -69,6 +69,7 @@ class Bin(Document):
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 @@ class Bin(Document):
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))

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) {
if(frm.doc.is_stock_item) {

View File

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

View File

@ -407,13 +407,13 @@ class Item(WebsiteGenerator):
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 @@ class Item(WebsiteGenerator):
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"])

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',
'non_standard_fieldnames': {
'Production Order': 'production_item',
'Product Bundle': 'new_item_code'
'Product Bundle': 'new_item_code',
'Batch': 'item'
},
'transactions': [
{

View File

@ -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 @@ frappe.listview_settings['Item'] = {
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"];
}
}
};