From 0394aec6a476517835a11b41d921b0a4fcbfd4ab Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 22 Apr 2016 17:22:22 +0530 Subject: [PATCH 1/3] [wip] inventory tool --- erpnext/public/build.json | 1 + erpnext/public/js/utils.js | 21 +---- erpnext/public/js/utils/inventory.js | 77 +++++++++++++++++++ .../stock/doctype/item/item_dashboard.html | 18 ++++- .../doctype/stock_entry/stock_entry_utils.py | 47 +++++++++-- erpnext/stock/get_item_details.py | 34 ++++---- .../page/stock_balance/stock_balance.html | 6 -- .../stock/page/stock_balance/stock_balance.js | 48 +++++++++--- .../stock/page/stock_balance/stock_balance.py | 4 +- 9 files changed, 191 insertions(+), 65 deletions(-) create mode 100644 erpnext/public/js/utils/inventory.js diff --git a/erpnext/public/build.json b/erpnext/public/build.json index b3da719691..16e7488627 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 f08cf4279d..8bca282a2f 100644 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -131,23 +131,4 @@ $(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 - } -} +}); \ 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 0000000000..80cd6a5268 --- /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', + ['' + r.message.name+ ''])); + 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 ac78e7935d..813aef33e2 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 %}
  • -
    + -
    - {% if d.item_code %} +
    + {% if show_item %} {{ d.item_code }} {% endif %} @@ -37,6 +37,18 @@
    +
    + {% if d.actual_qty %} +
  • {% 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 dcfc304655..c32c99b8e3 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 @@ def make_stock_entry(**args): 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 d8c4382283..c42350a421 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -48,7 +48,7 @@ def get_item_details(args): 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 @@ def get_item_details(args): if args.get("is_subcontracted") == "Yes": out.bom = get_default_bom(args.item_code) - + get_gross_profit(out) return out @@ -101,7 +101,7 @@ def process_args(args): 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 @@ def validate_item_details(args, item): 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 @@ def get_basic_details(args, item): 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 @@ def get_basic_details(args, item): }) # 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_projected_qty(item_code, warehouse): 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 @@ def get_default_bom(item_code=None): 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 a76252ea22..560f8436d3 100644 --- a/erpnext/stock/page/stock_balance/stock_balance.html +++ b/erpnext/stock/page/stock_balance/stock_balance.html @@ -1,11 +1,5 @@
    -
    -
    -
    -
    -
    -