Merge pull request #5518 from saurabh6790/warehouse_tree
Warehouse tree
diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js
index 703397e..44c2ca4 100644
--- a/erpnext/accounts/doctype/account/account.js
+++ b/erpnext/accounts/doctype/account/account.js
@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.list_route = "Accounts Browser/Account";
+cur_frm.list_route = "Tree/Account";
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
if(doc.__islocal) {
@@ -48,7 +48,7 @@
cur_frm.cscript.add_toolbar_buttons = function(doc) {
cur_frm.add_custom_button(__('Chart of Accounts'),
- function() { frappe.set_route("Accounts Browser", "Account"); }, __("View"))
+ function() { frappe.set_route("Tree", "Account"); }, __("View"))
if (doc.is_group == 1) {
cur_frm.add_custom_button(__('Group to Non-Group'),
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 718ba31..918917c 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -179,9 +179,12 @@
self.warehouse = None
def validate_warehouse(self, warehouse):
- if frappe.db.get_value("Stock Ledger Entry", {"warehouse": warehouse}):
+ lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
+
+ if frappe.db.sql_list("""select sle.name from `tabStock Ledger Entry` sle where exists (select wh.name from
+ tabWarehouse wh where lft >= %s and rgt <= %s and sle.warehouse = wh.name)""", (lft, rgt)):
throw(_("Stock entries exist against warehouse {0}, hence you cannot re-assign or modify Warehouse").format(warehouse))
-
+
def update_nsm_model(self):
"""update lft, rgt indices for nested set model"""
import frappe
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
new file mode 100644
index 0000000..3252788
--- /dev/null
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -0,0 +1,52 @@
+frappe.treeview_settings["Account"] = {
+ breadcrumbs: "Accounts",
+ title: __("Chart Of Accounts"),
+ get_tree_root: false,
+ filters: [{
+ fieldname: "company",
+ fieldtype:"Select",
+ options: $.map(locals[':Company'], function(c) { return c.name; }).sort(),
+ label: __("Company"),
+ default: frappe.defaults.get_default('company') ? frappe.defaults.get_default('company'): ""
+ }],
+ root_label: "Accounts",
+ get_tree_nodes: 'erpnext.accounts.utils.get_children',
+ add_tree_node: 'erpnext.accounts.utils.add_ac',
+ menu_items:[
+ {
+ label: __('New Company'),
+ action: function() { newdoc('Company'); },
+ condition: 'frappe.boot.user.can_create.indexOf("Company") === -1'
+ }
+ ],
+ fields: [
+ {fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
+ description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
+ {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
+ description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
+ {fieldtype:'Select', fieldname:'root_type', label:__('Root Type'),
+ options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n')},
+ {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
+ options: ['', 'Bank', 'Cash', 'Warehouse', 'Tax', 'Chargeable'].join('\n'),
+ description: __("Optional. This setting will be used to filter in various transactions."),
+ depends_on: 'eval:doc.is_group==1'},
+ {fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate'),
+ depends_on: 'eval:doc.is_group==1&&doc.account_type=="Tax"'},
+ {fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse",
+ depends_on: 'eval:(doc.is_group==1&&doc.account_type=="Warehouse")'},
+ {fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
+ description: __("Optional. Sets company's default currency, if not specified.")}
+ ],
+ onrender: function(node) {
+ var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr";
+ if (node.data && node.data.balance!==undefined) {
+ $('<span class="balance-area pull-right text-muted small">'
+ + (node.data.balance_in_account_currency ?
+ (format_currency(Math.abs(node.data.balance_in_account_currency),
+ node.data.account_currency) + " / ") : "")
+ + format_currency(Math.abs(node.data.balance), node.data.company_currency)
+ + " " + dr_or_cr
+ + '</span>').insertBefore(node.$ul);
+ }
+ }
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index 85a6052..a541901 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -3,7 +3,7 @@
frappe.provide("erpnext.accounts");
-cur_frm.list_route = "Accounts Browser/Cost Center";
+cur_frm.list_route = "Tree/Cost Center";
frappe.ui.form.on('Cost Center', {
@@ -34,7 +34,7 @@
cur_frm.set_intro(intro_txt);
cur_frm.add_custom_button(__('Chart of Cost Centers'),
- function() { frappe.set_route("Accounts Browser", "Cost Center"); }, __("View"))
+ function() { frappe.set_route("Tree", "Cost Center"); }, __("View"))
}
cur_frm.cscript.parent_cost_center = function(doc, cdt, cdn) {
diff --git a/erpnext/accounts/doctype/cost_center/cost_center_tree.js b/erpnext/accounts/doctype/cost_center/cost_center_tree.js
new file mode 100644
index 0000000..ac82f23
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center/cost_center_tree.js
@@ -0,0 +1,26 @@
+frappe.treeview_settings["Cost Center"] = {
+ breadcrumbs: "Accounts",
+ get_tree_root: false,
+ filters: [{
+ fieldname: "company",
+ fieldtype:"Select",
+ options: $.map(locals[':Company'], function(c) { return c.name; }).sort(),
+ label: __("Company"),
+ default: frappe.defaults.get_default('company') ? frappe.defaults.get_default('company'): ""
+ }],
+ root_label: "Cost Centers",
+ get_tree_nodes: 'erpnext.accounts.utils.get_children',
+ add_tree_node: 'erpnext.accounts.utils.add_cc',
+ menu_items:[
+ {
+ label: __('New Company'),
+ action: function() { newdoc('Company'); },
+ condition: 'frappe.boot.user.can_create.indexOf("Company") === -1'
+ }
+ ],
+ fields:[
+ {fieldtype:'Data', fieldname:'cost_center_name', label:__('New Cost Center Name'), reqd:true},
+ {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
+ description:__('Further cost centers can be made under Groups but entries can be made against non-Groups')}
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/page/accounts_browser/README.md b/erpnext/accounts/page/accounts_browser/README.md
deleted file mode 100644
index b879561..0000000
--- a/erpnext/accounts/page/accounts_browser/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Tree view browser for Chart of Accounts and Chart of Cost Centers
\ No newline at end of file
diff --git a/erpnext/accounts/page/accounts_browser/__init__.py b/erpnext/accounts/page/accounts_browser/__init__.py
deleted file mode 100644
index baffc48..0000000
--- a/erpnext/accounts/page/accounts_browser/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from __future__ import unicode_literals
diff --git a/erpnext/accounts/page/accounts_browser/accounts_browser.js b/erpnext/accounts/page/accounts_browser/accounts_browser.js
deleted file mode 100644
index ec906f8..0000000
--- a/erpnext/accounts/page/accounts_browser/accounts_browser.js
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-// tree of chart of accounts / cost centers
-// multiple companies
-// add node
-// edit node
-// see ledger
-
-frappe.pages["Accounts Browser"].on_page_load = function(wrapper){
- frappe.ui.make_app_page({
- parent: wrapper,
- single_column: true
- })
-
- frappe.breadcrumbs.add("Accounts");
-
- var main = wrapper.page.main,
- chart_area = $("<div>")
- .css({"margin-bottom": "15px", "min-height": "200px"})
- .appendTo(main),
- help_area = $('<hr><div style="padding: 0px 15px;">'+
- '<h4>'+__('Quick Help')+'</h4>'+
- '<ol>'+
- '<li>'+__('To add child nodes, explore tree and click on the node under which you want to add more nodes.')+'</li>'+
- '<li>'+
- __('Accounting Entries can be made against leaf nodes. Entries against Groups are not allowed.')+
- '</li>'+
- '<li>'+__('Please do NOT create Accounts for Customers and Suppliers. They are created directly from the Customer / Supplier masters.')+'</li>'+
- '<li>'+
- '<b>'+__('To create a Bank Account')+'</b>: '+
- __('Go to the appropriate group (usually Application of Funds > Current Assets > Bank Accounts and create a new Account (by clicking on Add Child) of type "Bank"')+
- '</li>'+
- '<li>'+
- '<b>'+__('To create a Tax Account') +'</b>: '+
- __('Go to the appropriate group (usually Source of Funds > Current Liabilities > Taxes and Duties and create a new Account (by clicking on Add Child) of type "Tax" and do mention the Tax rate.')+
- '</li>'+
- '</ol>'+
- '<p>'+__('Please setup your chart of accounts before you start Accounting Entries')+'</p></div>').appendTo(main);
-
- if (frappe.boot.user.can_create.indexOf("Company") !== -1) {
- wrapper.page.add_menu_item(__('New Company'), function() { newdoc('Company'); }, true);
- }
-
- wrapper.page.add_menu_item(__('Refresh'), function() {
- wrapper.$company_select.change();
- });
-
- wrapper.page.set_primary_action(__('New'), function() {
- erpnext.account_chart && erpnext.account_chart.make_new();
- }, "octicon octicon-plus");
-
- var company_list = $.map(locals[':Company'], function(c) { return c.name; }).sort();
-
- // company-select
- wrapper.$company_select = wrapper.page.add_select("Company", company_list)
- .change(function() {
- var ctype = frappe.get_route()[1] || 'Account';
- erpnext.account_chart = new erpnext.AccountsChart(ctype, $(this).val(),
- chart_area.get(0), wrapper.page);
- })
-
- if(frappe.defaults.get_default('company')) {
- wrapper.$company_select.val(frappe.defaults.get_default('company'));
- }
- wrapper.$company_select.change();
-}
-
-frappe.pages["Accounts Browser"].on_page_show = function(wrapper){
- // set route
- var ctype = frappe.get_route()[1] || 'Account';
-
- if(frappe.route_options) {
- if(frappe.route_options.company) {
- wrapper.$company_select.val(frappe.route_options.company).change();
- }
- frappe.route_options = null;
- } else if(erpnext.account_chart && erpnext.account_chart.ctype != ctype) {
- wrapper.$company_select.change();
- }
-
-}
-
-erpnext.AccountsChart = Class.extend({
- init: function(ctype, company, wrapper, page) {
- $(wrapper).empty();
- var me = this;
- me.ctype = ctype;
- me.can_create = frappe.model.can_create(this.ctype);
- me.can_delete = frappe.model.can_delete(this.ctype);
- me.can_write = frappe.model.can_write(this.ctype);
- me.page = page;
- me.set_title();
-
- // __("Accounts"), __("Cost Centers")
-
- me.company = company;
- this.tree = new frappe.ui.Tree({
- parent: $(wrapper),
- label: ctype==="Account" ? "Accounts" : "Cost Centers",
- args: {ctype: ctype, comp: company},
- method: 'erpnext.accounts.page.accounts_browser.accounts_browser.get_children',
- click: function(link) {
- // bold
- $('.bold').removeClass('bold'); // deselect
- $(link).parent().find('.balance-area:first').addClass('bold'); // select
-
- },
- toolbar: [
- { toggle_btn: true },
- {
- label: __("Open"),
- condition: function(node) { return !node.root },
- click: function(node, btn) {
- frappe.set_route("Form", me.ctype, node.label);
- }
- },
- {
- condition: function(node) { return node.expandable; },
- label: __("Add Child"),
- click: function() {
- me.make_new()
- },
- btnClass: "hidden-xs"
- },
- {
- condition: function(node) {
- return !node.root && me.ctype === 'Account'
- && frappe.boot.user.can_read.indexOf("GL Entry") !== -1
- },
- label: __("View Ledger"),
- click: function(node, btn) {
- frappe.route_options = {
- "account": node.label,
- "from_date": sys_defaults.year_start_date,
- "to_date": sys_defaults.year_end_date,
- "company": me.company
- };
- frappe.set_route("query-report", "General Ledger");
- },
- btnClass: "hidden-xs"
- },
- {
- condition: function(node) { return !node.root && me.can_write },
- label: __("Rename"),
- click: function(node) {
- frappe.model.rename_doc(me.ctype, node.label, function(new_name) {
- node.reload_parent();
- });
- },
- btnClass: "hidden-xs"
- },
- {
- condition: function(node) { return !node.root && me.can_delete },
- label: __("Delete"),
- click: function(node) {
- frappe.model.delete_doc(me.ctype, node.label, function() {
- node.parent.remove();
- });
- },
- btnClass: "hidden-xs"
- }
- ],
- onrender: function(node) {
- var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr";
- if (me.ctype == 'Account' && node.data && node.data.balance!==undefined) {
- $('<span class="balance-area pull-right text-muted small">'
- + (node.data.balance_in_account_currency ?
- (format_currency(Math.abs(node.data.balance_in_account_currency),
- node.data.account_currency) + " / ") : "")
- + format_currency(Math.abs(node.data.balance), node.data.company_currency)
- + " " + dr_or_cr
- + '</span>').insertBefore(node.$ul);
- }
- }
- });
- },
- set_title: function(val) {
- var chart_str = this.ctype=="Account" ? __("Chart of Accounts") : __("Chart of Cost Centers");
- if(val) {
- this.page.set_title(chart_str + " - " + cstr(val));
- } else {
- this.page.set_title(chart_str);
- }
- },
-
- make_new: function() {
- if(this.ctype=='Account') {
- this.new_account();
- } else {
- this.new_cost_center();
- }
- },
-
- new_account: function() {
- var me = this;
-
- var node = me.tree.get_selected_node();
-
- if(!(node && node.expandable)) {
- frappe.msgprint(__("Select a group node first."));
- return;
- }
-
- // the dialog
- var d = new frappe.ui.Dialog({
- title:__('New Account'),
- fields: [
- {fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
- description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
- {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
- description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
- {fieldtype:'Select', fieldname:'root_type', label:__('Root Type'),
- options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'),
- },
- {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
- options: ['', 'Bank', 'Cash', 'Warehouse', 'Tax', 'Chargeable'].join('\n'),
- description: __("Optional. This setting will be used to filter in various transactions.") },
- {fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate')},
- {fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse"},
- {fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
- description: __("Optional. Sets company's default currency, if not specified.")}
- ]
- })
-
- var fd = d.fields_dict;
-
- // account type if ledger
- $(fd.is_group.input).change(function() {
- if($(this).prop("checked")) {
- $(fd.account_type.wrapper).toggle(false);
- $(fd.tax_rate.wrapper).toggle(false);
- $(fd.warehouse.wrapper).toggle(false);
- } else {
- $(fd.account_type.wrapper).toggle(node.root ? false : true);
- fd.account_type.$input.trigger("change");
- }
- });
-
- // tax rate if tax
- $(fd.account_type.input).change(function() {
- $(fd.tax_rate.wrapper).toggle(fd.account_type.get_value()==='Tax');
- $(fd.warehouse.wrapper).toggle(fd.account_type.get_value()==='Warehouse');
- })
-
- // create
- d.set_primary_action(__("Create New"), function() {
- var btn = this;
- var v = d.get_values();
- if(!v) return;
-
- if(v.account_type==="Warehouse" && !v.warehouse) {
- msgprint(__("Warehouse is required"));
- return;
- }
-
- var node = me.tree.get_selected_node();
- v.parent_account = node.label;
- v.company = me.company;
-
- if(node.root) {
- v.is_root = 1;
- v.parent_account = null;
- } else {
- v.is_root = 0;
- v.root_type = null;
- }
-
- return frappe.call({
- args: v,
- method: 'erpnext.accounts.utils.add_ac',
- callback: function(r) {
- d.hide();
- if(node.expanded) {
- node.toggle_node();
- }
- node.load();
- }
- });
- });
-
- // show
- d.on_page_show = function() {
- $(fd.is_group.input).change();
- $(fd.account_type.input).change();
- }
-
- $(fd.is_group.input).prop("checked", false).change();
-
- // In case of root, show root type and hide account_type, is_group
- $(fd.root_type.wrapper).toggle(node.root);
- $(fd.is_group.wrapper).toggle(!node.root);
-
- d.show();
- },
-
- new_cost_center: function(){
- var me = this;
- // the dialog
- var d = new frappe.ui.Dialog({
- title:__('New Cost Center'),
- fields: [
- {fieldtype:'Data', fieldname:'cost_center_name', label:__('New Cost Center Name'), reqd:true},
- {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
- description:__('Further cost centers can be made under Groups but entries can be made against non-Groups')},
- {fieldtype:'Button', fieldname:'create_new', label:__('Create New') }
- ]
- });
-
- // create
- $(d.fields_dict.create_new.input).click(function() {
- var v = d.get_values();
- if(!v) return;
-
- var node = me.tree.get_selected_node();
-
- v.parent_cost_center = node.label;
- v.company = me.company;
-
- return frappe.call({
- args: v,
- method: 'erpnext.accounts.utils.add_cc',
- callback: function(r) {
- d.hide();
- if(node.expanded) {
- node.toggle_node();
- }
- node.load();
- }
- });
- });
- d.show();
- }
-});
diff --git a/erpnext/accounts/page/accounts_browser/accounts_browser.json b/erpnext/accounts/page/accounts_browser/accounts_browser.json
deleted file mode 100644
index f0fe2e8..0000000
--- a/erpnext/accounts/page/accounts_browser/accounts_browser.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "creation": "2012-06-14 15:07:28.000000",
- "docstatus": 0,
- "doctype": "Page",
- "icon": "icon-sitemap",
- "idx": 1,
- "modified": "2013-07-11 14:39:42.000000",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "accounts-browser",
- "owner": "Administrator",
- "page_name": "Accounts Browser",
- "roles": [
- {
- "role": "Accounts User"
- },
- {
- "role": "Accounts Manager"
- }
- ],
- "standard": "Yes"
-}
diff --git a/erpnext/accounts/page/accounts_browser/accounts_browser.py b/erpnext/accounts/page/accounts_browser/accounts_browser.py
deleted file mode 100644
index d96b213..0000000
--- a/erpnext/accounts/page/accounts_browser/accounts_browser.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-import frappe.defaults
-from frappe.utils import flt
-from erpnext.accounts.utils import get_balance_on
-from erpnext.accounts.report.financial_statements import sort_root_accounts
-
-@frappe.whitelist()
-def get_companies():
- """get a list of companies based on permission"""
- return [d.name for d in frappe.get_list("Company", fields=["name"],
- order_by="name")]
-
-@frappe.whitelist()
-def get_children():
- args = frappe.local.form_dict
- ctype, company = args['ctype'], args['comp']
- fieldname = frappe.db.escape(ctype.lower().replace(' ','_'))
- doctype = frappe.db.escape(ctype)
-
- # root
- if args['parent'] in ("Accounts", "Cost Centers"):
- fields = ", root_type, report_type, account_currency" if ctype=="Account" else ""
- acc = frappe.db.sql(""" select
- name as value, is_group as expandable {fields}
- from `tab{doctype}`
- where ifnull(`parent_{fieldname}`,'') = ''
- and `company` = %s and docstatus<2
- order by name""".format(fields=fields, fieldname = fieldname, doctype=doctype),
- company, as_dict=1)
-
- if args["parent"]=="Accounts":
- sort_root_accounts(acc)
- else:
- # other
- fields = ", account_currency" if ctype=="Account" else ""
- acc = frappe.db.sql("""select
- name as value, is_group as expandable, parent_{fieldname} as parent {fields}
- from `tab{doctype}`
- where ifnull(`parent_{fieldname}`,'') = %s
- and docstatus<2
- order by name""".format(fields=fields, fieldname=fieldname, doctype=doctype),
- args['parent'], as_dict=1)
-
- if ctype == 'Account':
- company_currency = frappe.db.get_value("Company", company, "default_currency")
- for each in acc:
- each["company_currency"] = company_currency
- each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False))
-
- if each.account_currency != company_currency:
- each["balance_in_account_currency"] = flt(get_balance_on(each.get("value")))
-
- return acc
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 27f1394..fbead26 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -10,6 +10,8 @@
# imported to enable erpnext.accounts.utils.get_account_currency
from erpnext.accounts.doctype.account.account import get_account_currency
+import frappe.defaults
+from erpnext.accounts.report.financial_statements import sort_root_accounts
class FiscalYearError(frappe.ValidationError): pass
@@ -127,7 +129,7 @@
if not args:
args = frappe.local.form_dict
args.pop("cmd")
-
+
ac = frappe.new_doc("Account")
if args.get("ignore_permissions"):
@@ -135,6 +137,10 @@
args.pop("ignore_permissions")
ac.update(args)
+
+ if not ac.parent_account:
+ ac.parent_account = args.get("parent")
+
ac.old_parent = ""
ac.freeze_account = "No"
if cint(ac.get("is_root")):
@@ -153,6 +159,10 @@
cc = frappe.new_doc("Cost Center")
cc.update(args)
+
+ if not cc.parent_cost_center:
+ cc.parent_cost_center = args.get("parent")
+
cc.old_parent = ""
cc.insert()
return cc.name
@@ -428,3 +438,51 @@
"account_currency": account_currency or frappe.defaults.get_defaults().currency,
"company": company or frappe.defaults.get_defaults().company
}, "name")
+
+@frappe.whitelist()
+def get_companies():
+ """get a list of companies based on permission"""
+ return [d.name for d in frappe.get_list("Company", fields=["name"],
+ order_by="name")]
+
+@frappe.whitelist()
+def get_children():
+ args = frappe.local.form_dict
+ doctype, company = args['doctype'], args['company']
+ fieldname = frappe.db.escape(doctype.lower().replace(' ','_'))
+ doctype = frappe.db.escape(doctype)
+
+ # root
+ if args['parent'] in ("Accounts", "Cost Centers"):
+ fields = ", root_type, report_type, account_currency" if doctype=="Account" else ""
+ acc = frappe.db.sql(""" select
+ name as value, is_group as expandable {fields}
+ from `tab{doctype}`
+ where ifnull(`parent_{fieldname}`,'') = ''
+ and `company` = %s and docstatus<2
+ order by name""".format(fields=fields, fieldname = fieldname, doctype=doctype),
+ company, as_dict=1)
+
+ if args["parent"]=="Accounts":
+ sort_root_accounts(acc)
+ else:
+ # other
+ fields = ", account_currency" if doctype=="Account" else ""
+ acc = frappe.db.sql("""select
+ name as value, is_group as expandable, parent_{fieldname} as parent {fields}
+ from `tab{doctype}`
+ where ifnull(`parent_{fieldname}`,'') = %s
+ and docstatus<2
+ order by name""".format(fields=fields, fieldname=fieldname, doctype=doctype),
+ args['parent'], as_dict=1)
+
+ if doctype == 'Account':
+ company_currency = frappe.db.get_value("Company", company, "default_currency")
+ for each in acc:
+ each["company_currency"] = company_currency
+ each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False))
+
+ if each.account_currency != company_currency:
+ each["balance_in_account_currency"] = flt(get_balance_on(each.get("value")))
+
+ return acc
diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py
index e11544a..b6b1480 100644
--- a/erpnext/config/accounts.py
+++ b/erpnext/config/accounts.py
@@ -51,10 +51,10 @@
},
{
"type": "page",
- "name": "Accounts Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
"label": _("Chart of Accounts"),
- "route": "Accounts Browser/Account",
+ "route": "Tree/Account",
"description": _("Tree of financial accounts."),
"doctype": "Account",
},
@@ -193,10 +193,10 @@
"items": [
{
"type": "page",
- "name": "Accounts Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
"label": _("Chart of Cost Centers"),
- "route": "Accounts Browser/Cost Center",
+ "route": "Tree/Cost Center",
"description": _("Tree of financial Cost Centers."),
"doctype": "Cost Center",
},
diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py
index 0dc51f6..b423ee9 100644
--- a/erpnext/config/buying.py
+++ b/erpnext/config/buying.py
@@ -97,10 +97,10 @@
},
{
"type": "page",
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
"label": _("Item Group"),
- "link": "Sales Browser/Item Group",
+ "link": "Tree/Item Group",
"description": _("Tree of Item Groups."),
"doctype": "Item Group",
},
diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py
index dfefb75..52958e3 100644
--- a/erpnext/config/crm.py
+++ b/erpnext/config/crm.py
@@ -92,27 +92,27 @@
{
"type": "page",
"label": _("Customer Group"),
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
- "link": "Sales Browser/Customer Group",
+ "link": "Tree/Customer Group",
"description": _("Manage Customer Group Tree."),
"doctype": "Customer Group",
},
{
"type": "page",
"label": _("Territory"),
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
- "link": "Sales Browser/Territory",
+ "link": "Tree/Territory",
"description": _("Manage Territory Tree."),
"doctype": "Territory",
},
{
"type": "page",
"label": _("Sales Person"),
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
- "link": "Sales Browser/Sales Person",
+ "link": "Tree/Sales Person",
"description": _("Manage Sales Person Tree."),
"doctype": "Sales Person",
},
diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py
index 771a0d7..1a725e2 100644
--- a/erpnext/config/selling.py
+++ b/erpnext/config/selling.py
@@ -30,9 +30,9 @@
{
"type": "page",
"label": _("Customer Group"),
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
- "link": "Sales Browser/Customer Group",
+ "link": "Tree/Customer Group",
"description": _("Manage Customer Group Tree."),
"doctype": "Customer Group",
},
@@ -69,10 +69,10 @@
},
{
"type": "page",
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
"label": _("Item Group"),
- "link": "Sales Browser/Item Group",
+ "link": "Tree/Item Group",
"description": _("Tree of Item Groups."),
"doctype": "Item Group",
},
@@ -101,9 +101,9 @@
{
"type": "page",
"label": _("Territory"),
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
- "link": "Sales Browser/Territory",
+ "link": "Tree/Territory",
"description": _("Manage Territory Tree."),
"doctype": "Territory",
},
@@ -115,9 +115,9 @@
{
"type": "page",
"label": _("Sales Person"),
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
- "link": "Sales Browser/Sales Person",
+ "link": "Tree/Sales Person",
"description": _("Manage Sales Person Tree."),
"doctype": "Sales Person",
},
diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py
index cf3a7ba..320d906 100644
--- a/erpnext/config/stock.py
+++ b/erpnext/config/stock.py
@@ -78,10 +78,10 @@
},
{
"type": "page",
- "name": "Sales Browser",
+ "name": "Tree",
"icon": "icon-sitemap",
"label": _("Item Group"),
- "link": "Sales Browser/Item Group",
+ "link": "Tree/Item Group",
"description": _("Tree of Item Groups."),
"doctype": "Item Group",
},
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 1976cc6..1e685e5 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -321,6 +321,7 @@
warehouse_account = frappe._dict()
for d in frappe.db.sql("""select warehouse, name, account_currency from tabAccount
- where account_type = 'Warehouse' and (warehouse is not null and warehouse != '')""", as_dict=1):
+ where account_type = 'Warehouse' and (warehouse is not null and warehouse != ''
+ and is_group != 1)""", as_dict=1):
warehouse_account.setdefault(d.warehouse, d)
return warehouse_account
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 6f45ff2..de8ac94 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -24,7 +24,7 @@
frm.events.update_cost(frm);
});
frm.add_custom_button(__("Browse BOM"), function() {
- frappe.set_route("bom-browser", frm.doc.name);
+ frappe.set_route("Tree", "BOM");
});
}
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 6730141..287ee9b 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -438,3 +438,14 @@
if item and not (bom.item.lower() == item.lower() or \
bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()):
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
+
+@frappe.whitelist()
+def get_children(parent=None):
+ if parent:
+ return frappe.db.sql("""select item_code,
+ bom_no as value, qty,
+ if(ifnull(bom_no, "")!="", 1, 0) as expandable
+ from `tabBOM Item`
+ where parent=%s
+ order by idx
+ """, parent, as_dict=True)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js
new file mode 100644
index 0000000..0404360
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom/bom_tree.js
@@ -0,0 +1,35 @@
+frappe.treeview_settings["BOM"] = {
+ get_tree_nodes: 'erpnext.manufacturing.doctype.bom.bom.get_children',
+ filters: [
+ {
+ fieldname: "bom",
+ fieldtype:"Link",
+ options: "BOM",
+ label: __("BOM")
+ }
+ ],
+ title: "BOM",
+ breadcrumb: "Manufacturing",
+ disable_add_node: true,
+ root_label: "bom", //fieldname from filters
+ get_label: function(node) {
+ if(node.data.qty) {
+ return node.data.qty + " x " + node.data.item_code;
+ } else {
+ return node.data.item_code || node.data.value;
+ }
+ },
+ toolbar: [
+ {toggle_btn: true},
+ {
+ label:__("Edit"),
+ condition: function(node) {
+ return node.expandable;
+ },
+ click: function(node) {
+
+ frappe.set_route("Form", "BOM", node.data.value);
+ }
+ }
+ ],
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/page/bom_browser/__init__.py b/erpnext/manufacturing/page/bom_browser/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/manufacturing/page/bom_browser/__init__.py
+++ /dev/null
diff --git a/erpnext/manufacturing/page/bom_browser/bom_browser.js b/erpnext/manufacturing/page/bom_browser/bom_browser.js
deleted file mode 100644
index 3c13905..0000000
--- a/erpnext/manufacturing/page/bom_browser/bom_browser.js
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.pages['bom-browser'].on_page_load = function(wrapper) {
- var page = frappe.ui.make_app_page({
- parent: wrapper,
- title: 'BOM Browser',
- single_column: true
- });
-
- page.main.css({
- "min-height": "300px",
- "padding-bottom": "25px"
- });
-
- page.tree_area = $('<div class="padding"><p class="text-muted">'+
- __("Select BOM to start")
- +'</p></div>').appendTo(page.main);
-
- frappe.breadcrumbs.add(frappe.breadcrumbs.last_module || "Manufacturing");
-
- var make_tree = function() {
- erpnext.bom_tree = new erpnext.BOMTree(page.$bom_select.val(), page, page.tree_area);
- }
-
- page.$bom_select = wrapper.page.add_field({fieldname: "bom",
- fieldtype:"Link", options: "BOM", label: __("BOM")}).$input
- .change(function() {
- make_tree();
- });
-
- page.set_secondary_action(__('Refresh'), function() {
- make_tree();
- });
-}
-
-
-frappe.pages['bom-browser'].on_page_show = function(wrapper){
- // set from route
- var bom = null;
- if(frappe.get_route()[1]) {
- var bom = frappe.get_route().splice(1).join("/");
- }
- if(frappe.route_options && frappe.route_options.bom) {
- var bom = frappe.route_options.bom;
- }
- if(bom) {
- wrapper.page.$bom_select.val(bom).trigger("change");
- }
-};
-
-erpnext.BOMTree = Class.extend({
- init: function(root, page, parent) {
- $(parent).empty();
- var me = this;
- me.page = page;
- me.bom = page.$bom_select.val();
- me.can_read = frappe.model.can_read("BOM");
- me.can_create = frappe.boot.user.can_create.indexOf("BOM") !== -1 ||
- frappe.boot.user.in_create.indexOf("BOM") !== -1;
- me.can_write = frappe.model.can_write("BOM");
- me.can_delete = frappe.model.can_delete("BOM");
- this.tree = new frappe.ui.Tree({
- parent: $(parent),
- label: me.bom,
- args: {parent: me.bom},
- method: 'erpnext.manufacturing.page.bom_browser.bom_browser.get_children',
- toolbar: [
- {toggle_btn: true},
- {
- label:__("Edit"),
- condition: function(node) {
- return node.expandable;
- },
- click: function(node) {
- frappe.set_route("Form", "BOM", node.data.value);
- }
- }
- ],
- get_label: function(node) {
- if(node.data.qty) {
- return node.data.qty + " x " + node.data.item_code;
- } else {
- return node.data.item_code || node.data.value;
- }
- }
- });
- }
-});
diff --git a/erpnext/manufacturing/page/bom_browser/bom_browser.json b/erpnext/manufacturing/page/bom_browser/bom_browser.json
deleted file mode 100644
index 5b75463..0000000
--- a/erpnext/manufacturing/page/bom_browser/bom_browser.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "content": null,
- "creation": "2015-05-25 02:57:33.472044",
- "docstatus": 0,
- "doctype": "Page",
- "modified": "2015-05-25 02:57:33.472044",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "bom-browser",
- "owner": "Administrator",
- "page_name": "bom-browser",
- "roles": [
- {
- "role": "Manufacturing User"
- }
- ],
- "script": null,
- "standard": "Yes",
- "style": null,
- "title": "BOM Browser"
-}
\ No newline at end of file
diff --git a/erpnext/manufacturing/page/bom_browser/bom_browser.py b/erpnext/manufacturing/page/bom_browser/bom_browser.py
deleted file mode 100644
index 8099389..0000000
--- a/erpnext/manufacturing/page/bom_browser/bom_browser.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-@frappe.whitelist()
-def get_children(parent):
- return frappe.db.sql("""select item_code,
- bom_no as value, qty,
- if(ifnull(bom_no, "")!="", 1, 0) as expandable
- from `tabBOM Item`
- where parent=%s
- order by idx
- """, parent, as_dict=True)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2493643..eda2c70 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -273,3 +273,4 @@
erpnext.patches.v7_0.update_mins_to_first_response
erpnext.patches.v6_20x.repost_valuation_rate_for_negative_inventory
erpnext.patches.v7_0.re_route
+erpnext.patches.v7_0.create_warehouse_nestedset
diff --git a/erpnext/patches/v7_0/create_warehouse_nestedset.py b/erpnext/patches/v7_0/create_warehouse_nestedset.py
new file mode 100644
index 0000000..80dbf2e
--- /dev/null
+++ b/erpnext/patches/v7_0/create_warehouse_nestedset.py
@@ -0,0 +1,48 @@
+import frappe
+from frappe import _
+
+def execute():
+ frappe.reload_doc("stock", "doctype", "warehouse")
+
+ for company in frappe.get_all("Company", fields=["name", "abbr"]):
+ if not frappe.db.get_value("Warehouse", "{0} - {1}".format(_("All Warehouses"), company.abbr)):
+ create_default_warehouse_group(company)
+
+ for warehouse in frappe.get_all("Warehouse", filters={"company": company.name}, fields=["name", "create_account_under",
+ "parent_warehouse", "is_group"]):
+ set_parent_to_warehouses(warehouse, company)
+ set_parent_to_warehouse_acounts(warehouse, company)
+
+def set_parent_to_warehouses(warehouse, company):
+ warehouse = frappe.get_doc("Warehouse", warehouse.name)
+ warehouse.is_group = "Yes" if warehouse.is_group == "Yes" else "No"
+
+ if not warehouse.parent_warehouse and warehouse.name != "{0} - {1}".format(_("All Warehouses"), company.abbr):
+ warehouse.parent_warehouse = "{0} - {1}".format(_("All Warehouses"), company.abbr)
+
+ warehouse.save(ignore_permissions=True)
+
+def set_parent_to_warehouse_acounts(warehouse, company):
+ account = frappe.db.get_value("Account", {"warehouse": warehouse.name})
+ stock_group = frappe.db.get_value("Account", {"account_type": "Stock",
+ "is_group": 1, "company": company.name})
+
+ if account and account != "{0} - {1}".format(_("All Warehouses"), company.abbr):
+ account = frappe.get_doc("Account", account)
+
+ if warehouse.create_account_under == stock_group or not warehouse.create_account_under:
+ if not warehouse.parent_warehouse:
+ account.parent_account = "{0} - {1}".format(_("All Warehouses"), company.abbr)
+ else:
+ account.parent_account = frappe.db.get_value("Account", warehouse.parent_warehouse)
+
+ account.save(ignore_permissions=True)
+
+def create_default_warehouse_group(company):
+ frappe.get_doc({
+ "doctype": "Warehouse",
+ "warehouse_name": _("All Warehouses"),
+ "is_group": "Yes",
+ "company": company.name,
+ "parent_warehouse": ""
+ }).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js
index d8133ce..233bd2e 100644
--- a/erpnext/public/js/conf.js
+++ b/erpnext/public/js/conf.js
@@ -20,12 +20,12 @@
// doctypes created via tree
$.extend(frappe.create_routes, {
- "Customer Group": "Sales Browser/Customer Group",
- "Territory": "Sales Browser/Territory",
- "Item Group": "Sales Browser/Item Group",
- "Sales Person": "Sales Browser/Sales Person",
- "Account": "Accounts Browser/Account",
- "Cost Center": "Accounts Browser/Cost Center"
+ "Customer Group": "Tree/Customer Group",
+ "Territory": "Tree/Territory",
+ "Item Group": "Tree/Item Group",
+ "Sales Person": "Tree/Sales Person",
+ "Account": "Tree/Account",
+ "Cost Center": "Tree/Cost Center"
});
// preferred modules for breadcrumbs
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index 12307fb..acc0409 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -71,7 +71,11 @@
warehouse: function(doc) {
return {
- filters: [["Warehouse", "company", "in", ["", cstr(doc.company)]]]
+ filters: [
+ ["Warehouse", "company", "in", ["", cstr(doc.company)]],
+ ["Warehouse", "is_group", "=", "No"]
+
+ ]
}
}
});
diff --git a/erpnext/selling/page/sales_browser/README.md b/erpnext/selling/page/sales_browser/README.md
deleted file mode 100644
index d6a20e1..0000000
--- a/erpnext/selling/page/sales_browser/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Tree editor for Territory, Customer Group, Item Group, Sales Partner
\ No newline at end of file
diff --git a/erpnext/selling/page/sales_browser/__init__.py b/erpnext/selling/page/sales_browser/__init__.py
deleted file mode 100644
index baffc48..0000000
--- a/erpnext/selling/page/sales_browser/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from __future__ import unicode_literals
diff --git a/erpnext/selling/page/sales_browser/sales_browser.js b/erpnext/selling/page/sales_browser/sales_browser.js
deleted file mode 100644
index a99fe72..0000000
--- a/erpnext/selling/page/sales_browser/sales_browser.js
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.pages["Sales Browser"].on_page_load = function(wrapper){
- var page = frappe.ui.make_app_page({
- parent: wrapper,
- single_column: true,
- });
-
- wrapper.page.add_menu_item(__('Refresh'), function() {
- wrapper.make_tree();
- });
-
- wrapper.make_tree = function() {
- var ctype = frappe.get_route()[1] || 'Territory';
- return frappe.call({
- method: 'erpnext.selling.page.sales_browser.sales_browser.get_children',
- args: {ctype: ctype},
- callback: function(r) {
- var root = r.message[0]["value"];
- erpnext.sales_chart = new erpnext.SalesChart(ctype, root, page,
- page.main.css({
- "min-height": "300px",
- "padding-bottom": "25px"
- }));
- }
- });
- }
-
- wrapper.make_tree();
-}
-
-frappe.pages['Sales Browser'].on_page_show = function(wrapper){
- // set route
- var ctype = frappe.get_route()[1] || 'Territory';
-
- wrapper.page.set_title(__('{0} Tree',[__(ctype)]));
-
- if(erpnext.sales_chart && erpnext.sales_chart.ctype != ctype) {
- wrapper.make_tree();
- }
-
- frappe.breadcrumbs.add(frappe.breadcrumbs.last_module || "Selling");
-};
-
-erpnext.SalesChart = Class.extend({
- init: function(ctype, root, page, parent) {
- $(parent).empty();
- var me = this;
- me.ctype = ctype;
- me.page = page;
- me.can_read = frappe.model.can_read(this.ctype);
- me.can_create = frappe.boot.user.can_create.indexOf(this.ctype) !== -1 ||
- frappe.boot.user.in_create.indexOf(this.ctype) !== -1;
- me.can_write = frappe.model.can_write(this.ctype);
- me.can_delete = frappe.model.can_delete(this.ctype);
-
- me.page.set_primary_action(__("New"), function() {
- me.new_node();
- }, "octicon octicon-plus");
-
- this.tree = new frappe.ui.Tree({
- parent: $(parent),
- label: __(root),
- args: {ctype: ctype},
- method: 'erpnext.selling.page.sales_browser.sales_browser.get_children',
- toolbar: [
- {toggle_btn: true},
- {
- label:__("Edit"),
- condition: function(node) {
- return !node.root && me.can_read;
- },
- click: function(node) {
- frappe.set_route("Form", me.ctype, node.label);
- }
- },
- {
- label:__("Add Child"),
- condition: function(node) { return me.can_create && node.expandable; },
- click: function(node) {
- me.new_node();
- },
- btnClass: "hidden-xs"
- },
- {
- label:__("Rename"),
- condition: function(node) { return !node.root && me.can_write; },
- click: function(node) {
- frappe.model.rename_doc(me.ctype, node.label, function(new_name) {
- node.$a.html(new_name);
- });
- },
- btnClass: "hidden-xs"
- },
- {
- label:__("Delete"),
- condition: function(node) { return !node.root && me.can_delete; },
- click: function(node) {
- frappe.model.delete_doc(me.ctype, node.label, function() {
- node.parent.remove();
- });
- },
- btnClass: "hidden-xs"
- }
-
- ]
- });
- },
- new_node: function() {
- var me = this;
- var node = me.tree.get_selected_node();
-
- if(!(node && node.expandable)) {
- frappe.msgprint(__("Select a group node first."));
- return;
- }
-
- var fields = [
- {fieldtype:'Data', fieldname: 'name_field',
- label:__('New {0} Name',[__(me.ctype)]), reqd:true},
- {fieldtype:'Select', fieldname:'is_group', label:__('Group Node'), options:'No\nYes',
- description: __("Further nodes can be only created under 'Group' type nodes")}
- ]
-
- if(me.ctype == "Sales Person") {
- fields.splice(-1, 0, {fieldtype:'Link', fieldname:'employee', label:__('Employee'),
- options:'Employee', description: __("Please enter Employee Id of this sales person")});
- }
-
- // the dialog
- var d = new frappe.ui.Dialog({
- title: __('New {0}',[__(me.ctype)]),
- fields: fields
- })
-
- d.set_value("is_group", "No");
- // create
- d.set_primary_action(__("Create New"), function() {
- var btn = this;
- var v = d.get_values();
- if(!v) return;
-
- var node = me.tree.get_selected_node();
-
- v.parent = node.label;
- v.ctype = me.ctype;
-
- return frappe.call({
- method: 'erpnext.selling.page.sales_browser.sales_browser.add_node',
- args: v,
- callback: function(r) {
- if(!r.exc) {
- d.hide();
- if(node.expanded) {
- node.toggle_node();
- }
- node.reload();
- }
- }
- });
- });
-
- d.show();
- },
-});
diff --git a/erpnext/selling/page/sales_browser/sales_browser.json b/erpnext/selling/page/sales_browser/sales_browser.json
deleted file mode 100644
index 54cac65..0000000
--- a/erpnext/selling/page/sales_browser/sales_browser.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "creation": "2012-06-14 15:07:26.000000",
- "docstatus": 0,
- "doctype": "Page",
- "icon": "icon-sitemap",
- "idx": 1,
- "modified": "2013-07-11 14:43:56.000000",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "sales-browser",
- "owner": "Administrator",
- "page_name": "Sales Browser",
- "roles": [
- {
- "role": "Sales Master Manager"
- },
- {
- "role": "Material Master Manager"
- },
- {
- "role": "Accounts Manager"
- },
- {
- "role": "Sales Master Manager"
- },
- {
- "role": "Purchase Manager"
- },
- {
- "role": "Purchase Master Manager"
- },
- {
- "role": "Material Manager"
- }
- ],
- "standard": "Yes"
-}
diff --git a/erpnext/selling/page/sales_browser/sales_browser.py b/erpnext/selling/page/sales_browser/sales_browser.py
deleted file mode 100644
index 018ba3b..0000000
--- a/erpnext/selling/page/sales_browser/sales_browser.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-
-@frappe.whitelist()
-def get_children():
- ctype = frappe.local.form_dict.get('ctype')
- parent_field = 'parent_' + ctype.lower().replace(' ', '_')
- parent = frappe.form_dict.get("parent") or ""
-
- return frappe.db.sql("""select name as value,
- if(is_group='Yes', 1, 0) as expandable
- from `tab{ctype}`
- where docstatus < 2
- and ifnull(`{parent_field}`,'') = %s
- order by name""".format(ctype=frappe.db.escape(ctype), parent_field=frappe.db.escape(parent_field)),
- parent, as_dict=1)
-
-@frappe.whitelist()
-def add_node():
- ctype = frappe.form_dict.get('ctype')
- parent_field = 'parent_' + ctype.lower().replace(' ', '_')
- name_field = ctype.lower().replace(' ', '_') + '_name'
-
- doc = frappe.new_doc(ctype)
- doc.update({
- name_field: frappe.form_dict['name_field'],
- parent_field: frappe.form_dict['parent'],
- "is_group": frappe.form_dict['is_group']
- })
- if ctype == "Sales Person":
- doc.employee = frappe.form_dict.get('employee')
-
- doc.save()
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index dfa6c0a..2a18286 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -18,11 +18,11 @@
!frm.doc.__onload.transactions_exist));
frm.add_custom_button(__('Cost Centers'), function() {
- frappe.set_route('Accounts Browser', 'Cost Center', {'company': frm.doc.name})
+ frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name})
})
frm.add_custom_button(__('Chart of Accounts'), function() {
- frappe.set_route('Accounts Browser', 'Account', {'company': frm.doc.name})
+ frappe.set_route('Tree', 'Account', {'company': frm.doc.name})
})
}
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 7da7c25..7d88297 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -87,15 +87,23 @@
.format(self.country.lower()))(self)
def create_default_warehouses(self):
- for whname in (_("Stores"), _("Work In Progress"), _("Finished Goods")):
- if not frappe.db.exists("Warehouse", whname + " - " + self.abbr):
+ for wh_detail in [
+ {"warehouse_name": _("All Warehouses"), "is_group": "Yes"},
+ {"warehouse_name": _("Stores"), "is_group": "No"},
+ {"warehouse_name": _("Work In Progress"), "is_group": "No"},
+ {"warehouse_name": _("Finished Goods"), "is_group": "No"}]:
+
+ if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)):
stock_group = frappe.db.get_value("Account", {"account_type": "Stock",
"is_group": 1, "company": self.name})
if stock_group:
warehouse = frappe.get_doc({
"doctype":"Warehouse",
- "warehouse_name": whname,
+ "warehouse_name": wh_detail["warehouse_name"],
+ "is_group": wh_detail["is_group"],
"company": self.name,
+ "parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \
+ if wh_detail["is_group"] == "No" else "",
"create_account_under": stock_group
})
warehouse.flags.ignore_permissions = True
diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js
index 7a5d2fa..3dc5c6c 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.js
+++ b/erpnext/setup/doctype/customer_group/customer_group.js
@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.list_route = "Sales Browser/Customer Group";
+cur_frm.list_route = "Tree/Customer Group";
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js
index d440c26..d6adebc 100644
--- a/erpnext/setup/doctype/item_group/item_group.js
+++ b/erpnext/setup/doctype/item_group/item_group.js
@@ -3,7 +3,7 @@
frappe.ui.form.on("Item Group", {
onload: function(frm) {
- frm.list_route = "Sales Browser/Item Group";
+ frm.list_route = "Tree/Item Group";
//get query select item group
frm.fields_dict['parent_item_group'].get_query = function(doc,cdt,cdn) {
@@ -19,7 +19,7 @@
refresh: function(frm) {
frm.trigger("set_root_readonly");
frm.add_custom_button(__("Item Group Tree"), function() {
- frappe.set_route("Sales Browser", "Item Group");
+ frappe.set_route("Tree", "Item Group");
}, "icon-sitemap");
},
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index 8bae546..1368392 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.list_route = "Sales Browser/Sales Person";
+cur_frm.list_route = "Tree/Sales Person";
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
diff --git a/erpnext/setup/doctype/sales_person/sales_person_tree.js b/erpnext/setup/doctype/sales_person/sales_person_tree.js
new file mode 100644
index 0000000..fd2127d
--- /dev/null
+++ b/erpnext/setup/doctype/sales_person/sales_person_tree.js
@@ -0,0 +1,11 @@
+frappe.treeview_settings["Sales Person"] = {
+ fields: [
+ {fieldtype:'Data', fieldname: 'name_field',
+ label:__('New Sales Person Name'), reqd:true},
+ {fieldtype:'Link', fieldname:'employee',
+ label:__('Employee'), options:'Employee',
+ description: __("Please enter Employee Id of this sales person")},
+ {fieldtype:'Select', fieldname:'is_group', label:__('Group Node'), options:'No\nYes',
+ description: __("Further nodes can be only created under 'Group' type nodes")}
+ ],
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/territory/territory.js b/erpnext/setup/doctype/territory/territory.js
index cde04b3..bf4e29b 100644
--- a/erpnext/setup/doctype/territory/territory.js
+++ b/erpnext/setup/doctype/territory/territory.js
@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.list_route = "Sales Browser/Territory";
+cur_frm.list_route = "Tree/Territory";
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index 7bdcb0a..97ef329 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -52,26 +52,26 @@
bootinfo.page_info.update({
"Chart of Accounts": {
"title": "Chart of Accounts",
- "route": "Accounts Browser/Account"
+ "route": "Tree/Account"
},
"Chart of Cost Centers": {
"title": "Chart of Cost Centers",
- "route": "Accounts Browser/Cost Center"
+ "route": "Tree/Cost Center"
},
"Item Group Tree": {
"title": "Item Group Tree",
- "route": "Sales Browser/Item Group"
+ "route": "Tree/Item Group"
},
"Customer Group Tree": {
"title": "Customer Group Tree",
- "route": "Sales Browser/Customer Group"
+ "route": "Tree/Customer Group"
},
"Territory Tree": {
"title": "Territory Tree",
- "route": "Sales Browser/Territory"
+ "route": "Tree/Territory"
},
"Sales Person Tree": {
"title": "Sales Person Tree",
- "route": "Sales Browser/Sales Person"
+ "route": "Tree/Sales Person"
}
})
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index a1580d5..2378e3f 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.utils import flt, nowdate
import frappe.defaults
from frappe.model.document import Document
@@ -15,13 +16,19 @@
self.validate_mandatory()
self.projected_qty = flt(self.actual_qty) + flt(self.ordered_qty) + \
- 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.block_transactions_against_group_warehouse()
def validate_mandatory(self):
qf = ['actual_qty', 'reserved_qty', 'ordered_qty', 'indented_qty']
for f in qf:
if (not getattr(self, f, None)) or (not self.get(f)):
self.set(f, 0.0)
+
+ def block_transactions_against_group_warehouse(self):
+ from erpnext.stock.utils import is_group_warehouse
+ is_group_warehouse(self.warehouse)
def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
self.update_qty(args)
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index fe7e54e..f35fa58 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -160,6 +160,28 @@
frm.fields_dict.supplier_items.grid.get_field("supplier").get_query = function(doc, cdt, cdn) {
return { query: "erpnext.controllers.queries.supplier_query" }
}
+
+ frm.fields_dict['default_warehouse'].get_query = function(doc) {
+ return {
+ filters: { "is_group": "No" }
+ }
+ }
+
+ frm.fields_dict.reorder_levels.grid.get_field("warehouse_group").get_query = function(doc, cdt, cdn) {
+ return {
+ filters: { "is_group": "Yes" }
+ }
+ }
+
+ frm.fields_dict.reorder_levels.grid.get_field("warehouse").get_query = function(doc, cdt, cdn) {
+ var d = locals[cdt][cdn];
+ return {
+ filters: {
+ "is_group": "No",
+ "parent_warehouse": d.warehouse_group
+ }
+ }
+ }
},
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index fcf2d0b..c05c5f3 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -227,6 +227,35 @@
"warehouse_reorder_qty": 20
}
]
+ },
+ {
+ "default_warehouse": "_Test Warehouse Group-C1 - _TC",
+ "description": "_Test Item 1",
+ "doctype": "Item",
+ "expense_account": "_Test Account Cost for Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "has_batch_no": 0,
+ "has_serial_no": 0,
+ "income_account": "Sales - _TC",
+ "inspection_required": 0,
+ "is_stock_item": 1,
+ "is_sub_contracted_item": 0,
+ "item_code": "_Test Item Warehouse Group Wise Reorder",
+ "item_group": "_Test Item Group",
+ "item_name": "_Test Item Warehouse Group Wise Reorder",
+ "apply_warehouse_wise_reorder_level": 1,
+ "reorder_levels": [
+ {
+ "warehouse_group": "_Test Warehouse Group - _TC",
+ "material_request_type": "Purchase",
+ "warehouse": "_Test Warehouse Group-C1 - _TC",
+ "warehouse_reorder_level": 20,
+ "warehouse_reorder_qty": 20
+ }
+ ],
+ "stock_uom": "_Test UOM",
+ "show_in_website": 1,
+ "website_warehouse": "_Test Warehouse Group-C1 - _TC"
}
]
diff --git a/erpnext/stock/doctype/item_reorder/item_reorder.json b/erpnext/stock/doctype/item_reorder/item_reorder.json
index fea8bf0..27a311d 100644
--- a/erpnext/stock/doctype/item_reorder/item_reorder.json
+++ b/erpnext/stock/doctype/item_reorder/item_reorder.json
@@ -3,19 +3,48 @@
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
+ "beta": 0,
"creation": "2013-03-07 11:42:59",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
+ "document_type": "Setup",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "warehouse_group",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Warehouse Group",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Warehouse",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "warehouse",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Warehouse",
@@ -24,6 +53,7 @@
"options": "Warehouse",
"permlevel": 0,
"print_hide": 0,
+ "print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
@@ -39,6 +69,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Re-order Level",
@@ -46,6 +77,7 @@
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
+ "print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
@@ -61,6 +93,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Re-order Qty",
@@ -68,6 +101,7 @@
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
+ "print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
@@ -83,6 +117,7 @@
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Material Request Type",
@@ -91,6 +126,7 @@
"options": "Purchase\nTransfer",
"permlevel": 0,
"print_hide": 0,
+ "print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
@@ -102,18 +138,21 @@
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
+ "image_view": 0,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2015-11-16 06:29:48.492627",
+ "modified": "2016-06-20 15:52:01.978593",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Reorder",
"owner": "Administrator",
"permissions": [],
+ "quick_entry": 0,
"read_only": 0,
- "read_only_onload": 0
+ "read_only_onload": 0,
+ "track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 0c33ff7..24d0546 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -79,8 +79,11 @@
def test_auto_material_request_for_variant(self):
self._test_auto_material_request("_Test Variant Item-S")
-
- def _test_auto_material_request(self, item_code, material_request_type="Purchase"):
+
+ def test_auto_material_request_for_warehouse_group(self):
+ self._test_auto_material_request("_Test Item Warehouse Group Wise Reorder", warehouse="_Test Warehouse Group-C1 - _TC")
+
+ def _test_auto_material_request(self, item_code, material_request_type="Purchase", warehouse="_Test Warehouse - _TC"):
item = frappe.get_doc("Item", item_code)
if item.variant_of:
@@ -89,14 +92,14 @@
template = item
projected_qty, actual_qty = frappe.db.get_value("Bin", {"item_code": item_code,
- "warehouse": "_Test Warehouse - _TC"}, ["projected_qty", "actual_qty"]) or [0, 0]
+ "warehouse": warehouse}, ["projected_qty", "actual_qty"]) or [0, 0]
# stock entry reqd for auto-reorder
- create_stock_reconciliation(item_code=item_code, warehouse="_Test Warehouse - _TC",
+ create_stock_reconciliation(item_code=item_code, warehouse=warehouse,
qty = actual_qty + abs(projected_qty) + 10, rate=100)
projected_qty = frappe.db.get_value("Bin", {"item_code": item_code,
- "warehouse": "_Test Warehouse - _TC"}, "projected_qty") or 0
+ "warehouse": warehouse}, "projected_qty") or 0
frappe.db.set_value("Stock Settings", None, "auto_indent", 1)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 9931ffa..2caabee 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -25,6 +25,7 @@
validate_warehouse_company(self.warehouse, self.company)
self.scrub_posting_time()
self.validate_and_set_fiscal_year()
+ self.block_transactions_against_group_warehouse()
from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.posting_date, self.fiscal_year, self.meta.get_label("posting_date"), self)
@@ -117,6 +118,9 @@
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
+ def block_transactions_against_group_warehouse(self):
+ from erpnext.stock.utils import is_group_warehouse
+ is_group_warehouse(self.warehouse)
def on_doctype_update():
if not frappe.db.sql("""show index from `tabStock Ledger Entry`
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index f0eea5a..9cc27b7 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -116,10 +116,11 @@
def set_valuation_method(item_code, valuation_method):
frappe.db.set_value("Item", item_code, "valuation_method", valuation_method)
- for warehouse in frappe.get_all("Warehouse", filters={"company": "_Test Company"}):
- update_entries_after({
- "item_code": item_code,
- "warehouse": warehouse.name
- }, allow_negative_stock=1)
+ for warehouse in frappe.get_all("Warehouse", filters={"company": "_Test Company"}, fields=["name", "is_group"]):
+ if warehouse.is_group == "No":
+ update_entries_after({
+ "item_code": item_code,
+ "warehouse": warehouse.name
+ }, allow_negative_stock=1)
test_dependencies = ["Item", "Warehouse"]
diff --git a/erpnext/stock/doctype/warehouse/test_records.json b/erpnext/stock/doctype/warehouse/test_records.json
index e2162d2..c1dad5c 100644
--- a/erpnext/stock/doctype/warehouse/test_records.json
+++ b/erpnext/stock/doctype/warehouse/test_records.json
@@ -3,35 +3,68 @@
"company": "_Test Company",
"create_account_under": "Stock Assets - _TC",
"doctype": "Warehouse",
- "warehouse_name": "_Test Warehouse"
- },
- {
- "company": "_Test Company",
- "create_account_under": "Fixed Assets - _TC",
- "doctype": "Warehouse",
- "warehouse_name": "_Test Warehouse 1"
- },
- {
- "company": "_Test Company",
- "create_account_under": "Fixed Assets - _TC",
- "doctype": "Warehouse",
- "warehouse_name": "_Test Warehouse 2"
+ "warehouse_name": "_Test Warehouse",
+ "is_group": "No"
},
{
"company": "_Test Company",
"create_account_under": "Stock Assets - _TC",
"doctype": "Warehouse",
- "warehouse_name": "_Test Rejected Warehouse"
+ "warehouse_name": "_Test Warehouse",
+ "is_group": "No"
+ },
+ {
+ "company": "_Test Company",
+ "create_account_under": "Fixed Assets - _TC",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Warehouse 1",
+ "is_group": "No"
+ },
+ {
+ "company": "_Test Company",
+ "create_account_under": "Fixed Assets - _TC",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Warehouse 2",
+ "is_group": "No"
+ },
+ {
+ "company": "_Test Company",
+ "create_account_under": "Stock Assets - _TC",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Rejected Warehouse",
+ "is_group": "No"
},
{
"company": "_Test Company 1",
"create_account_under": "Stock Assets - _TC1",
"doctype": "Warehouse",
- "warehouse_name": "_Test Warehouse 2"
+ "warehouse_name": "_Test Warehouse 2",
+ "is_group": "No"
},
{
"company": "_Test Company",
"doctype": "Warehouse",
- "warehouse_name": "_Test Warehouse No Account"
+ "warehouse_name": "_Test Warehouse No Account",
+ "is_group": "No"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Warehouse Group",
+ "is_group": "Yes"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Warehouse Group-C1",
+ "is_group": "No",
+ "parent_warehouse": "_Test Warehouse Group - _TC"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Warehouse Group-C2",
+ "is_group": "No",
+ "parent_warehouse": "_Test Warehouse Group - _TC"
}
]
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index ca80ca7..b6eaa13 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -4,4 +4,22 @@
import frappe
-test_records = frappe.get_test_records('Warehouse')
\ No newline at end of file
+import unittest
+test_records = frappe.get_test_records('Warehouse')
+
+class TestWarehouse(unittest.TestCase):
+ def test_parent_warehouse(self):
+ parent_warehouse = frappe.get_doc("Warehouse", "_Test Warehouse Group - _TC")
+ self.assertEquals(parent_warehouse.is_group, "Yes")
+
+ def test_warehouse_hierarchy(self):
+ p_warehouse = frappe.get_doc("Warehouse", "_Test Warehouse Group - _TC")
+
+ child_warehouses = frappe.db.sql("""select name, is_group, parent_warehouse from `tabWarehouse` wh
+ where wh.lft > %s and wh.rgt < %s""", (p_warehouse.lft, p_warehouse.rgt), as_dict=1)
+
+ for child_warehouse in child_warehouses:
+ self.assertEquals(p_warehouse.name, child_warehouse.parent_warehouse)
+ self.assertEquals(child_warehouse.is_group, "No")
+
+
diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js
index f1f0b66..4a84ead 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.js
+++ b/erpnext/stock/doctype/warehouse/warehouse.js
@@ -1,6 +1,8 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
+cur_frm.list_route = "Tree/Warehouse";
+
frappe.ui.form.on("Warehouse", {
refresh: function(frm) {
frm.toggle_display('warehouse_name', frm.doc.__islocal);
@@ -17,10 +19,20 @@
frappe.set_route("query-report", "General Ledger");
});
}
+
+ frm.fields_dict['parent_warehouse'].get_query = function(doc) {
+ return {
+ filters: {
+ "is_group": "Yes",
+ }
+ }
+ }
}
});
+
+
cur_frm.set_query("create_account_under", function() {
return {
filters: {
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 4e9dd07..6b60c63 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -2,6 +2,7 @@
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 1,
+ "beta": 0,
"creation": "2013-03-07 18:50:32",
"custom": 0,
"description": "A logical Warehouse against which stock entries are made.",
@@ -420,6 +421,159 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "tree_details",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Tree Details",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "parent_warehouse",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Parent Warehouse",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Warehouse",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 1,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "is_group",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Has Child Node",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nYes\nNo",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "lft",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "lft",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "rgt",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "rgt",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "old_parent",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Old Parent",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Warehouse",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"hide_heading": 0,
@@ -432,7 +586,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-04-18 05:44:24.837579",
+ "modified": "2016-05-23 21:25:21.396188",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 901b229..f1f1e70 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -5,10 +5,11 @@
import frappe
from frappe.utils import cint, validate_email_add
from frappe import throw, msgprint, _
+from frappe.utils.nestedset import NestedSet
-from frappe.model.document import Document
-
-class Warehouse(Document):
+class Warehouse(NestedSet):
+ nsm_parent_field = 'parent_warehouse'
+
def autoname(self):
suffix = " - " + frappe.db.get_value("Company", self.company, "abbr")
if not self.warehouse_name.endswith(suffix):
@@ -45,6 +46,7 @@
def on_update(self):
self.create_account_head()
+ self.update_nsm_model()
def create_account_head(self):
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
@@ -55,8 +57,9 @@
ac_doc = frappe.get_doc({
"doctype": "Account",
'account_name': self.warehouse_name,
- 'parent_account': self.create_account_under,
- 'is_group':0,
+ 'parent_account': self.parent_warehouse if self.parent_warehouse \
+ else self.create_account_under,
+ 'is_group': 1 if self.is_group=="Yes" else 0 ,
'company':self.company,
"account_type": "Warehouse",
"warehouse": self.name,
@@ -75,13 +78,16 @@
{"account_name": "Stock Assets", "company": self.company})
if parent_account:
+ frappe.db.set_value("Warehouse", self.name, "create_account_under", parent_account)
self.create_account_under = parent_account
else:
frappe.throw(_("Please enter parent account group for warehouse {0}").format(self.name))
elif frappe.db.get_value("Account", self.create_account_under, "company") != self.company:
frappe.throw(_("Warehouse {0}: Parent account {1} does not bolong to the company {2}")
.format(self.name, self.create_account_under, self.company))
-
+
+ def update_nsm_model(self):
+ frappe.utils.nestedset.update_nsm(self)
def on_trash(self):
# delete bin
@@ -101,6 +107,11 @@
if frappe.db.sql("""select name from `tabStock Ledger Entry`
where warehouse = %s""", self.name):
throw(_("Warehouse can not be deleted as stock ledger entry exists for this warehouse."))
+
+ if frappe.db.sql("""select name from `tabWarehouse` where parent_warehouse = %s""", self.name):
+ throw(_("Child warehouse exists for this warehouse. You can not delete this warehouse."))
+
+ self.update_nsm_model()
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
@@ -161,3 +172,51 @@
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
frappe.db.auto_commit_on_many_writes = 0
+
+@frappe.whitelist()
+def get_children():
+ from erpnext.stock.utils import get_stock_value_on
+ doctype = frappe.local.form_dict.get('doctype')
+ company = frappe.local.form_dict.get('company')
+
+ parent_field = 'parent_' + doctype.lower().replace(' ', '_')
+ parent = frappe.form_dict.get("parent") or ""
+
+ if parent == "Warehouses":
+ parent = ""
+
+ warehouses = frappe.db.sql("""select name as value,
+ if(is_group='Yes', 1, 0) as expandable
+ from `tab{doctype}`
+ where docstatus < 2
+ and ifnull(`{parent_field}`,'') = %s and `company` = %s
+ order by name""".format(doctype=frappe.db.escape(doctype), parent_field=frappe.db.escape(parent_field)),
+ (parent, company), as_dict=1)
+
+ # return warehouses
+ for wh in warehouses:
+ wh["balance"] = get_stock_value_on(warehouse=wh.value)
+ return warehouses
+
+@frappe.whitelist()
+def add_node():
+ doctype = frappe.form_dict.get('doctype')
+ company = frappe.form_dict.get('company')
+ parent_field = 'parent_' + doctype.lower().replace(' ', '_')
+ name_field = doctype.lower().replace(' ', '_') + '_name'
+
+ doc = frappe.new_doc(doctype)
+
+ parent = frappe.form_dict['parent']
+
+ if cint(frappe.form_dict['is_root']):
+ parent = None
+
+ doc.update({
+ name_field: frappe.form_dict['name_field'],
+ parent_field: parent,
+ "is_group": frappe.form_dict['is_group'],
+ "company": company
+ })
+
+ doc.save()
diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js
new file mode 100644
index 0000000..0361493
--- /dev/null
+++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js
@@ -0,0 +1,20 @@
+frappe.treeview_settings['Warehouse'] = {
+ get_tree_nodes: "erpnext.stock.doctype.warehouse.warehouse.get_children",
+ add_tree_node: "erpnext.stock.doctype.warehouse.warehouse.add_node",
+ get_tree_root: false,
+ root_label: "Warehouses",
+ filters: [{
+ fieldname: "company",
+ fieldtype:"Select",
+ options: $.map(locals[':Company'], function(c) { return c.name; }).sort(),
+ label: __("Company"),
+ default: frappe.defaults.get_default('company') ? frappe.defaults.get_default('company'): ""
+ }],
+ onrender: function(node) {
+ if (node.data && node.data.balance!==undefined) {
+ $('<span class="balance-area pull-right text-muted small">'
+ + format_currency(Math.abs(node.data.balance), node.data.company_currency)
+ + '</span>').insertBefore(node.$ul);
+ }
+ }
+}
\ No newline at end of file
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 4531913..ad810a9 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -37,7 +37,7 @@
item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
- def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type):
+ def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None):
if warehouse not in warehouse_company:
# a disabled warehouse
return
@@ -46,7 +46,10 @@
reorder_qty = flt(reorder_qty)
# projected_qty will be 0 if Bin does not exist
- projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
+ if warehouse_group:
+ projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
+ else:
+ projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
if (reorder_level or reorder_qty) and projected_qty < reorder_level:
deficiency = reorder_level - projected_qty
@@ -70,7 +73,7 @@
if item.get("reorder_levels"):
for d in item.get("reorder_levels"):
add_to_material_request(item_code, d.warehouse, d.warehouse_reorder_level,
- d.warehouse_reorder_qty, d.material_request_type)
+ d.warehouse_reorder_qty, d.material_request_type, warehouse_group=d.warehouse_group)
if material_requests:
return create_material_request(material_requests)
@@ -82,9 +85,17 @@
from tabBin where item_code in ({0})
and (warehouse != "" and warehouse is not null)"""\
.format(", ".join(["%s"] * len(items_to_consider))), items_to_consider):
-
+
item_warehouse_projected_qty.setdefault(item_code, {})[warehouse] = flt(projected_qty)
-
+
+ warehouse_doc = frappe.get_doc("Warehouse", warehouse)
+
+ if warehouse_doc.parent_warehouse:
+ if not item_warehouse_projected_qty.get(item_code, {}).get(warehouse_doc.parent_warehouse):
+ item_warehouse_projected_qty.setdefault(item_code, {})[warehouse_doc.parent_warehouse] = flt(projected_qty)
+ else:
+ item_warehouse_projected_qty[item_code][warehouse_doc.parent_warehouse] += flt(projected_qty)
+
return item_warehouse_projected_qty
def create_material_request(material_requests):
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 43b6b9a..696f2b0 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -71,7 +71,9 @@
conditions += " and item_code = '%s'" % frappe.db.escape(filters.get("item_code"), percent=False)
if filters.get("warehouse"):
- conditions += " and warehouse = '%s'" % frappe.db.escape(filters.get("warehouse"), percent=False)
+ lft, rgt = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"])
+ conditions += " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"%(lft, rgt)
return conditions
@@ -79,9 +81,9 @@
conditions = get_conditions(filters)
return frappe.db.sql("""select item_code, warehouse, posting_date, actual_qty, valuation_rate,
company, voucher_type, qty_after_transaction, stock_value_difference
- from `tabStock Ledger Entry` force index (posting_sort_index)
+ from `tabStock Ledger Entry` sle force index (posting_sort_index)
where docstatus < 2 %s order by posting_date, posting_time, name""" %
- conditions, as_dict=1)
+ conditions, as_dict=1, debug=1)
def get_item_warehouse_map(filters):
iwb_map = {}
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index ac4cbbd..b2e4670 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -41,7 +41,7 @@
return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date,
item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate,
stock_value, voucher_type, voucher_no, batch_no, serial_no, company
- from `tabStock Ledger Entry`
+ from `tabStock Ledger Entry` sle
where company = %(company)s and
posting_date between %(from_date)s and %(to_date)s
{sle_conditions}
@@ -73,7 +73,7 @@
conditions.append("""item_code in (select name from tabItem
{item_conditions})""".format(item_conditions=item_conditions))
if filters.get("warehouse"):
- conditions.append("warehouse=%(warehouse)s")
+ conditions.append(get_warehouse_condition(filters.get("warehouse")))
if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s")
@@ -86,7 +86,7 @@
from erpnext.stock.stock_ledger import get_previous_sle
last_entry = get_previous_sle({
"item_code": filters.item_code,
- "warehouse": filters.warehouse,
+ "warehouse": get_warehouse_condition(filters.warehouse),
"posting_date": filters.from_date,
"posting_time": "00:00:00"
})
@@ -96,4 +96,11 @@
for i, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
row[i] = last_entry.get(v, 0)
- return row
\ No newline at end of file
+ return row
+
+def get_warehouse_condition(warehouse):
+ lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
+
+ return " exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"%(lft, rgt)
+
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 89963ab..409833a 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -57,16 +57,20 @@
return data
def get_bin_list(filters):
- bin_filters = frappe._dict()
+ conditions = []
+
if filters.item_code:
- bin_filters.item_code = filters.item_code
+ conditions.append("item_code = '%s' "%filters.item_code)
+
if filters.warehouse:
- bin_filters.warehouse = filters.warehouse
+ lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])
+
+ conditions.append(" exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"%(lft, rgt))
- bin_list = frappe.get_all("Bin", fields=["item_code", "warehouse",
- "actual_qty", "planned_qty", "indented_qty", "ordered_qty", "reserved_qty",
- "reserved_qty_for_production", "projected_qty"],
- filters=bin_filters, order_by="item_code, warehouse")
+ bin_list = frappe.db.sql("""select item_code, warehouse, actual_qty, planned_qty, indented_qty,
+ ordered_qty, reserved_qty, reserved_qty_for_production, projected_qty
+ from tabBin where %s order by item_code, warehouse """% " and ".join(conditions), as_dict=1,debug=1)
return bin_list
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 3f9de86..51d82a6 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -15,24 +15,34 @@
values, condition = [posting_date], ""
if warehouse:
- values.append(warehouse)
- condition += " AND warehouse = %s"
+
+ lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"])
+
+ if is_group == "Yes":
+ values.extend([lft, rgt])
+ condition += "and exists (\
+ select name from `tabWarehouse` wh where wh.name = sle.warehouse\
+ and wh.lft >= %s and wh.rgt <= %s)"
+
+ else:
+ values.append(warehouse)
+ condition += " AND warehouse = %s"
if item_code:
values.append(item_code)
condition.append(" AND item_code = %s")
stock_ledger_entries = frappe.db.sql("""
- SELECT item_code, stock_value
- FROM `tabStock Ledger Entry`
+ SELECT item_code, stock_value, name, warehouse
+ FROM `tabStock Ledger Entry` sle
WHERE posting_date <= %s {0}
ORDER BY timestamp(posting_date, posting_time) DESC, name DESC
""".format(condition), values, as_dict=1)
sle_map = {}
for sle in stock_ledger_entries:
- sle_map.setdefault(sle.item_code, flt(sle.stock_value))
-
+ sle_map[sle.item_code] = sle_map.get(sle.item_code, 0.0) + flt(sle.stock_value)
+
return sum(sle_map.values())
def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None, with_valuation_rate=False):
@@ -177,3 +187,8 @@
if warehouse_company and warehouse_company != company:
frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company),
InvalidWarehouseCompany)
+
+def is_group_warehouse(warehouse):
+ if frappe.db.get_value("Warehouse", warehouse, "is_group") == "Yes":
+ frappe.throw(_("Group node warehouse is not allowed to select for transactions"))
+
\ No newline at end of file