From 3d2622ce917e38814c06c67c12c1d7938a01087a Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 25 Apr 2016 17:53:42 +0530 Subject: [PATCH] [refacator] added dashboard in item --- erpnext/public/build.json | 7 +- erpnext/public/css/erpnext.css | 10 +- erpnext/public/js/utils/inventory.js | 77 --------- erpnext/public/less/erpnext.less | 14 +- erpnext/stock/dashboard/__init__.py | 0 erpnext/stock/dashboard/item_dashboard.html | 7 + erpnext/stock/dashboard/item_dashboard.js | 162 ++++++++++++++++++ .../item_dashboard.py} | 2 +- .../item_dashboard_list.html} | 7 +- erpnext/stock/doctype/item/item.js | 17 +- erpnext/stock/doctype/item/item.py | 6 - .../doctype/stock_entry/stock_entry_utils.py | 2 +- .../page/stock_balance/stock_balance.html | 7 - .../stock/page/stock_balance/stock_balance.js | 95 +++------- 14 files changed, 230 insertions(+), 183 deletions(-) delete mode 100644 erpnext/public/js/utils/inventory.js create mode 100644 erpnext/stock/dashboard/__init__.py create mode 100644 erpnext/stock/dashboard/item_dashboard.html create mode 100644 erpnext/stock/dashboard/item_dashboard.js rename erpnext/stock/{page/stock_balance/stock_balance.py => dashboard/item_dashboard.py} (93%) rename erpnext/stock/{doctype/item/item_dashboard.html => dashboard/item_dashboard_list.html} (93%) delete mode 100644 erpnext/stock/page/stock_balance/stock_balance.html diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 16e7488627..ab0f673ea3 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -19,8 +19,11 @@ "public/js/pos/pos_item.html", "public/js/pos/pos_tax_row.html", "public/js/pos/pos.js", - "public/js/utils/item_selector.js", - "public/js/utils/inventory.js", "public/js/templates/item_selector.html" + ], + "js/item-dashboard.min.js": [ + "stock/dashboard/item_dashboard.html", + "stock/dashboard/item_dashboard_list.html", + "stock/dashboard/item_dashboard.js" ] } diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css index 75fab563ff..6777e1e4d7 100644 --- a/erpnext/public/css/erpnext.css +++ b/erpnext/public/css/erpnext.css @@ -107,7 +107,7 @@ } .erpnext-icon { width: 24px; - margin-right: 0px; + ackmargin-right: 0px; margin-top: -3px; } .pos .discount-amount-area .discount-field-col { @@ -116,3 +116,11 @@ .pos .discount-amount-area .input-group { margin-top: 2px; } +.dashboard-list-item { + background-color: inherit; + padding: 7px 15px; + border-bottom: 1px solid #d1d8dd; +} +.dashboard-list-item:last-child { + border-bottom: none; +} diff --git a/erpnext/public/js/utils/inventory.js b/erpnext/public/js/utils/inventory.js deleted file mode 100644 index 80cd6a5268..0000000000 --- a/erpnext/public/js/utils/inventory.js +++ /dev/null @@ -1,77 +0,0 @@ -erpnext.get_item_dashboard_data = function(data, max_count, show_item) { - if(!max_count) max_count = 0; - data.forEach(function(d) { - d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production; - d.pending_qty = 0; - d.total_reserved = d.reserved_qty + d.reserved_qty_for_production; - if(d.actual_or_pending > d.actual_qty) { - d.pending_qty = d.actual_or_pending - d.actual_qty; - } - - max_count = Math.max(d.actual_or_pending, d.actual_qty, - d.total_reserved, max_count); - }); - return { - data: data, - max_count: max_count, - show_item: show_item || false - } -} - -frappe.provide('erpnext.inventory'); - -erpnext.inventory.move_item = function(item, source, target, actual_qty, callback) { - var dialog = new frappe.ui.Dialog({ - title: target ? __('Add Item') : __('Move Item'), - fields: [ - {fieldname: 'item_code', label: __('Item'), - fieldtype: 'Link', options: 'Item', read_only: 1}, - {fieldname: 'source', label: __('Source Warehouse'), - fieldtype: 'Link', options: 'Warehouse', read_only: 1}, - {fieldname: 'target', label: __('Target Warehouse'), - fieldtype: 'Link', options: 'Warehouse', reqd: 1}, - {fieldname: 'qty', label: __('Quantity'), reqd: 1, - fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, - ], - }) - dialog.show(); - dialog.get_field('item_code').set_input(item); - - if(source) { - dialog.get_field('source').set_input(source); - } else { - dialog.get_field('source').df.hidden = 1; - dialog.get_field('source').refresh(); - } - - if(target) { - dialog.get_field('target').df.read_only = 1; - dialog.get_field('target').value = target; - dialog.get_field('target').refresh(); - } - - dialog.set_primary_action(__('Submit'), function() { - values = dialog.get_values(); - if(!values) { - return; - } - if(source && values.qty > actual_qty) { - frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty])); - return; - } - if(values.source === values.target) { - frappe.msgprint(__('Source and target warehouse must be different')); - } - - frappe.call({ - method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', - args: values, - callback: function(r) { - frappe.show_alert(__('Stock Entry {0} created', - ['' + r.message.name+ ''])); - dialog.hide(); - callback(r); - }, - }); - }); -} \ No newline at end of file diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index 23ee8413f4..58e8e6265d 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -132,7 +132,7 @@ } .erpnext-icon { - width: 24px; + width: 24px;ack margin-right: 0px; margin-top: -3px; } @@ -145,4 +145,14 @@ .input-group { margin-top: 2px; } -} \ No newline at end of file +} + +.dashboard-list-item { + background-color: inherit; + padding: 7px 15px; + border-bottom: 1px solid @border-color; +} + +.dashboard-list-item:last-child { + border-bottom: none; +} diff --git a/erpnext/stock/dashboard/__init__.py b/erpnext/stock/dashboard/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/dashboard/item_dashboard.html b/erpnext/stock/dashboard/item_dashboard.html new file mode 100644 index 0000000000..1e18969e63 --- /dev/null +++ b/erpnext/stock/dashboard/item_dashboard.html @@ -0,0 +1,7 @@ +
+
+
+ +
\ No newline at end of file diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js new file mode 100644 index 0000000000..86cc0f2c2b --- /dev/null +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -0,0 +1,162 @@ +frappe.provide('erpnext.stock'); + +erpnext.stock.ItemDashboard = Class.extend({ + init: function(opts) { + $.extend(this, opts); + this.make(); + }, + make: function() { + var me = this; + this.start = 0; + if(!this.sort_by) { + this.sort_by = 'projected_qty'; + this.sort_order = 'asc'; + } + + this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent); + this.result = this.content.find('.result'); + + // move + this.content.on('click', '.btn-move', function() { + erpnext.stock.move_item($(this).attr('data-item'), $(this).attr('data-warehouse'), + null, $(this).attr('data-actual_qty'), null, function() { me.refresh(); }); + }); + + this.content.on('click', '.btn-add', function() { + erpnext.stock.move_item($(this).attr('data-item'), null, $(this).attr('data-warehouse'), + $(this).attr('data-actual_qty'), $(this).attr('data-rate'), + function() { me.refresh(); }); + }); + + // more + this.content.find('.btn-more').on('click', function() { + me.start += 20; + me.refresh(); + }); + + }, + refresh: function() { + if(this.before_refresh) { + this.before_refresh(); + } + + var me = this; + frappe.call({ + method: 'erpnext.stock.dashboard.item_dashboard.get_data', + args: { + item_code: this.item_code, + warehouse: this.warehouse, + start: this.start, + sort_by: this.sort_by, + sort_order: this.sort_order, + }, + callback: function(r) { + me.render(r.message); + } + }); + }, + render: function(data) { + if(this.start===0) { + this.max_count = 0; + this.result.empty(); + } + + var context = this.get_item_dashboard_data(data, this.max_count, true); + this.max_count = this.max_count; + + // show more button + if(data.length===21) { + this.content.find('.more').removeClass('hidden'); + + // remove the last element + data.splice(-1); + } else { + this.content.find('.more').addClass('hidden'); + } + + $(frappe.render_template('item_dashboard_list', context)).appendTo(this.result); + + }, + get_item_dashboard_data: function(data, max_count, show_item) { + if(!max_count) max_count = 0; + data.forEach(function(d) { + d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production; + d.pending_qty = 0; + d.total_reserved = d.reserved_qty + d.reserved_qty_for_production; + if(d.actual_or_pending > d.actual_qty) { + d.pending_qty = d.actual_or_pending - d.actual_qty; + } + + max_count = Math.max(d.actual_or_pending, d.actual_qty, + d.total_reserved, max_count); + }); + return { + data: data, + max_count: max_count, + show_item: show_item || false + } + } +}) + +erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) { + var dialog = new frappe.ui.Dialog({ + title: target ? __('Add Item') : __('Move Item'), + fields: [ + {fieldname: 'item_code', label: __('Item'), + fieldtype: 'Link', options: 'Item', read_only: 1}, + {fieldname: 'source', label: __('Source Warehouse'), + fieldtype: 'Link', options: 'Warehouse', read_only: 1}, + {fieldname: 'target', label: __('Target Warehouse'), + fieldtype: 'Link', options: 'Warehouse', reqd: 1}, + {fieldname: 'qty', label: __('Quantity'), reqd: 1, + fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, + {fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 }, + ], + }) + dialog.show(); + dialog.get_field('item_code').set_input(item); + + if(source) { + dialog.get_field('source').set_input(source); + } else { + dialog.get_field('source').df.hidden = 1; + dialog.get_field('source').refresh(); + } + + if(rate) { + dialog.get_field('rate').set_value('rate'); + dialog.get_field('rate').df.hidden = 0; + dialog.get_field('rate').refresh(); + } + + if(target) { + dialog.get_field('target').df.read_only = 1; + dialog.get_field('target').value = target; + dialog.get_field('target').refresh(); + } + + dialog.set_primary_action(__('Submit'), function() { + values = dialog.get_values(); + if(!values) { + return; + } + if(source && values.qty > actual_qty) { + frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty])); + return; + } + if(values.source === values.target) { + frappe.msgprint(__('Source and target warehouse must be different')); + } + + frappe.call({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', + args: values, + callback: function(r) { + frappe.show_alert(__('Stock Entry {0} created', + ['' + r.message.name+ ''])); + dialog.hide(); + callback(r); + }, + }); + }); +} \ No newline at end of file diff --git a/erpnext/stock/page/stock_balance/stock_balance.py b/erpnext/stock/dashboard/item_dashboard.py similarity index 93% rename from erpnext/stock/page/stock_balance/stock_balance.py rename to erpnext/stock/dashboard/item_dashboard.py index e96f925e82..5a00b3d8dc 100644 --- a/erpnext/stock/page/stock_balance/stock_balance.py +++ b/erpnext/stock/dashboard/item_dashboard.py @@ -10,5 +10,5 @@ def get_data(item_code=None, warehouse=None, start=0, sort_by='actual_qty', sort if warehouse: filters['warehouse'] = warehouse return frappe.get_list("Bin", filters=filters, fields=['item_code', 'warehouse', - 'projected_qty', 'reserved_qty', 'reserved_qty_for_production', 'actual_qty'], + 'projected_qty', 'reserved_qty', 'reserved_qty_for_production', 'actual_qty', 'valuation_rate'], order_by='{0} {1}'.format(sort_by, sort_order), start=start, page_length = 21) \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item_dashboard.html b/erpnext/stock/dashboard/item_dashboard_list.html similarity index 93% rename from erpnext/stock/doctype/item/item_dashboard.html rename to erpnext/stock/dashboard/item_dashboard_list.html index 813aef33e2..f9ffbb3956 100644 --- a/erpnext/stock/doctype/item/item_dashboard.html +++ b/erpnext/stock/dashboard/item_dashboard_list.html @@ -1,5 +1,5 @@ {% for d in data %} -
  • +
    {{ d.warehouse }} @@ -47,8 +47,9 @@
    -
  • + {% endfor %} \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 2cde790da7..050f58367d 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -2,6 +2,7 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.item"); +frappe.require('assets/js/item-dashboard.min.js'); frappe.ui.form.on("Item", { onload: function(frm) { @@ -41,6 +42,7 @@ frappe.ui.form.on("Item", { // make sensitive fields(has_serial_no, is_stock_item, valuation_method) // read only if any stock ledger entry exists + erpnext.item.make_dashboard(frm); // clear intro @@ -80,15 +82,14 @@ frappe.ui.form.on("Item", { frm.dashboard.heatmap_message = __('This is based on stock movement. See {0} for details', ['' + __('Stock Ledger') + '']); frm.dashboard.show_dashboard(); - }, - dashboard_update: function(frm) { - if(frm.dashboard_data.stock_data && frm.dashboard_data.stock_data.length) { - var context = erpnext.get_item_dashboard_data(frm.dashboard_data.stock_data, 0); - frm.dashboard.add_section('
    Stock Levels
    \ - '; - } + var section = frm.dashboard.add_section('
    Stock Levels
    '); + erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({ + parent: section, + item_code: frm.doc.name + }); + erpnext.item.item_dashboard.refresh(); + }, validate: function(frm){ diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 114bbabc6e..aa5f330bf9 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -617,7 +617,6 @@ def get_dashboard_data(name): return { 'count': get_open_count('Item', name), 'timeline_data': get_timeline_data(name), - 'stock_data': get_stock_data(name) } def get_timeline_data(name): @@ -627,11 +626,6 @@ def get_timeline_data(name): 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', - 'reserved_qty', 'reserved_qty_for_production'], - filters={'item_code': name}, order_by = 'warehouse asc') - 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/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index 4cb0d906c6..0ac941085c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -67,7 +67,7 @@ def make_stock_entry(**args): "s_warehouse": args.source, "t_warehouse": args.target, "qty": args.qty, - "basic_rate": args.basic_rate, + "basic_rate": args.rate or args.basic_rate, "conversion_factor": 1.0, "serial_no": args.serial_no, 'cost_center': args.cost_center diff --git a/erpnext/stock/page/stock_balance/stock_balance.html b/erpnext/stock/page/stock_balance/stock_balance.html deleted file mode 100644 index 560f8436d3..0000000000 --- a/erpnext/stock/page/stock_balance/stock_balance.html +++ /dev/null @@ -1,7 +0,0 @@ -
    -
    -
    - -
    \ No newline at end of file diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js index 0fc8984315..7bf4792cb3 100644 --- a/erpnext/stock/page/stock_balance/stock_balance.js +++ b/erpnext/stock/page/stock_balance/stock_balance.js @@ -1,4 +1,4 @@ -{% include 'erpnext/stock/doctype/item/item_dashboard.html' %} +frappe.require('assets/js/item-dashboard.min.js'); frappe.pages['stock-balance'].on_page_load = function(wrapper) { var page = frappe.ui.make_app_page({ @@ -6,6 +6,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { title: 'Stock Balance', single_column: true }); + page.start = 0; page.warehouse_field = page.add_field({ fieldname: 'wareshouse', @@ -13,8 +14,8 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { fieldtype:'Link', options:'Warehouse', change: function() { - page.start = 0; - refresh() + page.item_dashboard.start = 0; + page.item_dashboard.refresh(); } }); @@ -24,35 +25,11 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { fieldtype:'Link', options:'Item', change: function() { - page.start = 0; - refresh() + page.item_dashboard.start = 0; + page.item_dashboard.refresh(); } }); - page.start = 0; - page.sort_by = 'projected_qty'; - page.sort_order = 'asc'; - - page.content = $(frappe.render_template('stock_balance')).appendTo(page.main); - page.result = page.content.find('.result'); - - // more - page.content.find('.btn-more').on('click', function() { - page.start += 20; - refresh(); - }); - - // move - page.content.on('click', '.btn-move', function() { - erpnext.inventory.move_item($(this).attr('data-item'), $(this).attr('data-warehouse'), - null, $(this).attr('data-actual_qty'), function() { refresh(); }); - }); - - page.content.on('click', '.btn-add', function() { - erpnext.inventory.move_item($(this).attr('data-item'), null, $(this).attr('data-warehouse'), - $(this).attr('data-actual_qty'), function() { refresh(); }); - }); - page.sort_selector = new frappe.ui.SortSelector({ parent: page.wrapper.find('.page-form'), args: { @@ -66,68 +43,36 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { ] }, change: function(sort_by, sort_order) { - page.sort_by = sort_by; - page.sort_order = sort_order; - page.start = 0; - refresh(); + page.item_dashboard.sort_by = sort_by; + page.item_dashboard.sort_order = sort_order; + page.item_dashboard.start = 0; + page.item_dashboard.refresh(); } }); page.sort_selector.wrapper.css({'margin-right': '15px', 'margin-top': '4px'}); - var refresh = function() { - var item_code = page.item_field.get_value(); - var warehouse = page.warehouse_field.get_value(); - frappe.call({ - method: 'erpnext.stock.page.stock_balance.stock_balance.get_data', - args: { - item_code: item_code, - warehouse: warehouse, - start: page.start, - sort_by: page.sort_by, - sort_order: page.sort_order, - }, - callback: function(r) { - render(r.message); - } - }); + page.item_dashboard = new erpnext.stock.ItemDashboard({ + parent: page.main, + }) + + page.item_dashboard.before_refresh = function() { + this.item_code = page.item_field.get_value(); + this.warehouse = page.warehouse_field.get_value(); } - var render = function(data) { - if(page.start===0) { - page.max_count = 0; - page.result.empty(); - } - - var context = erpnext.get_item_dashboard_data(data, page.max_count, true); - page.max_count = context.max_count; - - // show more button - if(data.length===21) { - page.content.find('.more').removeClass('hidden'); - - // remove the last element - data.splice(-1); - } else { - page.content.find('.more').addClass('hidden'); - } - - $(frappe.render_template('item_dashboard', context)).appendTo(page.result); - - } - - refresh(); + page.item_dashboard.refresh(); // item click var setup_click = function(doctype) { - page.result.on('click', 'a[data-type="'+ doctype.toLowerCase() +'"]', function() { + page.main.on('click', 'a[data-type="'+ doctype.toLowerCase() +'"]', function() { var name = $(this).attr('data-name'); var field = page[doctype.toLowerCase() + '_field']; if(field.get_value()===name) { frappe.set_route('Form', doctype, name) } else { field.set_input(name); - refresh(); + page.item_dashboard.refresh(); } }); }