diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index 703397e52a..44c2ca45fb 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.account_type = function(doc, cdt, cdn) { 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 718ba31169..918917c56a 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -179,9 +179,12 @@ class Account(Document): 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 0000000000..3252788fa1 --- /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) { + $('' + + (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 + + '').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 85a605229a..a5419016e6 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.cscript.refresh = function(doc, cdt, cdn) { 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 0000000000..ac82f23cb4 --- /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 b8795613fc..0000000000 --- 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 baffc48825..0000000000 --- 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 ec906f8798..0000000000 --- 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 = $("
") - .css({"margin-bottom": "15px", "min-height": "200px"}) - .appendTo(main), - help_area = $('
'+ - '

'+__('Quick Help')+'

'+ - '
    '+ - '
  1. '+__('To add child nodes, explore tree and click on the node under which you want to add more nodes.')+'
  2. '+ - '
  3. '+ - __('Accounting Entries can be made against leaf nodes. Entries against Groups are not allowed.')+ - '
  4. '+ - '
  5. '+__('Please do NOT create Accounts for Customers and Suppliers. They are created directly from the Customer / Supplier masters.')+'
  6. '+ - '
  7. '+ - ''+__('To create a Bank Account')+': '+ - __('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"')+ - '
  8. '+ - '
  9. '+ - ''+__('To create a Tax Account') +': '+ - __('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.')+ - '
  10. '+ - '
'+ - '

'+__('Please setup your chart of accounts before you start Accounting Entries')+'

').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) { - $('' - + (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 - + '').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 f0fe2e8288..0000000000 --- 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 d96b21355f..0000000000 --- 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 27f1394afe..fbead264cf 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -10,6 +10,8 @@ from frappe.utils import formatdate # 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 @@ def add_ac(args=None): 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 @@ def add_ac(args=None): 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 @@ def add_cc(args=None): 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 @@ def get_account_name(account_type=None, root_type=None, is_group=None, account_c "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 e11544abd1..b6b14803ef 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -51,10 +51,10 @@ def get_data(): }, { "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 @@ def get_data(): "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 0dc51f6df0..b423ee925d 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -97,10 +97,10 @@ def get_data(): }, { "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 dfefb75ed9..52958e39b7 100644 --- a/erpnext/config/crm.py +++ b/erpnext/config/crm.py @@ -92,27 +92,27 @@ def get_data(): { "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 771a0d7904..1a725e2ae9 100644 --- a/erpnext/config/selling.py +++ b/erpnext/config/selling.py @@ -30,9 +30,9 @@ def get_data(): { "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 @@ def get_data(): }, { "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 @@ def get_data(): { "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 @@ def get_data(): { "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 cf3a7baeb6..320d906a5b 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -78,10 +78,10 @@ def get_data(): }, { "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 1976cc6c56..1e685e5ca1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -321,6 +321,7 @@ def get_warehouse_account(): 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 6f45ff2ed7..de8ac946c9 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -24,7 +24,7 @@ frappe.ui.form.on("BOM", { 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 67301411db..287ee9b2f7 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -438,3 +438,14 @@ def validate_bom_no(item, bom_no): 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 0000000000..0404360ee4 --- /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 e69de29bb2..0000000000 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 3c13905caf..0000000000 --- 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 = $('

'+ - __("Select BOM to start") - +'

').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 5b75463457..0000000000 --- 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 80993897a9..0000000000 --- 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 2493643e66..eda2c70ebb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -273,3 +273,4 @@ execute:frappe.rename_doc("DocType", "Payments", "Sales Invoice Payment", force= 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 0000000000..80dbf2ec1c --- /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 d8133ce4cb..233bd2e496 100644 --- a/erpnext/public/js/conf.js +++ b/erpnext/public/js/conf.js @@ -20,12 +20,12 @@ $(document).bind('toolbar_setup', function() { // 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 12307fb9b3..acc0409038 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -71,7 +71,11 @@ $.extend(erpnext.queries, { 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 d6a20e10e7..0000000000 --- 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 baffc48825..0000000000 --- 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 a99fe72872..0000000000 --- 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 54cac650c5..0000000000 --- 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 018ba3b081..0000000000 --- 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 dfa6c0ac19..2a18286343 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -18,11 +18,11 @@ frappe.ui.form.on("Company", { !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 7da7c25d13..7d8829731d 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -87,15 +87,23 @@ class Company(Document): .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 7a5d2fa9dd..3dc5c6c607 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 d440c26a41..d6adebce97 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 @@ frappe.ui.form.on("Item Group", { 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 8bae546a1c..1368392489 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 0000000000..fd2127d013 --- /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 cde04b33aa..bf4e29b105 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 7bdcb0aa9c..97ef3299a3 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -52,26 +52,26 @@ def update_page_info(bootinfo): 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 a1580d52f0..2378e3f3e6 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 @@ class Bin(Document): 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 fe7e54e8e5..f35fa58508 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -160,6 +160,28 @@ $.extend(erpnext.item, { 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 fcf2d0bc14..c05c5f39bf 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 fea8bf039a..27a311d927 100644 --- a/erpnext/stock/doctype/item_reorder/item_reorder.json +++ b/erpnext/stock/doctype/item_reorder/item_reorder.json @@ -3,11 +3,39 @@ "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, @@ -16,6 +44,7 @@ "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 0c33ff7f4f..24d0546224 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 @@ class TestStockEntry(unittest.TestCase): 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 @@ class TestStockEntry(unittest.TestCase): 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 9931ffa7a4..2caabee7b4 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 @@ class StockLedgerEntry(Document): 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 @@ class StockLedgerEntry(Document): 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 f0eea5a3f6..9cc27b7e20 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 create_stock_reconciliation(**args): 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 e2162d22eb..c1dad5cfd0 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 ca80ca793c..b6eaa13602 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -4,4 +4,22 @@ from __future__ import unicode_literals 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 f1f0b66fcb..4a84eadb99 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.ui.form.on("Warehouse", { 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 4e9dd07df4..6b60c635d4 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 901b229cb8..f1f1e705a0 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals 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 @@ class Warehouse(Document): 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 @@ class Warehouse(Document): 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 @@ class Warehouse(Document): {"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 @@ class Warehouse(Document): 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 @@ class Warehouse(Document): 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 0000000000..03614931a5 --- /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) { + $('' + + format_currency(Math.abs(node.data.balance), node.data.company_currency) + + '').insertBefore(node.$ul); + } + } +} \ No newline at end of file diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 45319131fa..ad810a997a 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -37,7 +37,7 @@ def _reorder_item(): 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 @@ def _reorder_item(): 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 @@ def _reorder_item(): 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 @@ def get_item_warehouse_projected_qty(items_to_consider): 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 43b6b9a4f3..696f2b0059 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -71,7 +71,9 @@ def get_conditions(filters): 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 @@ def get_stock_ledger_entries(filters): 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 ac4cbbd2e0..b2e46701da 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -41,7 +41,7 @@ def get_stock_ledger_entries(filters): 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 @@ def get_sle_conditions(filters): 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 @@ def get_opening_balance(filters, columns): 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 @@ def get_opening_balance(filters, columns): 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 89963abde3..409833a939 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 @@ def get_data(filters): 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 3f9de86493..51d82a6dcc 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -15,24 +15,34 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None): 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 @@ def validate_warehouse_company(warehouse, company): 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