[wip] inventory tool
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index b3da719..16e7488 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -20,6 +20,7 @@
"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"
]
}
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index f08cf42..8bca282 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -131,23 +131,4 @@
});
});
}
-});
-
-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
- }
-}
+});
\ No newline at end of file
diff --git a/erpnext/public/js/utils/inventory.js b/erpnext/public/js/utils/inventory.js
new file mode 100644
index 0000000..80cd6a5
--- /dev/null
+++ b/erpnext/public/js/utils/inventory.js
@@ -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);
+ },
+ });
+ });
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item_dashboard.html b/erpnext/stock/doctype/item/item_dashboard.html
index ac78e79..813aef3 100644
--- a/erpnext/stock/doctype/item/item_dashboard.html
+++ b/erpnext/stock/doctype/item/item_dashboard.html
@@ -1,11 +1,11 @@
{% for d in data %}
<li class="list-group-item" style="background-color: inherit;">
<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>
</div>
- <div class="col-sm-4 small" style="margin-top: 8px;">
- {% if d.item_code %}
+ <div class="col-sm-3 small" style="margin-top: 8px;">
+ {% if show_item %}
<a data-type="item"
data-name="{{ d.item_code }}">{{ d.item_code }}</a>
{% endif %}
@@ -37,6 +37,18 @@
</span>
</span>
</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>
</li>
{% endfor %}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index dcfc304..c32c99b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -1,16 +1,34 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import frappe
+import frappe, erpnext
+from frappe.utils import cint, flt
+@frappe.whitelist()
def make_stock_entry(**args):
s = frappe.new_doc("Stock Entry")
args = frappe._dict(args)
+
if args.posting_date:
s.posting_date = args.posting_date
if 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 args.source and args.target:
s.purpose = "Material Transfer"
@@ -21,21 +39,34 @@
else:
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.delivery_note_no = args.delivery_note_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", {
- "item_code": args.item or args.item_code or "_Test Item",
- "s_warehouse": args.from_warehouse or args.source,
- "t_warehouse": args.to_warehouse or args.target,
+ "item_code": args.item,
+ "s_warehouse": args.source,
+ "t_warehouse": args.target,
"qty": args.qty,
"basic_rate": args.basic_rate,
- "expense_account": args.expense_account or "Stock Adjustment - _TC",
"conversion_factor": 1.0,
- "cost_center": "_Test Cost Center - _TC",
"serial_no": args.serial_no
})
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index d8c4382..c42350a 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -48,7 +48,7 @@
if frappe.db.exists("Product Bundle", args.item_code):
valuation_rate = 0.0
bundled_items = frappe.get_doc("Product Bundle", args.item_code)
-
+
for bundle_item in bundled_items.items:
valuation_rate += \
flt(get_valuation_rate(bundle_item.item_code, out.get("warehouse")).get("valuation_rate") \
@@ -83,7 +83,7 @@
if args.get("is_subcontracted") == "Yes":
out.bom = get_default_bom(args.item_code)
-
+
get_gross_profit(out)
return out
@@ -101,7 +101,7 @@
args.item_code = get_item_code(barcode=args.barcode)
elif not args.item_code and args.serial_no:
args.item_code = get_item_code(serial_no=args.serial_no)
-
+
set_transaction_type(args)
return args
@@ -127,7 +127,7 @@
if args.transaction_type=="selling" and cint(item.has_variants):
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
-
+
elif args.transaction_type=="buying" and args.doctype != "Material Request":
if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1:
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
@@ -143,7 +143,7 @@
user_default_warehouse_list = get_user_default_as_list('Warehouse')
user_default_warehouse = user_default_warehouse_list[0] \
if len(user_default_warehouse_list)==1 else ""
-
+
warehouse = user_default_warehouse or args.warehouse or item.default_warehouse
out = frappe._dict({
@@ -177,8 +177,8 @@
})
# if default specified in item is for another company, fetch from company
- for d in [["Account", "income_account", "default_income_account"],
- ["Account", "expense_account", "default_expense_account"],
+ for d in [["Account", "income_account", "default_income_account"],
+ ["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"], ["Warehouse", "warehouse", ""]]:
company = frappe.db.get_value(d[0], out.get(d[1]), "company")
if not out[d[1]] or (company and args.company != company):
@@ -365,7 +365,7 @@
def get_bin_details(item_code, warehouse):
return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
["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()
def get_batch_qty(batch_no,warehouse,item_code):
@@ -473,31 +473,31 @@
return bom
else:
frappe.throw(_("No default BOM exists for Item {0}").format(item_code))
-
+
def get_valuation_rate(item_code, warehouse=None):
item = frappe.get_doc("Item", item_code)
if item.is_stock_item:
if not warehouse:
warehouse = item.default_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},
["valuation_rate"], as_dict=True) or {"valuation_rate": 0}
-
+
elif not item.is_stock_item:
- valuation_rate =frappe.db.sql("""select sum(base_net_amount) / sum(qty)
- from `tabPurchase Invoice Item`
+ valuation_rate =frappe.db.sql("""select sum(base_net_amount) / sum(qty)
+ from `tabPurchase Invoice Item`
where item_code = %s and docstatus=1""", item_code)
-
+
if valuation_rate:
return {"valuation_rate": valuation_rate[0][0] or 0.0}
else:
return {"valuation_rate": 0.0}
-
+
def get_gross_profit(out):
if out.valuation_rate:
out.update({
"gross_profit": ((out.base_rate - out.valuation_rate) * out.qty)
})
-
+
return out
diff --git a/erpnext/stock/page/stock_balance/stock_balance.html b/erpnext/stock/page/stock_balance/stock_balance.html
index a76252e..560f843 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.html
+++ b/erpnext/stock/page/stock_balance/stock_balance.html
@@ -1,11 +1,5 @@
<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>
<div class="more hidden" style="padding-top: 15px;">
<a class="btn btn-default btn-xs btn-more">More</a>
diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js
index f19154b..0fc8984 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.js
+++ b/erpnext/stock/page/stock_balance/stock_balance.js
@@ -7,7 +7,7 @@
single_column: true
});
- var warehouse_field = page.add_field({
+ page.warehouse_field = page.add_field({
fieldname: 'wareshouse',
label: __('Warehouse'),
fieldtype:'Link',
@@ -18,7 +18,7 @@
}
});
- var item_field = page.add_field({
+ page.item_field = page.add_field({
fieldname: 'item_code',
label: __('Item'),
fieldtype:'Link',
@@ -30,8 +30,8 @@
});
page.start = 0;
- page.sort_by = 'actual_qty';
- page.sort_order = 'desc';
+ 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');
@@ -42,8 +42,19 @@
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.content.find('.sort-selector-area'),
+ parent: page.wrapper.find('.page-form'),
args: {
sort_by: 'projected_qty',
sort_order: 'asc',
@@ -60,11 +71,13 @@
page.start = 0;
refresh();
}
- })
+ });
+
+ page.sort_selector.wrapper.css({'margin-right': '15px', 'margin-top': '4px'});
var refresh = function() {
- var item_code = item_field.get_value();
- var warehouse = warehouse_field.get_value();
+ 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: {
@@ -86,7 +99,7 @@
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;
// show more button
@@ -105,4 +118,21 @@
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');
+
}
\ No newline at end of file
diff --git a/erpnext/stock/page/stock_balance/stock_balance.py b/erpnext/stock/page/stock_balance/stock_balance.py
index 1f67401..e96f925 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.py
+++ b/erpnext/stock/page/stock_balance/stock_balance.py
@@ -6,9 +6,9 @@
def get_data(item_code=None, warehouse=None, start=0, sort_by='actual_qty', sort_order='desc'):
filters = {}
if item_code:
- filters = {'item_code': item_code }
+ filters['item_code'] = item_code
if warehouse:
- filters = {'warehouse': 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'],
order_by='{0} {1}'.format(sort_by, sort_order), start=start, page_length = 21)
\ No newline at end of file