[enhancement] heatmaps on item and notifications for item
This commit is contained in:
parent
6fb803e181
commit
15a7f215b9
@ -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)
|
@ -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),
|
||||||
|
}
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
7
erpnext/patches/v7_0/update_item_projected.py
Normal file
7
erpnext/patches/v7_0/update_item_projected.py
Normal 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)
|
@ -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"]
|
||||||
|
@ -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"},
|
||||||
|
@ -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))
|
||||||
|
@ -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) {
|
||||||
|
@ -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",
|
||||||
|
@ -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"])
|
||||||
|
12
erpnext/stock/doctype/item/item_dashboard.html
Normal file
12
erpnext/stock/doctype/item/item_dashboard.html
Normal 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>
|
@ -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': [
|
||||||
{
|
{
|
||||||
|
@ -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"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user