[wip] inventory tool

This commit is contained in:
Rushabh Mehta 2016-04-22 17:22:22 +05:30
parent 68d428bfd4
commit 0394aec6a4
9 changed files with 191 additions and 65 deletions

View File

@ -20,6 +20,7 @@
"public/js/pos/pos_tax_row.html", "public/js/pos/pos_tax_row.html",
"public/js/pos/pos.js", "public/js/pos/pos.js",
"public/js/utils/item_selector.js", "public/js/utils/item_selector.js",
"public/js/utils/inventory.js",
"public/js/templates/item_selector.html" "public/js/templates/item_selector.html"
] ]
} }

View File

@ -132,22 +132,3 @@ $(document).on('app_ready', function() {
}); });
} }
}); });
erpnext.get_item_dashboard_data = function(data, max_count) {
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
}
}

View File

@ -0,0 +1,77 @@
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',
['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
dialog.hide();
callback(r);
},
});
});
}

View File

@ -1,11 +1,11 @@
{% for d in data %} {% for d in data %}
<li class="list-group-item" style="background-color: inherit;"> <li class="list-group-item" style="background-color: inherit;">
<div class="row"> <div class="row">
<div class="col-sm-4 small" style="margin-top: 8px;"> <div class="col-sm-3 small" style="margin-top: 8px;">
<a data-type="warehouse" data-name="{{ d.warehouse }}">{{ d.warehouse }}</a> <a data-type="warehouse" data-name="{{ d.warehouse }}">{{ d.warehouse }}</a>
</div> </div>
<div class="col-sm-4 small" style="margin-top: 8px;"> <div class="col-sm-3 small" style="margin-top: 8px;">
{% if d.item_code %} {% if show_item %}
<a data-type="item" <a data-type="item"
data-name="{{ d.item_code }}">{{ d.item_code }}</a> data-name="{{ d.item_code }}">{{ d.item_code }}</a>
{% endif %} {% endif %}
@ -37,6 +37,18 @@
</span> </span>
</span> </span>
</div> </div>
<div class="col-sm-2 text-right" style="margin-top: 8px;">
{% if d.actual_qty %}
<button class="btn btn-default btn-xs btn-move"
data-warehouse="{{ d.warehouse }}"
data-actual_qty="{{ d.actual_qty }}"
data-item="{{ d.item_code }}">{{ __("Move") }}</a>
{% endif %}
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add"
data-warehouse="{{ d.warehouse }}"
data-actual_qty="{{ d.actual_qty }}"
data-item="{{ d.item_code }}">{{ __("Add") }}</a>
</div>
</div> </div>
</li> </li>
{% endfor %} {% endfor %}

View File

@ -1,16 +1,34 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import frappe import frappe, erpnext
from frappe.utils import cint, flt
@frappe.whitelist()
def make_stock_entry(**args): def make_stock_entry(**args):
s = frappe.new_doc("Stock Entry") s = frappe.new_doc("Stock Entry")
args = frappe._dict(args) args = frappe._dict(args)
if args.posting_date: if args.posting_date:
s.posting_date = args.posting_date s.posting_date = args.posting_date
if args.posting_time: if args.posting_time:
s.posting_time = args.posting_time s.posting_time = args.posting_time
# map names
if args.from_warehouse:
args.source = args.from_warehouse
if args.to_warehouse:
args.target = args.to_warehouse
if args.item_code:
args.item = args.item_code
if isinstance(args.qty, basestring):
if '.' in args.qty:
args.qty = flt(args.qty)
else:
args.qty = cint(args.qty)
# purpose
if not args.purpose: if not args.purpose:
if args.source and args.target: if args.source and args.target:
s.purpose = "Material Transfer" s.purpose = "Material Transfer"
@ -21,21 +39,34 @@ def make_stock_entry(**args):
else: else:
s.purpose = args.purpose s.purpose = args.purpose
s.company = args.company or "_Test Company" # company
if not args.company:
if args.source:
args.company = frappe.db.get_value('Warehouse', args.source, 'company')
elif args.target:
args.company = frappe.db.get_value('Warehouse', args.target, 'company')
# set vales from test
if frappe.flags.in_test:
if not args.company:
args.company = '_Test Company'
if not args.item:
args.item = '_Test Item'
s.company = args.company or erpnext.get_default_company()
s.purchase_receipt_no = args.purchase_receipt_no s.purchase_receipt_no = args.purchase_receipt_no
s.delivery_note_no = args.delivery_note_no s.delivery_note_no = args.delivery_note_no
s.sales_invoice_no = args.sales_invoice_no s.sales_invoice_no = args.sales_invoice_no
s.difference_account = args.difference_account or "Stock Adjustment - _TC" if args.difference_account:
s.difference_account = args.difference_account
s.append("items", { s.append("items", {
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item,
"s_warehouse": args.from_warehouse or args.source, "s_warehouse": args.source,
"t_warehouse": args.to_warehouse or args.target, "t_warehouse": args.target,
"qty": args.qty, "qty": args.qty,
"basic_rate": args.basic_rate, "basic_rate": args.basic_rate,
"expense_account": args.expense_account or "Stock Adjustment - _TC",
"conversion_factor": 1.0, "conversion_factor": 1.0,
"cost_center": "_Test Cost Center - _TC",
"serial_no": args.serial_no "serial_no": args.serial_no
}) })

View File

@ -365,7 +365,7 @@ def get_projected_qty(item_code, warehouse):
def get_bin_details(item_code, warehouse): def get_bin_details(item_code, warehouse):
return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
["projected_qty", "actual_qty"], as_dict=True) \ ["projected_qty", "actual_qty"], as_dict=True) \
or {"projected_qty": 0, "actual_qty": 0, "valuation_rate": 0} or {"projected_qty": 0, "actual_qty": 0}
@frappe.whitelist() @frappe.whitelist()
def get_batch_qty(batch_no,warehouse,item_code): def get_batch_qty(batch_no,warehouse,item_code):

View File

@ -1,11 +1,5 @@
<div class="padding"> <div class="padding">
<div class="row" style="margin-bottom: 15px;">
<div class="col-sm-8"></div>
<div class="col-sm-4 sort-selector-area">
</div>
</div>
<div class="result list-group"> <div class="result list-group">
</div> </div>
<div class="more hidden" style="padding-top: 15px;"> <div class="more hidden" style="padding-top: 15px;">
<a class="btn btn-default btn-xs btn-more">More</a> <a class="btn btn-default btn-xs btn-more">More</a>

View File

@ -7,7 +7,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
single_column: true single_column: true
}); });
var warehouse_field = page.add_field({ page.warehouse_field = page.add_field({
fieldname: 'wareshouse', fieldname: 'wareshouse',
label: __('Warehouse'), label: __('Warehouse'),
fieldtype:'Link', fieldtype:'Link',
@ -18,7 +18,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
} }
}); });
var item_field = page.add_field({ page.item_field = page.add_field({
fieldname: 'item_code', fieldname: 'item_code',
label: __('Item'), label: __('Item'),
fieldtype:'Link', fieldtype:'Link',
@ -30,8 +30,8 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
}); });
page.start = 0; page.start = 0;
page.sort_by = 'actual_qty'; page.sort_by = 'projected_qty';
page.sort_order = 'desc'; page.sort_order = 'asc';
page.content = $(frappe.render_template('stock_balance')).appendTo(page.main); page.content = $(frappe.render_template('stock_balance')).appendTo(page.main);
page.result = page.content.find('.result'); page.result = page.content.find('.result');
@ -42,8 +42,19 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
refresh(); 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({ page.sort_selector = new frappe.ui.SortSelector({
parent: page.content.find('.sort-selector-area'), parent: page.wrapper.find('.page-form'),
args: { args: {
sort_by: 'projected_qty', sort_by: 'projected_qty',
sort_order: 'asc', sort_order: 'asc',
@ -60,11 +71,13 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
page.start = 0; page.start = 0;
refresh(); refresh();
} }
}) });
page.sort_selector.wrapper.css({'margin-right': '15px', 'margin-top': '4px'});
var refresh = function() { var refresh = function() {
var item_code = item_field.get_value(); var item_code = page.item_field.get_value();
var warehouse = warehouse_field.get_value(); var warehouse = page.warehouse_field.get_value();
frappe.call({ frappe.call({
method: 'erpnext.stock.page.stock_balance.stock_balance.get_data', method: 'erpnext.stock.page.stock_balance.stock_balance.get_data',
args: { args: {
@ -86,7 +99,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
page.result.empty(); page.result.empty();
} }
var context = erpnext.get_item_dashboard_data(data, page.max_count); var context = erpnext.get_item_dashboard_data(data, page.max_count, true);
page.max_count = context.max_count; page.max_count = context.max_count;
// show more button // show more button
@ -105,4 +118,21 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
refresh(); refresh();
// item click
var setup_click = function(doctype) {
page.result.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();
}
});
}
setup_click('Item');
setup_click('Warehouse');
} }

View File

@ -6,9 +6,9 @@ import frappe
def get_data(item_code=None, warehouse=None, start=0, sort_by='actual_qty', sort_order='desc'): def get_data(item_code=None, warehouse=None, start=0, sort_by='actual_qty', sort_order='desc'):
filters = {} filters = {}
if item_code: if item_code:
filters = {'item_code': item_code } filters['item_code'] = item_code
if warehouse: if warehouse:
filters = {'warehouse': warehouse } filters['warehouse'] = warehouse
return frappe.get_list("Bin", filters=filters, fields=['item_code', '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'],
order_by='{0} {1}'.format(sort_by, sort_order), start=start, page_length = 21) order_by='{0} {1}'.format(sort_by, sort_order), start=start, page_length = 21)