diff --git a/accounts/doctype/account/account.js b/accounts/doctype/account/account.js index 6b06572d8eb..9ccb16b1d98 100644 --- a/accounts/doctype/account/account.js +++ b/accounts/doctype/account/account.js @@ -18,12 +18,22 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) { cur_frm.toggle_display('account_name', doc.__islocal); // hide fields if group - cur_frm.toggle_display(['account_type', 'master_type', 'master_name', 'freeze_account', + cur_frm.toggle_display(['account_type', 'master_type', 'master_name', 'credit_days', 'credit_limit', 'tax_rate'], doc.group_or_ledger=='Ledger') // disable fields cur_frm.toggle_enable(['account_name', 'debit_or_credit', 'group_or_ledger', 'is_pl_account', 'company'], false); + + if(doc.group_or_ledger=='Ledger') { + wn.model.with_doc("Accounts Settings", "Accounts Settings", function (name) { + var accounts_settings = wn.model.get_doc("Accounts Settings", name); + var display = accounts_settings["frozen_accounts_modifier"] + && in_list(user_roles, accounts_settings["frozen_accounts_modifier"]); + + cur_frm.toggle_display('freeze_account', display); + }); + } // read-only for root accounts if(!doc.parent_account) { @@ -32,10 +42,10 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) { } else { // credit days and type if customer or supplier cur_frm.set_intro(null); - cur_frm.toggle_display(['credit_days', 'credit_limit', 'master_name'], - in_list(['Customer', 'Supplier'], doc.master_type)); - - // hide tax_rate + cur_frm.toggle_display(['credit_days', 'credit_limit'], in_list(['Customer', 'Supplier'], + doc.master_type)); + + cur_frm.cscript.master_type(doc, cdt, cdn); cur_frm.cscript.account_type(doc, cdt, cdn); // show / hide convert buttons @@ -44,7 +54,10 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) { } cur_frm.cscript.master_type = function(doc, cdt, cdn) { - cur_frm.toggle_display(['credit_days', 'credit_limit', 'master_name'], + cur_frm.toggle_display(['credit_days', 'credit_limit'], in_list(['Customer', 'Supplier'], + doc.master_type)); + + cur_frm.toggle_display('master_name', doc.account_type=='Warehouse' || in_list(['Customer', 'Supplier'], doc.master_type)); } @@ -58,10 +71,10 @@ cur_frm.add_fetch('parent_account', 'is_pl_account', 'is_pl_account'); // ----------------------------------------- cur_frm.cscript.account_type = function(doc, cdt, cdn) { if(doc.group_or_ledger=='Ledger') { - cur_frm.toggle_display(['tax_rate'], - doc.account_type == 'Tax'); - cur_frm.toggle_display(['master_type', 'master_name'], - cstr(doc.account_type)==''); + cur_frm.toggle_display(['tax_rate'], doc.account_type == 'Tax'); + cur_frm.toggle_display('master_type', cstr(doc.account_type)==''); + cur_frm.toggle_display('master_name', doc.account_type=='Warehouse' || + in_list(['Customer', 'Supplier'], doc.master_type)); } } @@ -109,11 +122,15 @@ cur_frm.cscript.convert_to_group = function(doc, cdt, cdn) { } cur_frm.fields_dict['master_name'].get_query = function(doc) { - if (doc.master_type) { + if (doc.master_type || doc.account_type=="Warehouse") { + var dt = doc.master_type || "Warehouse"; return { - doctype: doc.master_type, + doctype: dt, query: "accounts.doctype.account.account.get_master_name", - filters: { "master_type": doc.master_type } + filters: { + "master_type": dt, + "company": doc.company + } } } } diff --git a/accounts/doctype/account/account.py b/accounts/doctype/account/account.py index 65274d7ba9c..4961d72c85a 100644 --- a/accounts/doctype/account/account.py +++ b/accounts/doctype/account/account.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import flt, fmt_money +from webnotes.utils import flt, fmt_money, cstr, cint from webnotes import msgprint, _ get_value = webnotes.conn.get_value @@ -15,13 +15,25 @@ class DocType: self.nsm_parent_field = 'parent_account' def autoname(self): - """Append abbreviation to company on naming""" self.doc.name = self.doc.account_name.strip() + ' - ' + \ webnotes.conn.get_value("Company", self.doc.company, "abbr") def get_address(self): - address = webnotes.conn.get_value(self.doc.master_type, self.doc.master_name, "address") - return {'address': address} + return { + 'address': webnotes.conn.get_value(self.doc.master_type, + self.doc.master_name, "address") + } + + def validate(self): + self.validate_master_name() + self.validate_parent() + self.validate_duplicate_account() + self.validate_root_details() + self.validate_mandatory() + self.validate_frozen_accounts_modifier() + + if not self.doc.parent_account: + self.doc.parent_account = '' def validate_master_name(self): """Remind to add master name""" @@ -70,6 +82,15 @@ class DocType: if webnotes.conn.exists("Account", self.doc.name): if not webnotes.conn.get_value("Account", self.doc.name, "parent_account"): webnotes.msgprint("Root cannot be edited.", raise_exception=1) + + def validate_frozen_accounts_modifier(self): + old_value = webnotes.conn.get_value("Account", self.doc.name, "freeze_account") + if old_value and old_value != self.doc.freeze_account: + frozen_accounts_modifier = webnotes.conn.get_value( 'Accounts Settings', None, + 'frozen_accounts_modifier') + if not frozen_accounts_modifier or \ + frozen_accounts_modifier not in webnotes.user.get_roles(): + webnotes.throw(_("You are not authorized to set Frozen value")) def convert_group_to_ledger(self): if self.check_if_child_exists(): @@ -97,9 +118,7 @@ class DocType: # Check if any previous balance exists def check_gle_exists(self): - exists = webnotes.conn.sql("""select name from `tabGL Entry` where account = %s - and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name) - return exists and exists[0][0] or '' + return webnotes.conn.get_value("GL Entry", {"account": self.doc.name}) def check_if_child_exists(self): return webnotes.conn.sql("""select name from `tabAccount` where parent_account = %s @@ -110,16 +129,25 @@ class DocType: msgprint("Debit or Credit field is mandatory", raise_exception=1) if not self.doc.is_pl_account: msgprint("Is PL Account field is mandatory", raise_exception=1) - - def validate(self): - self.validate_master_name() - self.validate_parent() - self.validate_duplicate_account() - self.validate_root_details() - self.validate_mandatory() - - if not self.doc.parent_account: - self.doc.parent_account = '' + + def validate_warehouse_account(self): + if not cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")): + return + + if self.doc.account_type == "Warehouse": + old_warehouse = cstr(webnotes.conn.get_value("Account", self.doc.name, "master_name")) + if old_warehouse != cstr(self.doc.master_name): + if old_warehouse: + self.validate_warehouse(old_warehouse) + if self.doc.master_name: + self.validate_warehouse(self.doc.master_name) + else: + webnotes.throw(_("Master Name is mandatory if account type is Warehouse")) + + def validate_warehouse(self, warehouse): + if webnotes.conn.get_value("Stock Ledger Entry", {"warehouse": warehouse}): + webnotes.throw(_("Stock transactions exist against warehouse ") + warehouse + + _(" .You can not assign / modify / remove Master Name")) def update_nsm_model(self): """update lft, rgt indices for nested set model""" @@ -172,10 +200,6 @@ class DocType: self.validate_trash() self.update_nsm_model() - # delete all cancelled gl entry of this account - webnotes.conn.sql("""delete from `tabGL Entry` where account = %s and - ifnull(is_cancelled, 'No') = 'Yes'""", self.doc.name) - def on_rename(self, new, old, merge=False): company_abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr") parts = new.split(" - ") @@ -203,9 +227,11 @@ class DocType: return " - ".join(parts) def get_master_name(doctype, txt, searchfield, start, page_len, filters): - return webnotes.conn.sql("""select name from `tab%s` where %s like %s + conditions = (" and company='%s'"% filters["company"]) if doctype == "Warehouse" else "" + + return webnotes.conn.sql("""select name from `tab%s` where %s like %s %s order by name limit %s, %s""" % - (filters["master_type"], searchfield, "%s", "%s", "%s"), + (filters["master_type"], searchfield, "%s", conditions, "%s", "%s"), ("%%%s%%" % txt, start, page_len), as_list=1) def get_parent_account(doctype, txt, searchfield, start, page_len, filters): diff --git a/accounts/doctype/account/account.txt b/accounts/doctype/account/account.txt index 7a6ebf8dace..459e102d517 100644 --- a/accounts/doctype/account/account.txt +++ b/accounts/doctype/account/account.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-30 12:49:46", "docstatus": 0, - "modified": "2013-07-05 14:23:30", + "modified": "2013-09-24 11:22:18", "modified_by": "Administrator", "owner": "Administrator" }, @@ -23,7 +23,8 @@ "name": "__common__", "parent": "Account", "parentfield": "fields", - "parenttype": "DocType" + "parenttype": "DocType", + "permlevel": 0 }, { "amend": 0, @@ -45,14 +46,12 @@ "fieldname": "properties", "fieldtype": "Section Break", "label": "Account Details", - "oldfieldtype": "Section Break", - "permlevel": 0 + "oldfieldtype": "Section Break" }, { "doctype": "DocField", "fieldname": "column_break0", "fieldtype": "Column Break", - "permlevel": 0, "width": "50%" }, { @@ -64,7 +63,6 @@ "no_copy": 1, "oldfieldname": "account_name", "oldfieldtype": "Data", - "permlevel": 0, "read_only": 1, "reqd": 1, "search_index": 1 @@ -77,7 +75,6 @@ "label": "Level", "oldfieldname": "level", "oldfieldtype": "Int", - "permlevel": 0, "print_hide": 1, "read_only": 1 }, @@ -91,7 +88,6 @@ "oldfieldname": "group_or_ledger", "oldfieldtype": "Select", "options": "\nLedger\nGroup", - "permlevel": 0, "read_only": 1, "reqd": 1, "search_index": 1 @@ -104,7 +100,6 @@ "label": "Debit or Credit", "oldfieldname": "debit_or_credit", "oldfieldtype": "Data", - "permlevel": 0, "read_only": 1, "search_index": 1 }, @@ -117,7 +112,6 @@ "oldfieldname": "is_pl_account", "oldfieldtype": "Select", "options": "Yes\nNo", - "permlevel": 0, "read_only": 1, "search_index": 1 }, @@ -130,7 +124,6 @@ "oldfieldname": "company", "oldfieldtype": "Link", "options": "Company", - "permlevel": 0, "read_only": 1, "reqd": 1, "search_index": 1 @@ -139,7 +132,6 @@ "doctype": "DocField", "fieldname": "column_break1", "fieldtype": "Column Break", - "permlevel": 0, "width": "50%" }, { @@ -150,7 +142,6 @@ "oldfieldname": "parent_account", "oldfieldtype": "Link", "options": "Account", - "permlevel": 0, "search_index": 1 }, { @@ -162,7 +153,7 @@ "label": "Account Type", "oldfieldname": "account_type", "oldfieldtype": "Select", - "options": "\nFixed Asset Account\nBank or Cash\nExpense Account\nTax\nIncome Account\nChargeable", + "options": "\nFixed Asset Account\nBank or Cash\nExpense Account\nTax\nIncome Account\nChargeable\nWarehouse", "permlevel": 0, "search_index": 0 }, @@ -175,19 +166,17 @@ "label": "Rate", "oldfieldname": "tax_rate", "oldfieldtype": "Currency", - "permlevel": 0, "reqd": 0 }, { - "description": "If the account is frozen, entries are allowed for the \"Account Manager\" only.", + "description": "If the account is frozen, entries are allowed to restricted users.", "doctype": "DocField", "fieldname": "freeze_account", "fieldtype": "Select", "label": "Frozen", "oldfieldname": "freeze_account", "oldfieldtype": "Select", - "options": "No\nYes", - "permlevel": 2 + "options": "No\nYes" }, { "doctype": "DocField", @@ -197,7 +186,6 @@ "label": "Credit Days", "oldfieldname": "credit_days", "oldfieldtype": "Int", - "permlevel": 0, "print_hide": 1 }, { @@ -209,7 +197,6 @@ "oldfieldname": "credit_limit", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, "print_hide": 1 }, { @@ -220,8 +207,7 @@ "label": "Master Type", "oldfieldname": "master_type", "oldfieldtype": "Select", - "options": "\nSupplier\nCustomer\nEmployee", - "permlevel": 0 + "options": "\nSupplier\nCustomer\nEmployee" }, { "doctype": "DocField", @@ -230,8 +216,7 @@ "label": "Master Name", "oldfieldname": "master_name", "oldfieldtype": "Link", - "options": "[Select]", - "permlevel": 0 + "options": "[Select]" }, { "default": "1", @@ -239,8 +224,7 @@ "doctype": "DocField", "fieldname": "allow_negative_balance", "fieldtype": "Check", - "label": "Allow Negative Balance", - "permlevel": 0 + "label": "Allow Negative Balance" }, { "doctype": "DocField", @@ -248,7 +232,6 @@ "fieldtype": "Int", "hidden": 1, "label": "Lft", - "permlevel": 0, "print_hide": 1, "read_only": 1 }, @@ -258,7 +241,6 @@ "fieldtype": "Int", "hidden": 1, "label": "Rgt", - "permlevel": 0, "print_hide": 1, "read_only": 1 }, @@ -268,7 +250,6 @@ "fieldtype": "Data", "hidden": 1, "label": "Old Parent", - "permlevel": 0, "print_hide": 1, "read_only": 1 }, diff --git a/accounts/doctype/account/test_account.py b/accounts/doctype/account/test_account.py index 502f0e5e6b8..fadb73b0cbb 100644 --- a/accounts/doctype/account/test_account.py +++ b/accounts/doctype/account/test_account.py @@ -9,36 +9,38 @@ def make_test_records(verbose): accounts = [ # [account_name, parent_account, group_or_ledger] - ["_Test Account Bank Account", "Bank Accounts - _TC", "Ledger"], + ["_Test Account Bank Account", "Bank Accounts", "Ledger"], - ["_Test Account Stock Expenses", "Direct Expenses - _TC", "Group"], - ["_Test Account Shipping Charges", "_Test Account Stock Expenses - _TC", "Ledger"], - ["_Test Account Customs Duty", "_Test Account Stock Expenses - _TC", "Ledger"], + ["_Test Account Stock Expenses", "Direct Expenses", "Group"], + ["_Test Account Shipping Charges", "_Test Account Stock Expenses", "Ledger"], + ["_Test Account Customs Duty", "_Test Account Stock Expenses", "Ledger"], - ["_Test Account Tax Assets", "Current Assets - _TC", "Group"], - ["_Test Account VAT", "_Test Account Tax Assets - _TC", "Ledger"], - ["_Test Account Service Tax", "_Test Account Tax Assets - _TC", "Ledger"], + + ["_Test Account Tax Assets", "Current Assets", "Group"], + ["_Test Account VAT", "_Test Account Tax Assets", "Ledger"], + ["_Test Account Service Tax", "_Test Account Tax Assets", "Ledger"], + + ["_Test Account Reserves and Surplus", "Current Liabilities", "Ledger"], - ["_Test Account Cost for Goods Sold", "Expenses - _TC", "Ledger"], - ["_Test Account Excise Duty", "_Test Account Tax Assets - _TC", "Ledger"], - ["_Test Account Education Cess", "_Test Account Tax Assets - _TC", "Ledger"], - ["_Test Account S&H Education Cess", "_Test Account Tax Assets - _TC", "Ledger"], - ["_Test Account CST", "Direct Expenses - _TC", "Ledger"], - ["_Test Account Discount", "Direct Expenses - _TC", "Ledger"], + ["_Test Account Cost for Goods Sold", "Expenses", "Ledger"], + ["_Test Account Excise Duty", "_Test Account Tax Assets", "Ledger"], + ["_Test Account Education Cess", "_Test Account Tax Assets", "Ledger"], + ["_Test Account S&H Education Cess", "_Test Account Tax Assets", "Ledger"], + ["_Test Account CST", "Direct Expenses", "Ledger"], + ["_Test Account Discount", "Direct Expenses", "Ledger"], # related to Account Inventory Integration - ["_Test Account Stock In Hand", "Current Assets - _TC", "Ledger"], + ["_Test Account Stock In Hand", "Current Assets", "Ledger"], + ["_Test Account Fixed Assets", "Current Assets", "Ledger"], ] - test_objects = make_test_objects("Account", [[{ - "doctype": "Account", - "account_name": account_name, - "parent_account": parent_account, - "company": "_Test Company", - "group_or_ledger": group_or_ledger - }] for account_name, parent_account, group_or_ledger in accounts]) - - webnotes.conn.set_value("Company", "_Test Company", "stock_in_hand_account", - "_Test Account Stock In Hand - _TC") + for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"]]: + test_objects = make_test_objects("Account", [[{ + "doctype": "Account", + "account_name": account_name, + "parent_account": parent_account + " - " + abbr, + "company": company, + "group_or_ledger": group_or_ledger + }] for account_name, parent_account, group_or_ledger in accounts]) return test_objects \ No newline at end of file diff --git a/accounts/doctype/accounts_settings/accounts_settings.py b/accounts/doctype/accounts_settings/accounts_settings.py index 96f324acda5..0d106e8e8b6 100644 --- a/accounts/doctype/accounts_settings/accounts_settings.py +++ b/accounts/doctype/accounts_settings/accounts_settings.py @@ -5,23 +5,17 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cint +from webnotes.utils import cint, cstr +from webnotes import msgprint, _ class DocType: def __init__(self, d, dl): self.doc, self.doclist = d, dl - def validate(self): - self.make_adjustment_jv_for_auto_inventory() - - def make_adjustment_jv_for_auto_inventory(self): - previous_auto_inventory_accounting = cint(webnotes.conn.get_value("Accounts Settings", - None, "auto_inventory_accounting")) - if cint(self.doc.auto_inventory_accounting) != previous_auto_inventory_accounting: - from accounts.utils import create_stock_in_hand_jv - create_stock_in_hand_jv(reverse = \ - cint(self.doc.auto_inventory_accounting) < previous_auto_inventory_accounting) - def on_update(self): - for key in ["auto_inventory_accounting"]: - webnotes.conn.set_default(key, self.doc.fields.get(key, '')) + webnotes.conn.set_default("auto_accounting_for_stock", self.doc.auto_accounting_for_stock) + + if self.doc.auto_accounting_for_stock: + for wh in webnotes.conn.sql("select name from `tabWarehouse`"): + wh_bean = webnotes.bean("Warehouse", wh[0]) + wh_bean.save() \ No newline at end of file diff --git a/accounts/doctype/accounts_settings/accounts_settings.txt b/accounts/doctype/accounts_settings/accounts_settings.txt index b8be1614277..026cf613f79 100644 --- a/accounts/doctype/accounts_settings/accounts_settings.txt +++ b/accounts/doctype/accounts_settings/accounts_settings.txt @@ -2,7 +2,7 @@ { "creation": "2013-06-24 15:49:57", "docstatus": 0, - "modified": "2013-07-05 14:23:40", + "modified": "2013-09-24 11:52:58", "modified_by": "Administrator", "owner": "Administrator" }, @@ -39,11 +39,12 @@ "name": "Accounts Settings" }, { + "default": "1", "description": "If enabled, the system will post accounting entries for inventory automatically.", "doctype": "DocField", - "fieldname": "auto_inventory_accounting", + "fieldname": "auto_accounting_for_stock", "fieldtype": "Check", - "label": "Enable Auto Inventory Accounting" + "label": "Make Accounting Entry For Every Stock Movement" }, { "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", @@ -53,11 +54,19 @@ "label": "Accounts Frozen Upto" }, { - "description": "Users with this role are allowed to do / modify accounting entry before frozen date", + "description": "Users with this role are allowed to create / modify accounting entry before frozen date", "doctype": "DocField", "fieldname": "bde_auth_role", "fieldtype": "Link", - "label": "Allow Editing of Frozen Accounts For", + "label": "Allowed Role to Edit Entries Before Frozen Date", + "options": "Role" + }, + { + "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", + "doctype": "DocField", + "fieldname": "frozen_accounts_modifier", + "fieldtype": "Link", + "label": "Frozen Accounts Modifier", "options": "Role" }, { diff --git a/accounts/doctype/budget_control/README.md b/accounts/doctype/budget_control/README.md deleted file mode 100644 index 28210156c00..00000000000 --- a/accounts/doctype/budget_control/README.md +++ /dev/null @@ -1 +0,0 @@ -Backend scripts for Budget Management. \ No newline at end of file diff --git a/accounts/doctype/budget_control/__init__.py b/accounts/doctype/budget_control/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/accounts/doctype/budget_control/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/accounts/doctype/budget_control/budget_control.py b/accounts/doctype/budget_control/budget_control.py deleted file mode 100644 index 0aa64c86fc4..00000000000 --- a/accounts/doctype/budget_control/budget_control.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -from webnotes.utils import cstr, flt, getdate -from webnotes import msgprint - -class DocType: - def __init__(self,d,dl): - self.doc, self.doclist = d, dl - - # Get monthly budget - #------------------- - def get_monthly_budget(self, distribution_id, cfy, st_date, post_dt, budget_allocated): - - # get month_list - st_date, post_dt = getdate(st_date), getdate(post_dt) - - if distribution_id: - if st_date.month <= post_dt.month: - tot_per_allocated = webnotes.conn.sql("select ifnull(sum(percentage_allocation),0) from `tabBudget Distribution Detail` where parent='%s' and idx between '%s' and '%s'" % (distribution_id, st_date.month, post_dt.month))[0][0] - - if st_date.month > post_dt.month: - - tot_per_allocated = flt(webnotes.conn.sql("select ifnull(sum(percentage_allocation),0) from `tabBudget Distribution Detail` where parent='%s' and idx between '%s' and '%s'" % (distribution_id, st_date.month, 12 ))[0][0]) - tot_per_allocated = flt(tot_per_allocated) + flt(webnotes.conn.sql("select ifnull(sum(percentage_allocation),0) from `tabBudget Distribution Detail` where parent='%s' and idx between '%s' and '%s'" % (distribution_id, 1, post_dt.month))[0][0]) - - return (flt(budget_allocated) * flt(tot_per_allocated)) / 100 - period_diff = webnotes.conn.sql("select PERIOD_DIFF('%s','%s')" % (post_dt.strftime('%Y%m'), st_date.strftime('%Y%m'))) - - return (flt(budget_allocated) * (flt(period_diff[0][0]) + 1)) / 12 - - def validate_budget(self, acct, cost_center, actual, budget, action): - # action if actual exceeds budget - if flt(actual) > flt(budget): - msgprint("Your monthly expense "+ cstr((action == 'stop') and "will exceed" or "has exceeded") +" budget for Account - "+cstr(acct)+" under Cost Center - "+ cstr(cost_center) + ""+cstr((action == 'Stop') and ", you can not have this transaction." or ".")) - if action == 'Stop': raise Exception - - def check_budget(self,gle,cancel): - # get allocated budget - - bgt = webnotes.conn.sql("""select t1.budget_allocated, t1.actual, t2.distribution_id - from `tabBudget Detail` t1, `tabCost Center` t2 - where t1.account='%s' and t1.parent=t2.name and t2.name = '%s' - and t1.fiscal_year='%s'""" % - (gle['account'], gle['cost_center'], gle['fiscal_year']), as_dict =1) - - curr_amt = flt(gle['debit']) - flt(gle['credit']) - if cancel: curr_amt = -1 * curr_amt - - if bgt and bgt[0]['budget_allocated']: - # check budget flag in Company - bgt_flag = webnotes.conn.sql("""select yearly_bgt_flag, monthly_bgt_flag - from `tabCompany` where name = '%s'""" % gle['company'], as_dict =1) - - if bgt_flag and bgt_flag[0]['monthly_bgt_flag'] in ['Stop', 'Warn']: - # get start date and last date - start_date = webnotes.conn.get_value('Fiscal Year', gle['fiscal_year'], \ - 'year_start_date').strftime('%Y-%m-%d') - end_date = webnotes.conn.sql("select LAST_DAY('%s')" % gle['posting_date']) - - # get Actual - actual = self.get_period_difference(gle['account'] + - '~~~' + cstr(start_date) + '~~~' + cstr(end_date[0][0]), gle['cost_center']) - - # Get Monthly budget - budget = self.get_monthly_budget(bgt and bgt[0]['distribution_id'] or '' , \ - gle['fiscal_year'], start_date, gle['posting_date'], bgt[0]['budget_allocated']) - - # validate monthly budget - self.validate_budget(gle['account'], gle['cost_center'], \ - flt(actual) + flt(curr_amt), budget, bgt_flag[0]['monthly_bgt_flag']) - - # update actual against budget allocated in cost center - webnotes.conn.sql("""update `tabBudget Detail` set actual = ifnull(actual,0) + %s - where account = '%s' and fiscal_year = '%s' and parent = '%s'""" % - (curr_amt, gle['account'],gle['fiscal_year'], gle['cost_center'])) - - - def get_period_difference(self, arg, cost_center =''): - # used in General Ledger Page Report - # used for Budget where cost center passed as extra argument - acc, f, t = arg.split('~~~') - c, fy = '', webnotes.conn.get_defaults()['fiscal_year'] - - det = webnotes.conn.sql("select debit_or_credit, lft, rgt, is_pl_account from tabAccount where name=%s", acc) - if f: c += (' and t1.posting_date >= "%s"' % f) - if t: c += (' and t1.posting_date <= "%s"' % t) - if cost_center: c += (' and t1.cost_center = "%s"' % cost_center) - bal = webnotes.conn.sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) from `tabGL Entry` t1 where t1.account='%s' %s" % (acc, c)) - bal = bal and flt(bal[0][0]) or 0 - - if det[0][0] != 'Debit': - bal = (-1) * bal - - return flt(bal) \ No newline at end of file diff --git a/accounts/doctype/budget_control/budget_control.txt b/accounts/doctype/budget_control/budget_control.txt deleted file mode 100644 index 13adf4b4065..00000000000 --- a/accounts/doctype/budget_control/budget_control.txt +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "creation": "2012-03-27 14:35:41", - "docstatus": 0, - "modified": "2013-07-10 14:54:06", - "modified_by": "Administrator", - "owner": "nabin@webnotestech.com" - }, - { - "doctype": "DocType", - "issingle": 1, - "module": "Accounts", - "name": "__common__" - }, - { - "doctype": "DocType", - "name": "Budget Control" - } -] \ No newline at end of file diff --git a/accounts/doctype/budget_detail/budget_detail.txt b/accounts/doctype/budget_detail/budget_detail.txt index f16190d0bdd..f53ff52e582 100644 --- a/accounts/doctype/budget_detail/budget_detail.txt +++ b/accounts/doctype/budget_detail/budget_detail.txt @@ -2,7 +2,7 @@ { "creation": "2013-03-07 11:55:04", "docstatus": 0, - "modified": "2013-07-10 14:54:06", + "modified": "2013-08-22 17:27:59", "modified_by": "Administrator", "owner": "Administrator" }, @@ -20,7 +20,8 @@ "parent": "Budget Detail", "parentfield": "fields", "parenttype": "DocType", - "permlevel": 0 + "permlevel": 0, + "reqd": 1 }, { "doctype": "DocType", @@ -35,7 +36,6 @@ "oldfieldname": "account", "oldfieldtype": "Link", "options": "Account", - "reqd": 1, "search_index": 1 }, { @@ -45,18 +45,7 @@ "label": "Budget Allocated", "oldfieldname": "budget_allocated", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "reqd": 1 - }, - { - "doctype": "DocField", - "fieldname": "actual", - "fieldtype": "Currency", - "label": "Actual", - "oldfieldname": "actual", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "read_only": 1 + "options": "Company:company:default_currency" }, { "doctype": "DocField", @@ -67,7 +56,6 @@ "oldfieldname": "fiscal_year", "oldfieldtype": "Select", "options": "link:Fiscal Year", - "reqd": 1, "search_index": 1 } ] \ No newline at end of file diff --git a/accounts/doctype/budget_distribution/test_budget_distribution.py b/accounts/doctype/budget_distribution/test_budget_distribution.py index 7ac835db7d8..bf18cdefb1e 100644 --- a/accounts/doctype/budget_distribution/test_budget_distribution.py +++ b/accounts/doctype/budget_distribution/test_budget_distribution.py @@ -1,4 +1,70 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. # License: GNU General Public License v3. See license.txt -test_records = [] \ No newline at end of file +test_records = [ + [{ + "doctype": "Budget Distribution", + "distribution_id": "_Test Distribution", + "fiscal_year": "_Test Fiscal Year 2013", + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "January", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "February", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "March", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "April", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "May", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "June", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "July", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "August", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "September", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "October", + "percentage_allocation": "8" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "November", + "percentage_allocation": "10" + }, { + "doctype": "Budget Distribution Detail", + "parentfield": "budget_distribution_details", + "month": "December", + "percentage_allocation": "10" + }] +] \ No newline at end of file diff --git a/accounts/doctype/cost_center/cost_center.py b/accounts/doctype/cost_center/cost_center.py index 6f977d775eb..4b18aae4e8d 100644 --- a/accounts/doctype/cost_center/cost_center.py +++ b/accounts/doctype/cost_center/cost_center.py @@ -46,8 +46,7 @@ class DocType(DocTypeNestedSet): return 1 def check_gle_exists(self): - return webnotes.conn.sql("select name from `tabGL Entry` where cost_center = %s and \ - ifnull(is_cancelled, 'No') = 'No'", (self.doc.name)) + return webnotes.conn.get_value("GL Entry", {"cost_center": self.doc.name}) def check_if_child_exists(self): return webnotes.conn.sql("select name from `tabCost Center` where \ diff --git a/accounts/doctype/cost_center/test_cost_center.py b/accounts/doctype/cost_center/test_cost_center.py index 7c63d7c5dd2..056755e463d 100644 --- a/accounts/doctype/cost_center/test_cost_center.py +++ b/accounts/doctype/cost_center/test_cost_center.py @@ -7,6 +7,13 @@ test_records = [ "cost_center_name": "_Test Cost Center", "parent_cost_center": "_Test Company - _TC", "company": "_Test Company", - "group_or_ledger": "Ledger" + "group_or_ledger": "Ledger", + "distribution_id": "_Test Distribution", + }, { + "doctype": "Budget Detail", + "parentfield": "budget_details", + "account": "_Test Account Cost for Goods Sold - _TC", + "budget_allocated": 100000, + "fiscal_year": "_Test Fiscal Year 2013" }], ] \ No newline at end of file diff --git a/accounts/doctype/gl_entry/gl_entry.py b/accounts/doctype/gl_entry/gl_entry.py index 5cde97a7064..22435120e0f 100644 --- a/accounts/doctype/gl_entry/gl_entry.py +++ b/accounts/doctype/gl_entry/gl_entry.py @@ -7,50 +7,49 @@ import webnotes from webnotes.utils import flt, fmt_money, getdate from webnotes.model.code import get_obj from webnotes import msgprint, _ - class DocType: def __init__(self,d,dl): self.doc, self.doclist = d, dl - def validate(self): # not called on cancel + def validate(self): self.check_mandatory() self.pl_must_have_cost_center() self.validate_posting_date() - self.doc.is_cancelled = 'No' # will be reset by GL Control if cancelled self.check_credit_limit() self.check_pl_account() - - def on_update(self, adv_adj, cancel, update_outstanding = 'Yes'): - self.validate_account_details(adv_adj) self.validate_cost_center() - self.check_freezing_date(adv_adj) - self.check_negative_balance(adv_adj) + + def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'): + self.validate_account_details(adv_adj) + validate_frozen_account(self.doc.account, adv_adj) + check_freezing_date(self.doc.posting_date, adv_adj) + check_negative_balance(self.doc.account, adv_adj) # Update outstanding amt on against voucher if self.doc.against_voucher and self.doc.against_voucher_type != "POS" \ and update_outstanding == 'Yes': - self.update_outstanding_amt() + update_outstanding_amt(self.doc.account, self.doc.against_voucher_type, + self.doc.against_voucher) def check_mandatory(self): mandatory = ['account','remarks','voucher_type','voucher_no','fiscal_year','company'] for k in mandatory: if not self.doc.fields.get(k): - msgprint(k + _(" is mandatory for GL Entry"), raise_exception=1) - + webnotes.throw(k + _(" is mandatory for GL Entry")) + # Zero value transaction is not allowed if not (flt(self.doc.debit) or flt(self.doc.credit)): - msgprint(_("GL Entry: Debit or Credit amount is mandatory for ") + self.doc.account, - raise_exception=1) + webnotes.throw(_("GL Entry: Debit or Credit amount is mandatory for ") + + self.doc.account) def pl_must_have_cost_center(self): if webnotes.conn.get_value("Account", self.doc.account, "is_pl_account") == "Yes": if not self.doc.cost_center and self.doc.voucher_type != 'Period Closing Voucher': - msgprint(_("Cost Center must be specified for PL Account: ") + self.doc.account, - raise_exception=1) - else: - if self.doc.cost_center: - self.doc.cost_center = "" + webnotes.throw(_("Cost Center must be specified for PL Account: ") + + self.doc.account) + elif self.doc.cost_center: + self.doc.cost_center = None def validate_posting_date(self): from accounts.utils import validate_fiscal_year @@ -64,7 +63,7 @@ class DocType: if (self.doc.voucher_type=='Journal Voucher' or self.doc.voucher_type=='Sales Invoice') \ and (master_type =='Customer' and master_name): dbcr = webnotes.conn.sql("""select sum(debit), sum(credit) from `tabGL Entry` - where account = '%s' and is_cancelled='No'""" % self.doc.account) + where account = %s""", self.doc.account) if dbcr: tot_outstanding = flt(dbcr[0][0]) - flt(dbcr[0][1]) + \ flt(self.doc.debit) - flt(self.doc.credit) @@ -74,30 +73,23 @@ class DocType: def check_pl_account(self): if self.doc.is_opening=='Yes' and \ webnotes.conn.get_value("Account", self.doc.account, "is_pl_account") == "Yes": - msgprint(_("For opening balance entry account can not be a PL account"), - raise_exception=1) + webnotes.throw(_("For opening balance entry account can not be a PL account")) def validate_account_details(self, adv_adj): """Account must be ledger, active and not freezed""" - ret = webnotes.conn.sql("""select group_or_ledger, docstatus, freeze_account, company - from tabAccount where name=%s""", self.doc.account, as_dict=1) + ret = webnotes.conn.sql("""select group_or_ledger, docstatus, company + from tabAccount where name=%s""", self.doc.account, as_dict=1)[0] - if ret and ret[0]["group_or_ledger"]=='Group': - msgprint(_("Account") + ": " + self.doc.account + _(" is not a ledger"), raise_exception=1) + if ret.group_or_ledger=='Group': + webnotes.throw(_("Account") + ": " + self.doc.account + _(" is not a ledger")) - if ret and ret[0]["docstatus"]==2: - msgprint(_("Account") + ": " + self.doc.account + _(" is not active"), raise_exception=1) + if ret.docstatus==2: + webnotes.throw(_("Account") + ": " + self.doc.account + _(" is not active")) - # Account has been freezed for other users except account manager - if ret and ret[0]["freeze_account"]== 'Yes' and not adv_adj \ - and not 'Accounts Manager' in webnotes.user.get_roles(): - msgprint(_("Account") + ": " + self.doc.account + _(" has been freezed. \ - Only Accounts Manager can do transaction against this account"), raise_exception=1) - - if self.doc.is_cancelled in ("No", None) and ret and ret[0]["company"] != self.doc.company: - msgprint(_("Account") + ": " + self.doc.account + _(" does not belong to the company") \ - + ": " + self.doc.company, raise_exception=1) + if ret.company != self.doc.company: + webnotes.throw(_("Account") + ": " + self.doc.account + + _(" does not belong to the company") + ": " + self.doc.company) def validate_cost_center(self): if not hasattr(self, "cost_center_company"): @@ -105,70 +97,80 @@ class DocType: def _get_cost_center_company(): if not self.cost_center_company.get(self.doc.cost_center): - self.cost_center_company[self.doc.cost_center] = webnotes.conn.get_value("Cost Center", - self.doc.cost_center, "company") + self.cost_center_company[self.doc.cost_center] = webnotes.conn.get_value( + "Cost Center", self.doc.cost_center, "company") return self.cost_center_company[self.doc.cost_center] - if self.doc.is_cancelled in ("No", None) and \ - self.doc.cost_center and _get_cost_center_company() != self.doc.company: - msgprint(_("Cost Center") + ": " + self.doc.cost_center \ - + _(" does not belong to the company") + ": " + self.doc.company, raise_exception=True) - - def check_freezing_date(self, adv_adj): - """ - Nobody can do GL Entries where posting date is before freezing date - except authorized person - """ - if not adv_adj: - acc_frozen_upto = webnotes.conn.get_value('Accounts Settings', None, 'acc_frozen_upto') - if acc_frozen_upto: - bde_auth_role = webnotes.conn.get_value( 'Accounts Settings', None,'bde_auth_role') - if getdate(self.doc.posting_date) <= getdate(acc_frozen_upto) \ - and not bde_auth_role in webnotes.user.get_roles(): - msgprint(_("You are not authorized to do/modify back dated entries before ") + - getdate(acc_frozen_upto).strftime('%d-%m-%Y'), raise_exception=1) + if self.doc.cost_center and _get_cost_center_company() != self.doc.company: + webnotes.throw(_("Cost Center") + ": " + self.doc.cost_center + + _(" does not belong to the company") + ": " + self.doc.company) - def check_negative_balance(self, adv_adj): - if not adv_adj: - account = webnotes.conn.get_value("Account", self.doc.account, - ["allow_negative_balance", "debit_or_credit"], as_dict=True) - if not account["allow_negative_balance"]: - balance = webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry` - where account = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.account) - balance = account["debit_or_credit"] == "Debit" and \ - flt(balance[0][0]) or -1*flt(balance[0][0]) - - if flt(balance) < 0: - msgprint(_("Negative balance is not allowed for account ") + self.doc.account, - raise_exception=1) +def check_negative_balance(account, adv_adj=False): + if not adv_adj: + account_details = webnotes.conn.get_value("Account", account, + ["allow_negative_balance", "debit_or_credit"], as_dict=True) + if not account_details["allow_negative_balance"]: + balance = webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry` + where account = %s""", account) + balance = account_details["debit_or_credit"] == "Debit" and \ + flt(balance[0][0]) or -1*flt(balance[0][0]) + + if flt(balance) < 0: + webnotes.throw(_("Negative balance is not allowed for account ") + account) - def update_outstanding_amt(self): - # get final outstanding amt - bal = flt(webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry` - where against_voucher=%s and against_voucher_type=%s and account = %s - and ifnull(is_cancelled,'No') = 'No'""", (self.doc.against_voucher, - self.doc.against_voucher_type, self.doc.account))[0][0] or 0.0) +def check_freezing_date(posting_date, adv_adj=False): + """ + Nobody can do GL Entries where posting date is before freezing date + except authorized person + """ + if not adv_adj: + acc_frozen_upto = webnotes.conn.get_value('Accounts Settings', None, 'acc_frozen_upto') + if acc_frozen_upto: + bde_auth_role = webnotes.conn.get_value( 'Accounts Settings', None,'bde_auth_role') + if getdate(posting_date) <= getdate(acc_frozen_upto) \ + and not bde_auth_role in webnotes.user.get_roles(): + webnotes.throw(_("You are not authorized to do/modify back dated entries before ") + + getdate(acc_frozen_upto).strftime('%d-%m-%Y')) - if self.doc.against_voucher_type == 'Purchase Invoice': +def update_outstanding_amt(account, against_voucher_type, against_voucher, on_cancel=False): + # get final outstanding amt + bal = flt(webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry` + where against_voucher_type=%s and against_voucher=%s and account = %s""", + (against_voucher_type, against_voucher, account))[0][0] or 0.0) + + if against_voucher_type == 'Purchase Invoice': + bal = -bal + elif against_voucher_type == "Journal Voucher": + against_voucher_amount = flt(webnotes.conn.sql("""select sum(debit) - sum(credit) + from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s + and account = %s""", (against_voucher, account))[0][0]) + + bal = against_voucher_amount + bal + if against_voucher_amount < 0: bal = -bal - elif self.doc.against_voucher_type == "Journal Voucher": - against_voucher_amount = flt(webnotes.conn.sql("""select sum(debit) - sum(credit) - from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s - and account = %s""", (self.doc.against_voucher, self.doc.account))[0][0]) + # Validation : Outstanding can not be negative + if bal < 0 and not on_cancel: + webnotes.throw(_("Outstanding for Voucher ") + against_voucher + _(" will become ") + + fmt_money(bal) + _(". Outstanding cannot be less than zero. \ + Please match exact outstanding.")) + + # Update outstanding amt on against voucher + if against_voucher_type in ["Sales Invoice", "Purchase Invoice"]: + webnotes.conn.sql("update `tab%s` set outstanding_amount=%s where name='%s'" % + (against_voucher_type, bal, against_voucher)) - bal = against_voucher_amount + bal - if against_voucher_amount < 0: - bal = -bal - - # Validation : Outstanding can not be negative - if bal < 0 and self.doc.is_cancelled == 'No': - msgprint(_("Outstanding for Voucher ") + self.doc.against_voucher + - _(" will become ") + fmt_money(bal) + _(". Outstanding cannot be less than zero. \ - Please match exact outstanding."), raise_exception=1) - - # Update outstanding amt on against voucher - if self.doc.against_voucher_type in ["Sales Invoice", "Purchase Invoice"]: - webnotes.conn.sql("update `tab%s` set outstanding_amount=%s where name='%s'"% - (self.doc.against_voucher_type, bal, self.doc.against_voucher)) \ No newline at end of file +def validate_frozen_account(account, adv_adj): + frozen_account = webnotes.conn.get_value("Account", account, "freeze_account") + if frozen_account == 'Yes' and not adv_adj: + frozen_accounts_modifier = webnotes.conn.get_value( 'Accounts Settings', None, + 'frozen_accounts_modifier') + if not frozen_accounts_modifier: + webnotes.throw(account + _(" is a frozen account. \ + Either make the account active or assign role in Accounts Settings \ + who can create / modify entries against this account")) + elif frozen_accounts_modifier not in webnotes.user.get_roles(): + webnotes.throw(account + _(" is a frozen account. ") + + _("To create / edit transactions against this account, you need role") + ": " + + frozen_accounts_modifier) diff --git a/accounts/doctype/gl_entry/gl_entry.txt b/accounts/doctype/gl_entry/gl_entry.txt index 2f20a350a48..90b2ed4afff 100644 --- a/accounts/doctype/gl_entry/gl_entry.txt +++ b/accounts/doctype/gl_entry/gl_entry.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-10 16:34:06", "docstatus": 0, - "modified": "2013-07-05 14:39:07", + "modified": "2013-08-22 17:12:13", "modified_by": "Administrator", "owner": "Administrator" }, @@ -171,17 +171,6 @@ "oldfieldtype": "Text", "search_index": 0 }, - { - "doctype": "DocField", - "fieldname": "is_cancelled", - "fieldtype": "Select", - "in_filter": 1, - "label": "Is Cancelled", - "oldfieldname": "is_cancelled", - "oldfieldtype": "Select", - "options": "No\nYes", - "search_index": 0 - }, { "doctype": "DocField", "fieldname": "is_opening", diff --git a/accounts/doctype/journal_voucher/journal_voucher.py b/accounts/doctype/journal_voucher/journal_voucher.py index a43f8ca7a58..ed4a0d79c7c 100644 --- a/accounts/doctype/journal_voucher/journal_voucher.py +++ b/accounts/doctype/journal_voucher/journal_voucher.py @@ -7,7 +7,7 @@ import webnotes from webnotes.utils import cint, cstr, flt, fmt_money, formatdate, getdate from webnotes.model.doc import addchild from webnotes.model.bean import getlist -from webnotes import msgprint +from webnotes import msgprint, _ from setup.utils import get_company_currency from controllers.accounts_controller import AccountsController @@ -49,7 +49,7 @@ class DocType(AccountsController): from accounts.utils import remove_against_link_from_jv remove_against_link_from_jv(self.doc.doctype, self.doc.name, "against_jv") - self.make_gl_entries(cancel=1) + self.make_gl_entries(1) def on_trash(self): pass @@ -255,7 +255,7 @@ class DocType(AccountsController): "against_voucher": d.against_voucher or d.against_invoice or d.against_jv, "remarks": self.doc.remark, "cost_center": d.cost_center - }, cancel) + }) ) if gl_map: make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj) diff --git a/accounts/doctype/journal_voucher/test_journal_voucher.py b/accounts/doctype/journal_voucher/test_journal_voucher.py index 30e3ada60ac..f80b9453a80 100644 --- a/accounts/doctype/journal_voucher/test_journal_voucher.py +++ b/accounts/doctype/journal_voucher/test_journal_voucher.py @@ -8,6 +8,7 @@ import webnotes class TestJournalVoucher(unittest.TestCase): def test_journal_voucher_with_against_jv(self): + self.clear_account_balance() jv_invoice = webnotes.bean(copy=test_records[2]) jv_invoice.insert() jv_invoice.submit() @@ -31,6 +32,101 @@ class TestJournalVoucher(unittest.TestCase): self.assertTrue(not webnotes.conn.sql("""select name from `tabJournal Voucher Detail` where against_jv=%s""", jv_invoice.doc.name)) + + def test_jv_against_stock_account(self): + from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory + set_perpetual_inventory() + + jv = webnotes.bean(copy=test_records[0]) + jv.doclist[1].account = "_Test Warehouse - _TC" + jv.insert() + + from accounts.general_ledger import StockAccountInvalidTransaction + self.assertRaises(StockAccountInvalidTransaction, jv.submit) + + set_perpetual_inventory(0) + + def test_monthly_budget_crossed_ignore(self): + webnotes.conn.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore") + self.clear_account_balance() + + jv = webnotes.bean(copy=test_records[0]) + jv.doclist[2].account = "_Test Account Cost for Goods Sold - _TC" + jv.doclist[2].cost_center = "_Test Cost Center - _TC" + jv.doclist[2].debit = 20000.0 + jv.doclist[1].credit = 20000.0 + jv.insert() + jv.submit() + self.assertTrue(webnotes.conn.get_value("GL Entry", + {"voucher_type": "Journal Voucher", "voucher_no": jv.doc.name})) + + def test_monthly_budget_crossed_stop(self): + from accounts.utils import BudgetError + webnotes.conn.set_value("Company", "_Test Company", "monthly_bgt_flag", "Stop") + self.clear_account_balance() + + jv = webnotes.bean(copy=test_records[0]) + jv.doclist[2].account = "_Test Account Cost for Goods Sold - _TC" + jv.doclist[2].cost_center = "_Test Cost Center - _TC" + jv.doclist[2].debit = 20000.0 + jv.doclist[1].credit = 20000.0 + jv.insert() + + self.assertRaises(BudgetError, jv.submit) + + webnotes.conn.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore") + + def test_yearly_budget_crossed_stop(self): + from accounts.utils import BudgetError + self.clear_account_balance() + self.test_monthly_budget_crossed_ignore() + + webnotes.conn.set_value("Company", "_Test Company", "yearly_bgt_flag", "Stop") + + jv = webnotes.bean(copy=test_records[0]) + jv.doc.posting_date = "2013-08-12" + jv.doclist[2].account = "_Test Account Cost for Goods Sold - _TC" + jv.doclist[2].cost_center = "_Test Cost Center - _TC" + jv.doclist[2].debit = 150000.0 + jv.doclist[1].credit = 150000.0 + jv.insert() + + self.assertRaises(BudgetError, jv.submit) + + webnotes.conn.set_value("Company", "_Test Company", "yearly_bgt_flag", "Ignore") + + def test_monthly_budget_on_cancellation(self): + from accounts.utils import BudgetError + webnotes.conn.set_value("Company", "_Test Company", "monthly_bgt_flag", "Stop") + self.clear_account_balance() + + jv = webnotes.bean(copy=test_records[0]) + jv.doclist[1].account = "_Test Account Cost for Goods Sold - _TC" + jv.doclist[1].cost_center = "_Test Cost Center - _TC" + jv.doclist[1].credit = 30000.0 + jv.doclist[2].debit = 30000.0 + jv.submit() + + self.assertTrue(webnotes.conn.get_value("GL Entry", + {"voucher_type": "Journal Voucher", "voucher_no": jv.doc.name})) + + jv1 = webnotes.bean(copy=test_records[0]) + jv1.doclist[2].account = "_Test Account Cost for Goods Sold - _TC" + jv1.doclist[2].cost_center = "_Test Cost Center - _TC" + jv1.doclist[2].debit = 40000.0 + jv1.doclist[1].credit = 40000.0 + jv1.submit() + + self.assertTrue(webnotes.conn.get_value("GL Entry", + {"voucher_type": "Journal Voucher", "voucher_no": jv1.doc.name})) + + self.assertRaises(BudgetError, jv.cancel) + + webnotes.conn.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore") + + def clear_account_balance(self): + webnotes.conn.sql("""delete from `tabGL Entry`""") + test_records = [ [{ diff --git a/accounts/doctype/payment_to_invoice_matching_tool/payment_to_invoice_matching_tool.py b/accounts/doctype/payment_to_invoice_matching_tool/payment_to_invoice_matching_tool.py index 7522c23f283..1dda6c725af 100644 --- a/accounts/doctype/payment_to_invoice_matching_tool/payment_to_invoice_matching_tool.py +++ b/accounts/doctype/payment_to_invoice_matching_tool/payment_to_invoice_matching_tool.py @@ -21,7 +21,7 @@ class DocType: def get_voucher_details(self): total_amount = webnotes.conn.sql("""select sum(%s) from `tabGL Entry` where voucher_type = %s and voucher_no = %s - and account = %s and ifnull(is_cancelled, 'No') = 'No'""" % + and account = %s""" % (self.doc.account_type, '%s', '%s', '%s'), (self.doc.voucher_type, self.doc.voucher_no, self.doc.account)) @@ -29,7 +29,7 @@ class DocType: reconciled_payment = webnotes.conn.sql(""" select sum(ifnull(%s, 0)) - sum(ifnull(%s, 0)) from `tabGL Entry` where against_voucher = %s and voucher_no != %s - and account = %s and ifnull(is_cancelled, 'No') = 'No'""" % + and account = %s""" % ((self.doc.account_type == 'debit' and 'credit' or 'debit'), self.doc.account_type, '%s', '%s', '%s'), (self.doc.voucher_no, self.doc.voucher_no, self.doc.account)) @@ -135,7 +135,6 @@ def gl_entry_details(doctype, txt, searchfield, start, page_len, filters): where gle.account = '%(acc)s' and gle.voucher_type = '%(dt)s' and gle.voucher_no like '%(txt)s' - and ifnull(gle.is_cancelled, 'No') = 'No' and (ifnull(gle.against_voucher, '') = '' or ifnull(gle.against_voucher, '') = gle.voucher_no ) and ifnull(gle.%(account_type)s, 0) > 0 @@ -143,8 +142,7 @@ def gl_entry_details(doctype, txt, searchfield, start, page_len, filters): from `tabGL Entry` where against_voucher_type = '%(dt)s' and against_voucher = gle.voucher_no - and voucher_no != gle.voucher_no - and ifnull(is_cancelled, 'No') = 'No') + and voucher_no != gle.voucher_no) != abs(ifnull(gle.debit, 0) - ifnull(gle.credit, 0) ) %(mcond)s diff --git a/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 237bb638a56..70a49925527 100644 --- a/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -3,178 +3,101 @@ from __future__ import unicode_literals import webnotes - from webnotes.utils import cstr, flt, getdate -from webnotes.model import db_exists -from webnotes.model.doc import Document -from webnotes.model.bean import copy_doclist -from webnotes.model.code import get_obj -from webnotes import msgprint +from webnotes import msgprint, _ +from controllers.accounts_controller import AccountsController - - - -class DocType: +class DocType(AccountsController): def __init__(self,d,dl): self.doc, self.doclist = d, dl - self.td, self.tc = 0, 0 self.year_start_date = '' - self.year_end_date = '' + def validate(self): + self.validate_account_head() + self.validate_posting_date() + self.validate_pl_balances() + + def on_submit(self): + self.make_gl_entries() + + def on_cancel(self): + webnotes.conn.sql("""delete from `tabGL Entry` + where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.doc.name) def validate_account_head(self): - acc_det = webnotes.conn.sql("select debit_or_credit, is_pl_account, group_or_ledger, company \ - from `tabAccount` where name = '%s'" % (self.doc.closing_account_head)) - - # Account should be under liability - if cstr(acc_det[0][0]) != 'Credit' or cstr(acc_det[0][1]) != 'No': - msgprint("Account: %s must be created under 'Source of Funds'" % self.doc.closing_account_head) - raise Exception - - # Account must be a ledger - if cstr(acc_det[0][2]) != 'Ledger': - msgprint("Account %s must be a ledger" % self.doc.closing_account_head) - raise Exception - - # Account should belong to company selected - if cstr(acc_det[0][3]) != self.doc.company: - msgprint("Account %s does not belong to Company %s ." % (self.doc.closing_account_head, self.doc.company)) - raise Exception - + debit_or_credit, is_pl_account = webnotes.conn.get_value("Account", + self.doc.closing_account_head, ["debit_or_credit", "is_pl_account"]) + + if debit_or_credit != 'Credit' or is_pl_account != 'No': + webnotes.throw(_("Account") + ": " + self.doc.closing_account_head + + _("must be a Liability account")) def validate_posting_date(self): - yr = webnotes.conn.sql("""select year_start_date, adddate(year_start_date, interval 1 year) - from `tabFiscal Year` where name=%s""", (self.doc.fiscal_year, )) - self.year_start_date = yr and yr[0][0] or '' - self.year_end_date = yr and yr[0][1] or '' - - # Posting Date should be within closing year - if getdate(self.doc.posting_date) < getdate(self.year_start_date) or getdate(self.doc.posting_date) > getdate(self.year_end_date): - msgprint("Posting Date should be within Closing Fiscal Year") - raise Exception + from accounts.utils import get_fiscal_year + self.year_start_date = get_fiscal_year(self.doc.posting_date)[1] - # Period Closing Entry - pce = webnotes.conn.sql("select name from `tabPeriod Closing Voucher` \ - where posting_date > '%s' and fiscal_year = '%s' and docstatus = 1" \ - % (self.doc.posting_date, self.doc.fiscal_year)) + pce = webnotes.conn.sql("""select name from `tabPeriod Closing Voucher` + where posting_date > %s and fiscal_year = %s and docstatus = 1""", + (self.doc.posting_date, self.doc.fiscal_year)) if pce and pce[0][0]: - msgprint("Another Period Closing Entry: %s has been made after posting date: %s"\ - % (cstr(pce[0][0]), self.doc.posting_date)) - raise Exception + webnotes.throw(_("Another Period Closing Entry") + ": " + cstr(pce[0][0]) + + _("has been made after posting date") + ": " + self.doc.posting_date) - def validate_pl_balances(self): - income_bal = webnotes.conn.sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \ - from `tabGL Entry` t1, tabAccount t2 where t1.account = t2.name \ - and t1.posting_date between '%s' and '%s' and t2.debit_or_credit = 'Credit' \ - and t2.group_or_ledger = 'Ledger' and t2.is_pl_account = 'Yes' and t2.docstatus < 2 \ - and t2.company = '%s'" % (self.year_start_date, self.doc.posting_date, self.doc.company)) + income_bal = webnotes.conn.sql(""" + select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) + from `tabGL Entry` t1, tabAccount t2 + where t1.account = t2.name and t1.posting_date between %s and %s + and t2.debit_or_credit = 'Credit' and t2.is_pl_account = 'Yes' + and t2.docstatus < 2 and t2.company = %s""", + (self.year_start_date, self.doc.posting_date, self.doc.company)) - expense_bal = webnotes.conn.sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \ - from `tabGL Entry` t1, tabAccount t2 where t1.account = t2.name \ - and t1.posting_date between '%s' and '%s' and t2.debit_or_credit = 'Debit' \ - and t2.group_or_ledger = 'Ledger' and t2.is_pl_account = 'Yes' and t2.docstatus < 2 \ - and t2.company = '%s'" % (self.year_start_date, self.doc.posting_date, self.doc.company)) + expense_bal = webnotes.conn.sql(""" + select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) + from `tabGL Entry` t1, tabAccount t2 + where t1.account = t2.name and t1.posting_date between %s and %s + and t2.debit_or_credit = 'Debit' and t2.is_pl_account = 'Yes' + and t2.docstatus < 2 and t2.company=%s""", + (self.year_start_date, self.doc.posting_date, self.doc.company)) income_bal = income_bal and income_bal[0][0] or 0 expense_bal = expense_bal and expense_bal[0][0] or 0 if not income_bal and not expense_bal: - msgprint("Both Income and Expense balances are zero. No Need to make Period Closing Entry.") - raise Exception + webnotes.throw(_("Both Income and Expense balances are zero. \ + No Need to make Period Closing Entry.")) - - def get_pl_balances(self, d_or_c): - """Get account (pl) specific balance""" - acc_bal = webnotes.conn.sql("select t1.account, sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \ - from `tabGL Entry` t1, `tabAccount` t2 where t1.account = t2.name and t2.group_or_ledger = 'Ledger' \ - and ifnull(t2.is_pl_account, 'No') = 'Yes' and ifnull(is_cancelled, 'No') = 'No' \ - and t2.debit_or_credit = '%s' and t2.docstatus < 2 and t2.company = '%s' \ - and t1.posting_date between '%s' and '%s' group by t1.account " \ - % (d_or_c, self.doc.company, self.year_start_date, self.doc.posting_date)) - return acc_bal - + def get_pl_balances(self): + """Get balance for pl accounts""" + return webnotes.conn.sql(""" + select t1.account, sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) as balance + from `tabGL Entry` t1, `tabAccount` t2 + where t1.account = t2.name and ifnull(t2.is_pl_account, 'No') = 'Yes' + and t2.docstatus < 2 and t2.company = %s + and t1.posting_date between %s and %s + group by t1.account + """, (self.doc.company, self.year_start_date, self.doc.posting_date), as_dict=1) - def make_gl_entries(self, acc_det): - for a in acc_det: - if flt(a[1]): - fdict = { - 'account': a[0], - 'cost_center': '', - 'against': '', - 'debit': flt(a[1]) < 0 and -1*flt(a[1]) or 0, - 'credit': flt(a[1]) > 0 and flt(a[1]) or 0, - 'remarks': self.doc.remarks, - 'voucher_type': self.doc.doctype, - 'voucher_no': self.doc.name, - 'transaction_date': self.doc.transaction_date, - 'posting_date': self.doc.posting_date, - 'fiscal_year': self.doc.fiscal_year, - 'against_voucher': '', - 'against_voucher_type': '', - 'company': self.doc.company, - 'is_opening': 'No', - 'aging_date': self.doc.posting_date - } + def make_gl_entries(self): + gl_entries = [] + net_pl_balance = 0 + pl_accounts = self.get_pl_balances() + for acc in pl_accounts: + if flt(acc.balance): + gl_entries.append(self.get_gl_dict({ + "account": acc.account, + "debit": abs(flt(acc.balance)) if flt(acc.balance) < 0 else 0, + "credit": abs(flt(acc.balance)) if flt(acc.balance) > 0 else 0, + })) - self.save_entry(fdict) - + net_pl_balance += flt(acc.balance) - def save_entry(self, fdict, is_cancel = 'No'): - # Create new GL entry object and map values - le = Document('GL Entry') - for k in fdict: - le.fields[k] = fdict[k] - - le_obj = get_obj(doc=le) - # validate except on_cancel - if is_cancel == 'No': - le_obj.validate() + if net_pl_balance: + gl_entries.append(self.get_gl_dict({ + "account": self.doc.closing_account_head, + "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, + "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0 + })) - # update total debit / credit except on_cancel - self.td += flt(le.credit) - self.tc += flt(le.debit) - - # save - le.save(1) - le_obj.on_update(adv_adj = '', cancel = '') - - - def validate(self): - # validate account head - self.validate_account_head() - - # validate posting date - self.validate_posting_date() - - # check if pl balance: - self.validate_pl_balances() - - - def on_submit(self): - - # Makes closing entries for Expense Account - in_acc_det = self.get_pl_balances('Credit') - self.make_gl_entries(in_acc_det) - - # Makes closing entries for Expense Account - ex_acc_det = self.get_pl_balances('Debit') - self.make_gl_entries(ex_acc_det) - - - # Makes Closing entry for Closing Account Head - bal = self.tc - self.td - self.make_gl_entries([[self.doc.closing_account_head, flt(bal)]]) - - - def on_cancel(self): - # get all submit entries of current closing entry voucher - gl_entries = webnotes.conn.sql("select account, debit, credit from `tabGL Entry` where voucher_type = 'Period Closing Voucher' and voucher_no = '%s' and ifnull(is_cancelled, 'No') = 'No'" % (self.doc.name)) - - # Swap Debit & Credit Column and make gl entry - for gl in gl_entries: - fdict = {'account': gl[0], 'cost_center': '', 'against': '', 'debit': flt(gl[2]), 'credit' : flt(gl[1]), 'remarks': "cancelled", 'voucher_type': self.doc.doctype, 'voucher_no': self.doc.name, 'transaction_date': self.doc.transaction_date, 'posting_date': self.doc.posting_date, 'fiscal_year': self.doc.fiscal_year, 'against_voucher': '', 'against_voucher_type': '', 'company': self.doc.company, 'is_opening': 'No', 'aging_date': 'self.doc.posting_date'} - self.save_entry(fdict, is_cancel = 'Yes') - - # Update is_cancelled = 'Yes' to all gl entries for current voucher - webnotes.conn.sql("update `tabGL Entry` set is_cancelled = 'Yes' where voucher_type = '%s' and voucher_no = '%s'" % (self.doc.doctype, self.doc.name)) \ No newline at end of file + from accounts.general_ledger import make_gl_entries + make_gl_entries(gl_entries) diff --git a/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py new file mode 100644 index 00000000000..c21d63f6ecb --- /dev/null +++ b/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -0,0 +1,53 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +# License: GNU General Public License v3. See license.txt + + +from __future__ import unicode_literals +import unittest +import webnotes + +class TestPeriodClosingVoucher(unittest.TestCase): + def test_closing_entry(self): + from accounts.doctype.journal_voucher.test_journal_voucher import test_records as jv_records + jv = webnotes.bean(copy=jv_records[2]) + jv.insert() + jv.submit() + + jv1 = webnotes.bean(copy=jv_records[0]) + jv1.doclist[2].account = "_Test Account Cost for Goods Sold - _TC" + jv1.doclist[2].debit = 600.0 + jv1.doclist[1].credit = 600.0 + jv1.insert() + jv1.submit() + + pcv = webnotes.bean(copy=test_record) + pcv.insert() + pcv.submit() + + gl_entries = webnotes.conn.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type='Period Closing Voucher' and voucher_no=%s + order by account asc, debit asc""", pcv.doc.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_gl_entries = sorted([ + ["_Test Account Reserves and Surplus - _TC", 200.0, 0.0], + ["_Test Account Cost for Goods Sold - _TC", 0.0, 600.0], + ["Sales - _TC", 400.0, 0.0] + ]) + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_gl_entries[i][0], gle.account) + self.assertEquals(expected_gl_entries[i][1], gle.debit) + self.assertEquals(expected_gl_entries[i][2], gle.credit) + + +test_dependencies = ["Customer", "Cost Center"] + +test_record = [{ + "doctype": "Period Closing Voucher", + "closing_account_head": "_Test Account Reserves and Surplus - _TC", + "company": "_Test Company", + "fiscal_year": "_Test Fiscal Year 2013", + "posting_date": "2013-03-31", + "remarks": "test" +}] diff --git a/accounts/doctype/pos_setting/pos_setting.py b/accounts/doctype/pos_setting/pos_setting.py index 183ebbe1d77..6bb85cbe645 100755 --- a/accounts/doctype/pos_setting/pos_setting.py +++ b/accounts/doctype/pos_setting/pos_setting.py @@ -35,7 +35,7 @@ class DocType: (res[0][0], self.doc.company), raise_exception=1) def validate_expense_account(self): - if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) \ + if cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) \ and not self.doc.expense_account: msgprint(_("Expense Account is mandatory"), raise_exception=1) @@ -61,4 +61,4 @@ class DocType: webnotes.defaults.set_global_default("is_pos", 1) def on_trash(self): - self.on_update() \ No newline at end of file + self.on_update() diff --git a/accounts/doctype/pos_setting/pos_setting.txt b/accounts/doctype/pos_setting/pos_setting.txt index 73b92460a5d..2420ad55039 100755 --- a/accounts/doctype/pos_setting/pos_setting.txt +++ b/accounts/doctype/pos_setting/pos_setting.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 12:15:51", "docstatus": 0, - "modified": "2013-08-09 16:35:03", + "modified": "2013-08-28 19:13:42", "modified_by": "Administrator", "owner": "Administrator" }, @@ -163,7 +163,7 @@ "reqd": 1 }, { - "depends_on": "eval:sys_defaults.auto_inventory_accounting", + "depends_on": "eval:sys_defaults.auto_accounting_for_stock", "doctype": "DocField", "fieldname": "expense_account", "fieldtype": "Link", diff --git a/accounts/doctype/purchase_invoice/purchase_invoice.py b/accounts/doctype/purchase_invoice/purchase_invoice.py index be0e2241284..9a6b9010235 100644 --- a/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -211,28 +211,29 @@ class DocType(BuyingController): raise Exception def set_against_expense_account(self): - auto_inventory_accounting = \ - cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) + auto_accounting_for_stock = cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) - if auto_inventory_accounting: + if auto_accounting_for_stock: stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") against_accounts = [] + stock_items = self.get_stock_items() for item in self.doclist.get({"parentfield": "entries"}): - if auto_inventory_accounting and item.item_code in self.stock_items: + if auto_accounting_for_stock and item.item_code in stock_items: # in case of auto inventory accounting, against expense account is always # Stock Received But Not Billed for a stock item - item.expense_head = item.cost_center = None + item.expense_head = stock_not_billed_account + item.cost_center = None if stock_not_billed_account not in against_accounts: against_accounts.append(stock_not_billed_account) elif not item.expense_head: - msgprint(_("""Expense account is mandatory for item: """) + (item.item_code or item.item_name), - raise_exception=1) + msgprint(_("Expense account is mandatory for item") + ": " + + (item.item_code or item.item_name), raise_exception=1) elif item.expense_head not in against_accounts: - # if no auto_inventory_accounting or not a stock item + # if no auto_accounting_for_stock or not a stock item against_accounts.append(item.expense_head) self.doc.against_expense_account = ",".join(against_accounts) @@ -313,9 +314,8 @@ class DocType(BuyingController): self.update_prevdoc_status() def make_gl_entries(self): - from accounts.general_ledger import make_gl_entries - auto_inventory_accounting = \ - cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) + auto_accounting_for_stock = \ + cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) gl_entries = [] @@ -352,17 +352,15 @@ class DocType(BuyingController): valuation_tax += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount) # item gl entries - stock_item_and_auto_inventory_accounting = False - if auto_inventory_accounting: - stock_account = self.get_company_default("stock_received_but_not_billed") - + stock_item_and_auto_accounting_for_stock = False + stock_items = self.get_stock_items() for item in self.doclist.get({"parentfield": "entries"}): - if auto_inventory_accounting and item.item_code in self.stock_items: + if auto_accounting_for_stock and item.item_code in stock_items: if flt(item.valuation_rate): # if auto inventory accounting enabled and stock item, # then do stock related gl entries # expense will be booked in sales invoice - stock_item_and_auto_inventory_accounting = True + stock_item_and_auto_accounting_for_stock = True valuation_amt = (flt(item.amount, self.precision("amount", item)) + flt(item.item_tax_amount, self.precision("item_tax_amount", item)) + @@ -370,7 +368,7 @@ class DocType(BuyingController): gl_entries.append( self.get_gl_dict({ - "account": stock_account, + "account": item.expense_head, "against": self.doc.credit_to, "debit": valuation_amt, "remarks": self.doc.remarks or "Accounting Entry for Stock" @@ -389,13 +387,13 @@ class DocType(BuyingController): }) ) - if stock_item_and_auto_inventory_accounting and valuation_tax: + if stock_item_and_auto_accounting_for_stock and valuation_tax: # credit valuation tax amount in "Expenses Included In Valuation" # this will balance out valuation amount included in cost of goods sold gl_entries.append( self.get_gl_dict({ "account": self.get_company_default("expenses_included_in_valuation"), - "cost_center": self.get_company_default("stock_adjustment_cost_center"), + "cost_center": self.get_company_default("cost_center"), "against": self.doc.credit_to, "credit": valuation_tax, "remarks": self.doc.remarks or "Accounting Entry for Stock" @@ -416,6 +414,7 @@ class DocType(BuyingController): ) if gl_entries: + from accounts.general_ledger import make_gl_entries make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2)) def on_cancel(self): @@ -455,4 +454,4 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): and tabAccount.company = '%(company)s' and tabAccount.%(key)s LIKE '%(txt)s' %(mcond)s""" % {'company': filters['company'], 'key': searchfield, - 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield)}) \ No newline at end of file + 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield)}) diff --git a/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 7d68602c6ca..9e9e2f61c37 100644 --- a/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -9,14 +9,15 @@ import webnotes.model import json from webnotes.utils import cint import webnotes.defaults +from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory test_dependencies = ["Item", "Cost Center"] test_ignore = ["Serial No"] class TestPurchaseInvoice(unittest.TestCase): - def test_gl_entries_without_auto_inventory_accounting(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) - self.assertTrue(not cint(webnotes.defaults.get_global_default("auto_inventory_accounting"))) + def test_gl_entries_without_auto_accounting_for_stock(self): + set_perpetual_inventory(0) + self.assertTrue(not cint(webnotes.defaults.get_global_default("auto_accounting_for_stock"))) wrapper = webnotes.bean(copy=test_records[0]) wrapper.run_method("calculate_taxes_and_totals") @@ -41,9 +42,9 @@ class TestPurchaseInvoice(unittest.TestCase): for d in gl_entries: self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) - def test_gl_entries_with_auto_inventory_accounting(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) + def test_gl_entries_with_auto_accounting_for_stock(self): + set_perpetual_inventory(1) + self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 1) pi = webnotes.bean(copy=test_records[1]) pi.run_method("calculate_taxes_and_totals") @@ -68,11 +69,11 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + set_perpetual_inventory(0) def test_gl_entries_with_aia_for_non_stock_items(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) + set_perpetual_inventory() + self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 1) pi = webnotes.bean(copy=test_records[1]) pi.doclist[1].item_code = "_Test Non Stock Item" @@ -98,8 +99,7 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + set_perpetual_inventory(0) def test_purchase_invoice_calculation(self): wrapper = webnotes.bean(copy=test_records[0]) diff --git a/accounts/doctype/sales_invoice/sales_invoice.js b/accounts/doctype/sales_invoice/sales_invoice.js index 60f73b5a191..aeae28ce03e 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.js +++ b/accounts/doctype/sales_invoice/sales_invoice.js @@ -384,7 +384,7 @@ cur_frm.set_query("income_account", "entries", function(doc) { }); // expense account -if (sys_defaults.auto_inventory_accounting) { +if (sys_defaults.auto_accounting_for_stock) { cur_frm.fields_dict['entries'].grid.get_field('expense_account').get_query = function(doc) { return { filters: { diff --git a/accounts/doctype/sales_invoice/sales_invoice.py b/accounts/doctype/sales_invoice/sales_invoice.py index c7dba9c95b3..12deed73a87 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.py +++ b/accounts/doctype/sales_invoice/sales_invoice.py @@ -82,7 +82,7 @@ class DocType(SellingController): def on_submit(self): if cint(self.doc.update_stock) == 1: - self.update_stock_ledger(update_stock=1) + self.update_stock_ledger() self.update_serial_nos() else: # Check for Approving Authority @@ -90,7 +90,6 @@ class DocType(SellingController): get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self) - self.set_buying_amount() self.check_prev_docstatus() self.update_status_updater_args() @@ -111,7 +110,7 @@ class DocType(SellingController): def on_cancel(self): if cint(self.doc.update_stock) == 1: - self.update_stock_ledger(update_stock = -1) + self.update_stock_ledger() self.update_serial_nos(cancel = True) sales_com_obj = get_obj(dt = 'Sales Common') @@ -196,8 +195,6 @@ class DocType(SellingController): pos = get_pos_settings(self.doc.company) if pos: - self.doc.conversion_rate = flt(pos.conversion_rate) - if not for_validate: self.doc.customer = pos.customer self.set_customer_defaults() @@ -526,41 +523,18 @@ class DocType(SellingController): msgprint("Delivery Note : "+ cstr(d.delivery_note) +" is not submitted") raise Exception , "Validation Error." - - def make_sl_entry(self, d, wh, qty, in_value, update_stock): - st_uom = webnotes.conn.sql("select stock_uom from `tabItem` where name = '%s'"%d['item_code']) - self.values.append({ - 'item_code' : d['item_code'], - 'warehouse' : wh, - 'posting_date' : self.doc.posting_date, - 'posting_time' : self.doc.posting_time, - 'voucher_type' : 'Sales Invoice', - 'voucher_no' : cstr(self.doc.name), - 'voucher_detail_no' : cstr(d['name']), - 'actual_qty' : qty, - 'stock_uom' : st_uom and st_uom[0][0] or '', - 'incoming_rate' : in_value, - 'company' : self.doc.company, - 'fiscal_year' : self.doc.fiscal_year, - 'is_cancelled' : (update_stock==1) and 'No' or 'Yes', - 'batch_no' : cstr(d['batch_no']), - 'serial_no' : d['serial_no'], - "project" : self.doc.project_name - }) - - def update_stock_ledger(self, update_stock): - self.values = [] + def update_stock_ledger(self): + sl_entries = [] items = get_obj('Sales Common').get_item_list(self) for d in items: - if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes": - if not d['warehouse']: - msgprint("Message: Please enter Warehouse for item %s as it is stock item." \ - % d['item_code'], raise_exception=1) - - # Reduce actual qty from warehouse - self.make_sl_entry( d, d['warehouse'], - flt(d['qty']) , 0, update_stock) + if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes" \ + and d.warehouse: + sl_entries.append(self.get_sl_entries(d, { + "actual_qty": -1*flt(d.qty), + "stock_uom": webnotes.conn.get_value("Item", d.item_code, "stock_uom") + })) - get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values) + self.make_sl_entries(sl_entries) def make_gl_entries(self): from accounts.general_ledger import make_gl_entries, merge_similar_entries @@ -584,6 +558,10 @@ class DocType(SellingController): make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2), update_outstanding=update_outstanding, merge_entries=False) + if cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) \ + and cint(self.doc.update_stock): + self.update_gl_entries_after() + def make_customer_gl_entry(self, gl_entries): if self.doc.grand_total: gl_entries.append( @@ -625,15 +603,9 @@ class DocType(SellingController): ) # expense account gl entries - if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) \ + if cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) \ and cint(self.doc.update_stock): - - for item in self.doclist.get({"parentfield": "entries"}): - self.check_expense_account(item) - - if item.buying_amount: - gl_entries += self.get_gl_entries_for_stock(item.expense_account, - -1*item.buying_amount, cost_center=item.cost_center) + gl_entries += self.get_gl_entries_for_stock() def make_pos_gl_entries(self, gl_entries): if cint(self.doc.is_pos) and self.doc.cash_bank_account and self.doc.paid_amount: diff --git a/accounts/doctype/sales_invoice/test_sales_invoice.py b/accounts/doctype/sales_invoice/test_sales_invoice.py index 5e13af626f7..dee098a3e4f 100644 --- a/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -5,10 +5,13 @@ import webnotes import unittest, json from webnotes.utils import flt, cint from webnotes.model.bean import DocstatusTransitionError, TimestampMismatchError +from accounts.utils import get_stock_and_account_difference +from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory class TestSalesInvoice(unittest.TestCase): def make(self): w = webnotes.bean(copy=test_records[0]) + w.doc.is_pos = 0 w.insert() w.submit() return w @@ -92,7 +95,6 @@ class TestSalesInvoice(unittest.TestCase): si.doclist[1].ref_rate = 1 si.doclist[2].export_rate = 3 si.doclist[2].ref_rate = 3 - si.run_method("calculate_taxes_and_totals") si.insert() expected_values = { @@ -299,8 +301,8 @@ class TestSalesInvoice(unittest.TestCase): "Batched for Billing") def test_sales_invoice_gl_entry_without_aii(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) - + self.clear_stock_account_balance() + set_perpetual_inventory(0) si = webnotes.bean(copy=test_records[1]) si.insert() si.submit() @@ -308,6 +310,7 @@ class TestSalesInvoice(unittest.TestCase): gl_entries = webnotes.conn.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc""", si.doc.name, as_dict=1) + self.assertTrue(gl_entries) expected_values = sorted([ @@ -325,19 +328,14 @@ class TestSalesInvoice(unittest.TestCase): # cancel si.cancel() - gle_count = webnotes.conn.sql("""select count(name) from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s - and ifnull(is_cancelled, 'No') = 'Yes' - order by account asc""", si.doc.name) + gle = webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s""", si.doc.name) - self.assertEquals(gle_count[0][0], 8) + self.assertFalse(gle) def test_pos_gl_entry_with_aii(self): - webnotes.conn.sql("delete from `tabStock Ledger Entry`") - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - - old_default_company = webnotes.conn.get_default("company") - webnotes.conn.set_default("company", "_Test Company") + self.clear_stock_account_balance() + set_perpetual_inventory() self._insert_purchase_receipt() self._insert_pos_settings() @@ -362,20 +360,19 @@ class TestSalesInvoice(unittest.TestCase): ["_Test Item", "_Test Warehouse - _TC", -1.0]) # check gl entries - stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company", - "stock_in_hand_account") - gl_entries = webnotes.conn.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc, debit asc""", si.doc.name, as_dict=1) self.assertTrue(gl_entries) + stock_in_hand = webnotes.conn.get_value("Account", {"master_name": "_Test Warehouse - _TC"}) + expected_gl_entries = sorted([ [si.doc.debit_to, 630.0, 0.0], [pos[1]["income_account"], 0.0, 500.0], [pos[2]["account_head"], 0.0, 80.0], [pos[3]["account_head"], 0.0, 50.0], - [stock_in_hand_account, 0.0, 75.0], + [stock_in_hand, 0.0, 75.0], [pos[1]["expense_account"], 75.0, 0.0], [si.doc.debit_to, 0.0, 600.0], ["_Test Account Bank Account - _TC", 600.0, 0.0] @@ -385,20 +382,22 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(expected_gl_entries[i][1], gle.debit) self.assertEquals(expected_gl_entries[i][2], gle.credit) + + # cancel si.cancel() - gl_count = webnotes.conn.sql("""select count(name) - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - and ifnull(is_cancelled, 'No') = 'Yes' - order by account asc, name asc""", si.doc.name) + gle = webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s""", si.doc.name) - self.assertEquals(gl_count[0][0], 16) - - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) - webnotes.conn.set_default("company", old_default_company) + self.assertFalse(gle) - def test_sales_invoice_gl_entry_with_aii_no_item_code(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + self.assertFalse(get_stock_and_account_difference([stock_in_hand])) + + set_perpetual_inventory(0) + + def test_sales_invoice_gl_entry_with_aii_no_item_code(self): + self.clear_stock_account_balance() + set_perpetual_inventory() si_copy = webnotes.copy_doclist(test_records[1]) si_copy[1]["item_code"] = None @@ -421,12 +420,12 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(expected_values[i][0], gle.account) self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) - - def test_sales_invoice_gl_entry_with_aii_non_stock_item(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + set_perpetual_inventory(0) + + def test_sales_invoice_gl_entry_with_aii_non_stock_item(self): + self.clear_stock_account_balance() + set_perpetual_inventory() si_copy = webnotes.copy_doclist(test_records[1]) si_copy[1]["item_code"] = "_Test Non Stock Item" si = webnotes.bean(si_copy) @@ -449,7 +448,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(expected_values[i][1], gle.debit) self.assertEquals(expected_values[i][2], gle.credit) - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + set_perpetual_inventory(0) def _insert_purchase_receipt(self): from stock.doctype.purchase_receipt.test_purchase_receipt import test_records \ @@ -643,9 +642,14 @@ class TestSalesInvoice(unittest.TestCase): return new_si # if yearly, test 3 repetitions, else test 13 repetitions - count = no_of_months == 12 and 3 or 13 + count = 3 if no_of_months == 12 else 13 for i in xrange(count): base_si = _test(i) + + def clear_stock_account_balance(self): + webnotes.conn.sql("delete from `tabStock Ledger Entry`") + webnotes.conn.sql("delete from tabBin") + webnotes.conn.sql("delete from `tabGL Entry`") def test_serialized(self): from stock.doctype.stock_entry.test_stock_entry import make_serialized_item diff --git a/accounts/doctype/sales_invoice_item/sales_invoice_item.txt b/accounts/doctype/sales_invoice_item/sales_invoice_item.txt index 057f166d75c..07cdc547e0b 100644 --- a/accounts/doctype/sales_invoice_item/sales_invoice_item.txt +++ b/accounts/doctype/sales_invoice_item/sales_invoice_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-06-04 11:02:19", "docstatus": 0, - "modified": "2013-07-25 16:32:10", + "modified": "2013-08-29 16:58:56", "modified_by": "Administrator", "owner": "Administrator" }, @@ -416,17 +416,6 @@ "print_hide": 1, "read_only": 1 }, - { - "doctype": "DocField", - "fieldname": "buying_amount", - "fieldtype": "Currency", - "hidden": 1, - "label": "Buying Amount", - "no_copy": 1, - "options": "Company:company:default_currency", - "print_hide": 1, - "read_only": 1 - }, { "allow_on_submit": 1, "doctype": "DocField", diff --git a/accounts/general_ledger.py b/accounts/general_ledger.py index c35e31e0db0..995fdc061b1 100644 --- a/accounts/general_ledger.py +++ b/accounts/general_ledger.py @@ -5,17 +5,35 @@ from __future__ import unicode_literals import webnotes from webnotes.utils import flt, cstr, now from webnotes.model.doc import Document +from webnotes import msgprint, _ +from accounts.utils import validate_expense_against_budget + + +class StockAccountInvalidTransaction(webnotes.ValidationError): pass def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'): + if gl_map: + if not cancel: + gl_map = process_gl_map(gl_map, merge_entries) + save_entries(gl_map, adv_adj, update_outstanding) + else: + delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) + +def process_gl_map(gl_map, merge_entries=True): if merge_entries: gl_map = merge_similar_entries(gl_map) - if cancel: - set_as_cancel(gl_map[0]["voucher_type"], gl_map[0]["voucher_no"]) + for entry in gl_map: + # round off upto 2 decimal + entry.debit = flt(entry.debit, 2) + entry.credit = flt(entry.credit, 2) + + # toggle debit, credit if negative entry + if flt(entry.debit) < 0 or flt(entry.credit) < 0: + entry.debit, entry.credit = abs(flt(entry.credit)), abs(flt(entry.debit)) - check_budget(gl_map, cancel) - save_entries(gl_map, cancel, adv_adj, update_outstanding) + return gl_map def merge_similar_entries(gl_map): merged_gl_map = [] @@ -24,79 +42,86 @@ def merge_similar_entries(gl_map): # to that entry same_head = check_if_in_list(entry, merged_gl_map) if same_head: - same_head['debit'] = flt(same_head['debit']) + flt(entry['debit']) - same_head['credit'] = flt(same_head['credit']) + flt(entry['credit']) + same_head.debit = flt(same_head.debit) + flt(entry.debit) + same_head.credit = flt(same_head.credit) + flt(entry.credit) else: merged_gl_map.append(entry) - + + # filter zero debit and credit entries + merged_gl_map = filter(lambda x: flt(x.debit)!=0 or flt(x.credit)!=0, merged_gl_map) return merged_gl_map -def check_if_in_list(gle, gl_mqp): - for e in gl_mqp: - if e['account'] == gle['account'] and \ +def check_if_in_list(gle, gl_map): + for e in gl_map: + if e.account == gle.account and \ cstr(e.get('against_voucher'))==cstr(gle.get('against_voucher')) \ and cstr(e.get('against_voucher_type')) == \ cstr(gle.get('against_voucher_type')) \ and cstr(e.get('cost_center')) == cstr(gle.get('cost_center')): return e -def check_budget(gl_map, cancel): - for gle in gl_map: - if gle.get('cost_center'): - #check budget only if account is expense account - acc_details = webnotes.conn.get_value("Account", gle['account'], - ['is_pl_account', 'debit_or_credit']) - if acc_details[0]=="Yes" and acc_details[1]=="Debit": - webnotes.get_obj('Budget Control').check_budget(gle, cancel) - -def save_entries(gl_map, cancel, adv_adj, update_outstanding): +def save_entries(gl_map, adv_adj, update_outstanding): + validate_account_for_auto_accounting_for_stock(gl_map) + total_debit = total_credit = 0.0 - def _swap(gle): - gle.debit, gle.credit = abs(flt(gle.credit)), abs(flt(gle.debit)) - for entry in gl_map: - gle = Document('GL Entry', fielddata=entry) + make_entry(entry, adv_adj, update_outstanding) + # check against budget + validate_expense_against_budget(entry) - # round off upto 2 decimal - gle.debit = flt(gle.debit, 2) - gle.credit = flt(gle.credit, 2) - - # toggle debit, credit if negative entry - if flt(gle.debit) < 0 or flt(gle.credit) < 0: - _swap(gle) - - # toggled debit/credit in two separate condition because - # both should be executed at the - # time of cancellation when there is negative amount (tax discount) - if cancel: - _swap(gle) - - gle_obj = webnotes.get_obj(doc=gle) - # validate except on_cancel - if not cancel: - gle_obj.validate() - - # save - gle.save(1) - gle_obj.on_update(adv_adj, cancel, update_outstanding) # update total debit / credit - total_debit += flt(gle.debit) - total_credit += flt(gle.credit) + total_debit += flt(entry.debit) + total_credit += flt(entry.credit) - # print gle.account, gle.debit, gle.credit, total_debit, total_credit - - if not cancel: - validate_total_debit_credit(total_debit, total_credit) + validate_total_debit_credit(total_debit, total_credit) + +def make_entry(args, adv_adj, update_outstanding): + args.update({"doctype": "GL Entry"}) + gle = webnotes.bean([args]) + gle.ignore_permissions = 1 + gle.insert() + gle.run_method("on_update_with_args", adv_adj, update_outstanding) + gle.submit() def validate_total_debit_credit(total_debit, total_credit): if abs(total_debit - total_credit) > 0.005: - webnotes.msgprint("""Debit and Credit not equal for - this voucher: Diff (Debit) is %s""" % - (total_debit - total_credit), raise_exception=1) - -def set_as_cancel(voucher_type, voucher_no): - webnotes.conn.sql("""update `tabGL Entry` set is_cancelled='Yes', - modified=%s, modified_by=%s - where voucher_type=%s and voucher_no=%s""", - (now(), webnotes.session.user, voucher_type, voucher_no)) \ No newline at end of file + webnotes.throw(_("Debit and Credit not equal for this voucher: Diff (Debit) is ") + + cstr(total_debit - total_credit)) + +def validate_account_for_auto_accounting_for_stock(gl_map): + if gl_map[0].voucher_type=="Journal Voucher": + aii_accounts = [d[0] for d in webnotes.conn.sql("""select name from tabAccount + where account_type = 'Warehouse' and ifnull(master_name, '')!=''""")] + + for entry in gl_map: + if entry.account in aii_accounts: + webnotes.throw(_("Account") + ": " + entry.account + + _(" can only be debited/credited through Stock transactions"), + StockAccountInvalidTransaction) + + +def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, + adv_adj=False, update_outstanding="Yes"): + + from accounts.doctype.gl_entry.gl_entry import check_negative_balance, \ + check_freezing_date, update_outstanding_amt, validate_frozen_account + + if not gl_entries: + gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) + if gl_entries: + check_freezing_date(gl_entries[0]["posting_date"], adv_adj) + + webnotes.conn.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", + (voucher_type or gl_entries[0]["voucher_type"], voucher_no or gl_entries[0]["voucher_no"])) + + for entry in gl_entries: + validate_frozen_account(entry["account"], adv_adj) + check_negative_balance(entry["account"], adv_adj) + validate_expense_against_budget(entry) + + if entry.get("against_voucher") and entry.get("against_voucher_type") != "POS" \ + and update_outstanding == 'Yes': + update_outstanding_amt(entry["account"], entry.get("against_voucher_type"), + entry.get("against_voucher"), on_cancel=True) \ No newline at end of file diff --git a/accounts/page/voucher_import_tool/voucher_import_tool.py b/accounts/page/voucher_import_tool/voucher_import_tool.py index 416cf0e0237..9425afc7d0f 100644 --- a/accounts/page/voucher_import_tool/voucher_import_tool.py +++ b/accounts/page/voucher_import_tool/voucher_import_tool.py @@ -104,7 +104,7 @@ def import_vouchers(common_values, data, start_idx, import_type): if account.master_name: map_fields(["against_sales_invoice:against_invoice", - "against_purhase_invoice:against_voucher", + "against_purchase_invoice:against_voucher", "against_journal_voucher:against_jv"], d, detail.fields) webnotes.conn.commit() diff --git a/accounts/report/accounts_payable/accounts_payable.py b/accounts/report/accounts_payable/accounts_payable.py index 20702fd0a21..d9a0ca2a4ca 100644 --- a/accounts/report/accounts_payable/accounts_payable.py +++ b/accounts/report/accounts_payable/accounts_payable.py @@ -73,7 +73,7 @@ def get_gl_entries(filters, before_report_date=True): conditions, supplier_accounts = get_conditions(filters, before_report_date) gl_entries = [] gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` - where ifnull(is_cancelled, 'No') = 'No' %s order by posting_date, account""" % + where docstatus < 2 %s order by posting_date, account""" % (conditions), tuple(supplier_accounts), as_dict=1) return gl_entries @@ -126,7 +126,7 @@ def get_outstanding_amount(gle, report_date): select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) from `tabGL Entry` where account = %s and posting_date <= %s and against_voucher_type = %s - and against_voucher = %s and name != %s and ifnull(is_cancelled, 'No') = 'No'""", + and against_voucher = %s and name != %s""", (gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0] outstanding_amount = flt(gle.credit) - flt(gle.debit) - flt(payment_amount) diff --git a/accounts/report/accounts_receivable/accounts_receivable.py b/accounts/report/accounts_receivable/accounts_receivable.py index 3ae222328c5..86a2475359b 100644 --- a/accounts/report/accounts_receivable/accounts_receivable.py +++ b/accounts/report/accounts_receivable/accounts_receivable.py @@ -65,7 +65,7 @@ def get_columns(): def get_gl_entries(filters, upto_report_date=True): conditions, customer_accounts = get_conditions(filters, upto_report_date) return webnotes.conn.sql("""select * from `tabGL Entry` - where ifnull(is_cancelled, 'No') = 'No' %s order by posting_date, account""" % + where docstatus < 2 %s order by posting_date, account""" % (conditions), tuple(customer_accounts), as_dict=1) def get_conditions(filters, upto_report_date=True): @@ -116,7 +116,7 @@ def get_outstanding_amount(gle, report_date): select sum(ifnull(credit, 0)) - sum(ifnull(debit, 0)) from `tabGL Entry` where account = %s and posting_date <= %s and against_voucher_type = %s - and against_voucher = %s and name != %s and ifnull(is_cancelled, 'No') = 'No'""", + and against_voucher = %s and name != %s""", (gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0] return flt(gle.debit) - flt(gle.credit) - flt(payment_amount) @@ -130,7 +130,7 @@ def get_payment_amount(gle, report_date, entries_after_report_date): payment_amount = webnotes.conn.sql(""" select sum(ifnull(credit, 0)) - sum(ifnull(debit, 0)) from `tabGL Entry` where account = %s and posting_date <= %s and against_voucher_type = %s - and against_voucher = %s and name != %s and ifnull(is_cancelled, 'No') = 'No'""", + and against_voucher = %s and name != %s""", (gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0] return flt(payment_amount) diff --git a/accounts/report/budget_variance_report/budget_variance_report.py b/accounts/report/budget_variance_report/budget_variance_report.py index 015e2c01b31..ee4f6fe20a9 100644 --- a/accounts/report/budget_variance_report/budget_variance_report.py +++ b/accounts/report/budget_variance_report/budget_variance_report.py @@ -87,7 +87,7 @@ def get_actual_details(filters): return webnotes.conn.sql("""select gl.account, gl.debit, gl.credit, gl.cost_center, MONTHNAME(gl.posting_date) as month_name from `tabGL Entry` gl, `tabBudget Detail` bd - where gl.fiscal_year=%s and company=%s and is_cancelled='No' + where gl.fiscal_year=%s and company=%s and bd.account=gl.account""" % ('%s', '%s'), (filters.get("fiscal_year"), filters.get("company")), as_dict=1) diff --git a/accounts/report/gross_profit/gross_profit.py b/accounts/report/gross_profit/gross_profit.py index 590babbf6bc..d9c20d5ccc7 100644 --- a/accounts/report/gross_profit/gross_profit.py +++ b/accounts/report/gross_profit/gross_profit.py @@ -52,8 +52,7 @@ def get_stock_ledger_entries(filters): query = """select item_code, voucher_type, voucher_no, voucher_detail_no, posting_date, posting_time, stock_value, warehouse, actual_qty as qty - from `tabStock Ledger Entry` - where ifnull(`is_cancelled`, "No") = "No" """ + from `tabStock Ledger Entry`""" if filters.get("company"): query += """ and company=%(company)s""" diff --git a/accounts/utils.py b/accounts/utils.py index 9beaac76a69..5c6c16b76a9 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import nowdate, cstr, flt, now +from webnotes.utils import nowdate, nowtime, cstr, flt, now, getdate, add_months from webnotes.model.doc import addchild from webnotes import msgprint, _ from webnotes.utils import formatdate @@ -12,6 +12,8 @@ from utilities import build_filter_conditions class FiscalYearError(webnotes.ValidationError): pass +class BudgetError(webnotes.ValidationError): pass + def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1): return get_fiscal_years(date, fiscal_year, label, verbose=1)[0] @@ -91,15 +93,10 @@ def get_balance_on(account=None, date=None): else: cond.append("""gle.account = "%s" """ % (account, )) - # join conditional conditions - cond = " and ".join(cond) - if cond: - cond += " and " - bal = webnotes.conn.sql(""" SELECT sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) FROM `tabGL Entry` gle - WHERE %s ifnull(is_cancelled, 'No') = 'No' """ % (cond, ))[0][0] + WHERE %s""" % " and ".join(cond))[0][0] # if credit account, it should calculate credit - debit if bal and acc.debit_or_credit == 'Credit': @@ -236,8 +233,7 @@ def remove_against_link_from_jv(ref_type, ref_no, against_field): set against_voucher_type=null, against_voucher=null, modified=%s, modified_by=%s where against_voucher_type=%s and against_voucher=%s - and voucher_no != ifnull(against_voucher, "") - and ifnull(is_cancelled, "No")="No" """, + and voucher_no != ifnull(against_voucher, '')""", (now(), webnotes.session.user, ref_type, ref_no)) @webnotes.whitelist() @@ -250,79 +246,6 @@ def get_company_default(company, fieldname): _("' in Company: ") + company), raise_exception=True) return value - -def create_stock_in_hand_jv(reverse=False): - from webnotes.utils import nowdate - today = nowdate() - fiscal_year = get_fiscal_year(today)[0] - jv_list = [] - - for company in webnotes.conn.sql_list("select name from `tabCompany`"): - stock_rbnb_value = get_stock_rbnb_value(company) - stock_rbnb_value = reverse and -1*stock_rbnb_value or stock_rbnb_value - if stock_rbnb_value: - jv = webnotes.bean([ - { - "doctype": "Journal Voucher", - "naming_series": "JV-AUTO-", - "company": company, - "posting_date": today, - "fiscal_year": fiscal_year, - "voucher_type": "Journal Entry", - "user_remark": (_("Auto Inventory Accounting") + ": " + - (_("Disabled") if reverse else _("Enabled")) + ". " + - _("Journal Entry for inventory that is received but not yet invoiced")) - }, - { - "doctype": "Journal Voucher Detail", - "parentfield": "entries", - "account": get_company_default(company, "stock_received_but_not_billed"), - (stock_rbnb_value > 0 and "credit" or "debit"): abs(stock_rbnb_value) - }, - { - "doctype": "Journal Voucher Detail", - "parentfield": "entries", - "account": get_company_default(company, "stock_adjustment_account"), - (stock_rbnb_value > 0 and "debit" or "credit"): abs(stock_rbnb_value), - "cost_center": get_company_default(company, "stock_adjustment_cost_center") - }, - ]) - jv.insert() - - jv_list.append(jv.doc.name) - - if jv_list: - msgprint(_("Following Journal Vouchers have been created automatically") + \ - ":\n%s" % ("\n".join([("%s" % (jv, jv)) for jv in jv_list]),)) - - msgprint(_("""These adjustment vouchers book the difference between \ - the total value of received items and the total value of invoiced items, \ - as a required step to use Auto Inventory Accounting. - This is an approximation to get you started. - You will need to submit these vouchers after checking if the values are correct. - For more details, read: \ - \ - Auto Inventory Accounting""")) - - webnotes.msgprint("""Please refresh the system to get effect of Auto Inventory Accounting""") - - -def get_stock_rbnb_value(company): - total_received_amount = webnotes.conn.sql("""select sum(valuation_rate*qty*conversion_factor) - from `tabPurchase Receipt Item` pr_item where docstatus=1 - and exists(select name from `tabItem` where name = pr_item.item_code - and is_stock_item='Yes') - and exists(select name from `tabPurchase Receipt` - where name = pr_item.parent and company = %s)""", company) - - total_billed_amount = webnotes.conn.sql("""select sum(valuation_rate*qty*conversion_factor) - from `tabPurchase Invoice Item` pi_item where docstatus=1 - and exists(select name from `tabItem` where name = pi_item.item_code - and is_stock_item='Yes') - and exists(select name from `tabPurchase Invoice` - where name = pi_item.parent and company = %s)""", company) - return flt(total_received_amount[0][0]) - flt(total_billed_amount[0][0]) - def fix_total_debit_credit(): vouchers = webnotes.conn.sql("""select voucher_type, voucher_no, @@ -338,4 +261,91 @@ def fix_total_debit_credit(): webnotes.conn.sql("""update `tabGL Entry` set %s = %s + %s where voucher_type = %s and voucher_no = %s and %s > 0 limit 1""" % (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr), - (d.diff, d.voucher_type, d.voucher_no)) \ No newline at end of file + (d.diff, d.voucher_type, d.voucher_no)) + +def get_stock_and_account_difference(account_list=None, posting_date=None): + from stock.utils import get_stock_balance_on + + if not posting_date: posting_date = nowdate() + + difference = {} + + account_warehouse = dict(webnotes.conn.sql("""select name, master_name from tabAccount + where account_type = 'Warehouse' and ifnull(master_name, '') != '' + and name in (%s)""" % ', '.join(['%s']*len(account_list)), account_list)) + + for account, warehouse in account_warehouse.items(): + account_balance = get_balance_on(account, posting_date) + stock_value = get_stock_balance_on(warehouse, posting_date) + if abs(flt(stock_value) - flt(account_balance)) > 0.005: + difference.setdefault(account, flt(stock_value) - flt(account_balance)) + + return difference + +def validate_expense_against_budget(args): + args = webnotes._dict(args) + if webnotes.conn.get_value("Account", {"name": args.account, "is_pl_account": "Yes", + "debit_or_credit": "Debit"}): + budget = webnotes.conn.sql(""" + select bd.budget_allocated, cc.distribution_id + from `tabCost Center` cc, `tabBudget Detail` bd + where cc.name=bd.parent and cc.name=%s and account=%s and bd.fiscal_year=%s + """, (args.cost_center, args.account, args.fiscal_year), as_dict=True) + + if budget and budget[0].budget_allocated: + yearly_action, monthly_action = webnotes.conn.get_value("Company", args.company, + ["yearly_bgt_flag", "monthly_bgt_flag"]) + action_for = action = "" + + if monthly_action in ["Stop", "Warn"]: + budget_amount = get_allocated_budget(budget[0].distribution_id, + args.posting_date, args.fiscal_year, budget[0].budget_allocated) + + args["month_end_date"] = webnotes.conn.sql("select LAST_DAY(%s)", + args.posting_date)[0][0] + action_for, action = "Monthly", monthly_action + + elif yearly_action in ["Stop", "Warn"]: + budget_amount = budget[0].budget_allocated + action_for, action = "Monthly", yearly_action + + if action_for: + actual_expense = get_actual_expense(args) + if actual_expense > budget_amount: + webnotes.msgprint(action_for + _(" budget ") + cstr(budget_amount) + + _(" for account ") + args.account + _(" against cost center ") + + args.cost_center + _(" will exceed by ") + + cstr(actual_expense - budget_amount) + _(" after this transaction.") + , raise_exception=BudgetError if action=="Stop" else False) + +def get_allocated_budget(distribution_id, posting_date, fiscal_year, yearly_budget): + if distribution_id: + distribution = {} + for d in webnotes.conn.sql("""select bdd.month, bdd.percentage_allocation + from `tabBudget Distribution Detail` bdd, `tabBudget Distribution` bd + where bdd.parent=bd.name and bd.fiscal_year=%s""", fiscal_year, as_dict=1): + distribution.setdefault(d.month, d.percentage_allocation) + + dt = webnotes.conn.get_value("Fiscal Year", fiscal_year, "year_start_date") + budget_percentage = 0.0 + + while(dt <= getdate(posting_date)): + if distribution_id: + budget_percentage += distribution.get(getdate(dt).strftime("%B"), 0) + else: + budget_percentage += 100.0/12 + + dt = add_months(dt, 1) + + return yearly_budget * budget_percentage / 100 + +def get_actual_expense(args): + args["condition"] = " and posting_date<='%s'" % args.month_end_date \ + if args.get("month_end_date") else "" + + return webnotes.conn.sql(""" + select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) + from `tabGL Entry` + where account='%(account)s' and cost_center='%(cost_center)s' + and fiscal_year='%(fiscal_year)s' and company='%(company)s' %(condition)s + """ % (args))[0][0] \ No newline at end of file diff --git a/buying/doctype/purchase_order/purchase_order.py b/buying/doctype/purchase_order/purchase_order.py index c41b9dfef88..3389f7419a5 100644 --- a/buying/doctype/purchase_order/purchase_order.py +++ b/buying/doctype/purchase_order/purchase_order.py @@ -88,6 +88,7 @@ class DocType(BuyingController): def update_bin(self, is_submit, is_stopped = 0): + from stock.utils import update_bin pc_obj = get_obj('Purchase Common') for d in getlist(self.doclist, 'po_details'): #1. Check if is_stock_item == 'Yes' @@ -122,12 +123,13 @@ class DocType(BuyingController): # Update ordered_qty and indented_qty in bin args = { - "item_code" : d.item_code, - "ordered_qty" : (is_submit and 1 or -1) * flt(po_qty), - "indented_qty" : (is_submit and 1 or -1) * flt(ind_qty), + "item_code": d.item_code, + "warehouse": d.warehouse, + "ordered_qty": (is_submit and 1 or -1) * flt(po_qty), + "indented_qty": (is_submit and 1 or -1) * flt(ind_qty), "posting_date": self.doc.transaction_date } - get_obj("Warehouse", d.warehouse).update_bin(args) + update_bin(args) def check_modified_date(self): mod_db = webnotes.conn.sql("select modified from `tabPurchase Order` where name = '%s'" % self.doc.name) diff --git a/buying/doctype/quality_inspection/quality_inspection.js b/buying/doctype/quality_inspection/quality_inspection.js index 8c7c9328a2e..f3517b457cb 100644 --- a/buying/doctype/quality_inspection/quality_inspection.js +++ b/buying/doctype/quality_inspection/quality_inspection.js @@ -1,11 +1,6 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. // License: GNU General Public License v3. See license.txt -cur_frm.cscript.item_code = function(doc, cdt, cdn) { - if (doc.item_code) - return get_server_fields('get_purchase_receipt_item_details','','',doc,cdt,cdn,1); -} - cur_frm.cscript.inspection_type = function(doc, cdt, cdn) { if(doc.inspection_type == 'Incoming'){ doc.delivery_note_no = ''; @@ -30,13 +25,22 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; // item code based on GRN/DN cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) { - var filter = {}; - if (doc.purchase_receipt_no) filter['parent'] = doc.purchase_receipt_no; - - else if (doc.delivery_note_no) filter['parent'] = doc.delivery_note_no; - - return{ - filters: filter + if (doc.purchase_receipt_no) { + return { + query: "buying.doctype.quality_inspection.quality_inspection.item_query", + filters: { + "from": "Purchase Receipt Item", + "parent": doc.purchase_receipt_no + } + } + } else if (doc.delivery_note_no) { + return { + query: "buying.doctype.quality_inspection.quality_inspection.item_query", + filters: { + "from": "Delivery Note Item", + "parent": doc.delivery_note_no + } + } } } diff --git a/buying/doctype/quality_inspection/quality_inspection.py b/buying/doctype/quality_inspection/quality_inspection.py index 19b2d2aabef..3380bd6e1fc 100644 --- a/buying/doctype/quality_inspection/quality_inspection.py +++ b/buying/doctype/quality_inspection/quality_inspection.py @@ -33,3 +33,17 @@ class DocType: webnotes.conn.sql("update `tabPurchase Receipt Item` t1, `tabPurchase Receipt` t2 set t1.qa_no = '', t2.modified = '%s' \ where t1.parent = '%s' and t1.item_code = '%s' and t1.parent = t2.name" \ % (self.doc.modified, self.doc.purchase_receipt_no, self.doc.item_code)) + + +def item_query(doctype, txt, searchfield, start, page_len, filters): + if filters.get("from"): + from webnotes.widgets.reportview import get_match_cond + filters.update({ + "txt": txt, + "mcond": get_match_cond(filters["from"], searchfield), + "start": start, + "page_len": page_len + }) + return webnotes.conn.sql("""select item_code from `tab%(from)s` + where parent='%(parent)s' and docstatus < 2 and item_code like '%%%(txt)s%%' %(mcond)s + order by item_code limit %(start)s, %(page_len)s""" % filters) \ No newline at end of file diff --git a/buying/utils.py b/buying/utils.py index f4fb2f3ff87..ce81b6bd67b 100644 --- a/buying/utils.py +++ b/buying/utils.py @@ -98,8 +98,8 @@ def _get_price_list_rate(args, item_bean, meta): from utilities.transaction_base import validate_currency validate_currency(args, item_bean.doc, meta) - out.import_ref_rate = \ - flt(price_list_rate[0].ref_rate * args.plc_conversion_rate / args.conversion_rate) + out.import_ref_rate = flt(price_list_rate[0].ref_rate) * \ + flt(args.plc_conversion_rate) / flt(args.conversion_rate) # if not found, fetch from last purchase transaction if not out.import_ref_rate: diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py index 1247e668c09..927b24980ff 100644 --- a/controllers/accounts_controller.py +++ b/controllers/accounts_controller.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import webnotes from webnotes import _, msgprint from webnotes.utils import flt, cint, today, cstr -from setup.utils import get_company_currency, get_price_list_currency +from setup.utils import get_company_currency from accounts.utils import get_fiscal_year, validate_fiscal_year from utilities.transaction_base import TransactionBase, validate_conversion_rate import json @@ -13,7 +13,6 @@ import json class AccountsController(TransactionBase): def validate(self): self.set_missing_values(for_validate=True) - self.validate_date_with_fiscal_year() if self.meta.get_field("currency"): self.calculate_taxes_and_totals() @@ -21,7 +20,7 @@ class AccountsController(TransactionBase): self.set_total_in_words() self.validate_for_freezed_account() - + def set_missing_values(self, for_validate=False): for fieldname in ["posting_date", "transaction_date"]: if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname): @@ -54,35 +53,38 @@ class AccountsController(TransactionBase): self.doc.doctype + _(" can not be made."), raise_exception=1) def set_price_list_currency(self, buying_or_selling): - company_currency = get_company_currency(self.doc.company) - fieldname = buying_or_selling.lower() + "_price_list" - - # TODO - change this, since price list now has only one currency allowed - if self.meta.get_field(fieldname) and self.doc.fields.get(fieldname): - self.doc.fields.update(get_price_list_currency(self.doc.fields.get(fieldname))) + if self.meta.get_field("currency"): + company_currency = get_company_currency(self.doc.company) + + # price list part + fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \ + else "buying_price_list" + if self.meta.get_field(fieldname) and self.doc.fields.get(fieldname): + if not self.doc.price_list_currency: + self.doc.price_list_currency = webnotes.conn.get_value("Price List", + self.doc.fields.get(fieldname), "currency") - if self.doc.price_list_currency: if self.doc.price_list_currency == company_currency: self.doc.plc_conversion_rate = 1.0 - elif not self.doc.plc_conversion_rate or \ - (flt(self.doc.plc_conversion_rate)==1 and company_currency!= self.doc.price_list_currency): - exchange = self.doc.price_list_currency + "-" + company_currency - self.doc.plc_conversion_rate = flt(webnotes.conn.get_value("Currency Exchange", - exchange, "exchange_rate")) - - if not self.doc.currency: - self.doc.currency = self.doc.price_list_currency - self.doc.conversion_rate = self.doc.plc_conversion_rate - - if self.meta.get_field("currency"): - if self.doc.currency and self.doc.currency != company_currency: - if not self.doc.conversion_rate: - exchange = self.doc.currency + "-" + company_currency - self.doc.conversion_rate = flt(webnotes.conn.get_value("Currency Exchange", - exchange, "exchange_rate")) - else: - self.doc.conversion_rate = 1 - + + elif not self.doc.plc_conversion_rate: + self.doc.plc_conversion_rate = self.get_exchange_rate( + self.doc.price_list_currency, company_currency) + + # currency + if not self.doc.currency: + self.doc.currency = self.doc.price_list_currency + self.doc.conversion_rate = self.doc.plc_conversion_rate + elif self.doc.currency == company_currency: + self.doc.conversion_rate = 1.0 + elif not self.doc.conversion_rate: + self.doc.conversion_rate = self.get_exchange_rate(self.doc.currency, + company_currency) + + def get_exchange_rate(self, from_currency, to_currency): + exchange = "%s-%s" % (from_currency, to_currency) + return flt(webnotes.conn.get_value("Currency Exchange", exchange, "exchange_rate")) + def set_missing_item_details(self, get_item_details): """set missing item values""" for item in self.doclist.get({"parentfield": self.fname}): @@ -335,24 +337,20 @@ class AccountsController(TransactionBase): self.calculate_outstanding_amount() - def get_gl_dict(self, args, cancel=None): + def get_gl_dict(self, args): """this method populates the common properties of a gl entry record""" - if cancel is None: - cancel = (self.doc.docstatus == 2) - - gl_dict = { + gl_dict = webnotes._dict({ 'company': self.doc.company, 'posting_date': self.doc.posting_date, 'voucher_type': self.doc.doctype, 'voucher_no': self.doc.name, 'aging_date': self.doc.fields.get("aging_date") or self.doc.posting_date, 'remarks': self.doc.remarks, - 'is_cancelled': cancel and "Yes" or "No", 'fiscal_year': self.doc.fiscal_year, 'debit': 0, 'credit': 0, 'is_opening': self.doc.fields.get("is_opening") or "No", - } + }) gl_dict.update(args) return gl_dict @@ -407,20 +405,17 @@ class AccountsController(TransactionBase): def get_company_default(self, fieldname): from accounts.utils import get_company_default return get_company_default(self.doc.company, fieldname) - - @property - def stock_items(self): - if not hasattr(self, "_stock_items"): - self._stock_items = [] - item_codes = list(set(item.item_code for item in - self.doclist.get({"parentfield": self.fname}))) - if item_codes: - self._stock_items = [r[0] for r in webnotes.conn.sql("""select name - from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \ - (", ".join((["%s"]*len(item_codes))),), item_codes)] + def get_stock_items(self): + stock_items = [] + item_codes = list(set(item.item_code for item in + self.doclist.get({"parentfield": self.fname}))) + if item_codes: + stock_items = [r[0] for r in webnotes.conn.sql("""select name + from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \ + (", ".join((["%s"]*len(item_codes))),), item_codes)] - return self._stock_items + return stock_items @property def company_abbr(self): diff --git a/controllers/buying_controller.py b/controllers/buying_controller.py index 043099a70df..7e49e60f8a1 100644 --- a/controllers/buying_controller.py +++ b/controllers/buying_controller.py @@ -62,7 +62,7 @@ class BuyingController(StockController): raise_exception=WrongWarehouseCompany) def validate_stock_or_nonstock_items(self): - if not self.stock_items: + if not self.get_stock_items(): tax_for_valuation = [d.account_head for d in self.doclist.get({"parentfield": "purchase_tax_details"}) if d.category in ["Valuation", "Valuation and Total"]] diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index 5605ccf3a25..f1117ed177b 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -23,7 +23,7 @@ class SellingController(StockController): self.set_price_list_and_item_details() if self.doc.fields.get("__islocal"): self.set_taxes("other_charges", "charge") - + def set_missing_lead_customer_details(self): if self.doc.customer: if not (self.doc.contact_person and self.doc.customer_address and self.doc.customer_name): @@ -83,46 +83,6 @@ class SellingController(StockController): if self.meta.get_field("in_words_export"): self.doc.in_words_export = money_in_words(disable_rounded_total and self.doc.grand_total_export or self.doc.rounded_total_export, self.doc.currency) - - def set_buying_amount(self, stock_ledger_entries = None): - from stock.utils import get_buying_amount, get_sales_bom_buying_amount - if not stock_ledger_entries: - stock_ledger_entries = self.get_stock_ledger_entries() - - item_sales_bom = {} - for d in self.doclist.get({"parentfield": "packing_details"}): - new_d = webnotes._dict(d.fields.copy()) - new_d.total_qty = -1 * d.qty - item_sales_bom.setdefault(d.parent_item, []).append(new_d) - - if stock_ledger_entries: - for item in self.doclist.get({"parentfield": self.fname}): - if item.item_code in self.stock_items or \ - (item_sales_bom and item_sales_bom.get(item.item_code)): - - buying_amount = 0 - if item.item_code in self.stock_items: - buying_amount = get_buying_amount(self.doc.doctype, self.doc.name, - item.name, stock_ledger_entries.get((item.item_code, - item.warehouse), [])) - elif item_sales_bom and item_sales_bom.get(item.item_code): - buying_amount = get_sales_bom_buying_amount(item.item_code, item.warehouse, - self.doc.doctype, self.doc.name, item.name, stock_ledger_entries, - item_sales_bom) - - # buying_amount >= 0.01 so that gl entry doesn't get created for such small amounts - item.buying_amount = buying_amount >= 0.01 and buying_amount or 0 - webnotes.conn.set_value(item.doctype, item.name, "buying_amount", - item.buying_amount) - - def check_expense_account(self, item): - if item.buying_amount and not item.expense_account: - msgprint(_("""Expense account is mandatory for item: """) + item.item_code, - raise_exception=1) - - if item.buying_amount and not item.cost_center: - msgprint(_("""Cost Center is mandatory for item: """) + item.item_code, - raise_exception=1) def calculate_taxes_and_totals(self): self.other_fname = "other_charges" diff --git a/controllers/stock_controller.py b/controllers/stock_controller.py index 1aeca1bc733..d4c92a90a7e 100644 --- a/controllers/stock_controller.py +++ b/controllers/stock_controller.py @@ -3,39 +3,237 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cint +from webnotes.utils import cint, flt, cstr +from webnotes import msgprint, _ import webnotes.defaults + from controllers.accounts_controller import AccountsController +from accounts.general_ledger import make_gl_entries, delete_gl_entries class StockController(AccountsController): - def get_gl_entries_for_stock(self, against_stock_account, amount, - stock_in_hand_account=None, cost_center=None): - if not stock_in_hand_account: - stock_in_hand_account = self.get_company_default("stock_in_hand_account") - if not cost_center: - cost_center = self.get_company_default("stock_adjustment_cost_center") + def make_gl_entries(self): + if not cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")): + return - if amount: - gl_entries = [ - # stock in hand account - self.get_gl_dict({ - "account": stock_in_hand_account, - "against": against_stock_account, - "debit": amount, - "remarks": self.doc.remarks or "Accounting Entry for Stock", - }, self.doc.docstatus == 2), - - # account against stock in hand - self.get_gl_dict({ - "account": against_stock_account, - "against": stock_in_hand_account, - "credit": amount, - "cost_center": cost_center or None, - "remarks": self.doc.remarks or "Accounting Entry for Stock", - }, self.doc.docstatus == 2), - ] + warehouse_account = self.get_warehouse_account() + + if self.doc.docstatus==1: + gl_entries = self.get_gl_entries_for_stock(warehouse_account) + make_gl_entries(gl_entries) + else: + delete_gl_entries(voucher_type=self.doc.doctype, voucher_no=self.doc.name) + + self.update_gl_entries_after(warehouse_account) + + def get_gl_entries_for_stock(self, warehouse_account=None, default_expense_account=None, + default_cost_center=None): + from accounts.general_ledger import process_gl_map + if not warehouse_account: + warehouse_account = self.get_warehouse_account() + + stock_ledger = self.get_stock_ledger_details() + voucher_details = self.get_voucher_details(stock_ledger, default_expense_account, + default_cost_center) + + gl_list = [] + warehouse_with_no_account = [] + for detail in voucher_details: + sle_list = stock_ledger.get(detail.name) + if sle_list: + for sle in sle_list: + if warehouse_account.get(sle.warehouse): + # from warehouse account + gl_list.append(self.get_gl_dict({ + "account": warehouse_account[sle.warehouse], + "against": detail.expense_account, + "cost_center": detail.cost_center, + "remarks": self.doc.remarks or "Accounting Entry for Stock", + "debit": sle.stock_value_difference + })) + + # to target warehouse / expense account + gl_list.append(self.get_gl_dict({ + "account": detail.expense_account, + "against": warehouse_account[sle.warehouse], + "cost_center": detail.cost_center, + "remarks": self.doc.remarks or "Accounting Entry for Stock", + "credit": sle.stock_value_difference + })) + elif sle.warehouse not in warehouse_with_no_account: + warehouse_with_no_account.append(sle.warehouse) + + if warehouse_with_no_account: + msgprint(_("No accounting entries for following warehouses") + ": \n" + + "\n".join(warehouse_with_no_account)) + + return process_gl_map(gl_list) - return gl_entries + def get_voucher_details(self, stock_ledger, default_expense_account, default_cost_center): + if not default_expense_account: + details = self.doclist.get({"parentfield": self.fname}) + for d in details: + self.check_expense_account(d) + else: + details = [webnotes._dict({ + "name":d, + "expense_account": default_expense_account, + "cost_center": default_cost_center + }) for d in stock_ledger.keys()] + + return details + + def get_stock_ledger_details(self): + stock_ledger = {} + for sle in webnotes.conn.sql("""select warehouse, stock_value_difference, voucher_detail_no + from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""", + (self.doc.doctype, self.doc.name), as_dict=True): + stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle) + return stock_ledger + + def get_warehouse_account(self): + for d in webnotes.conn.sql("select name from tabWarehouse"): + webnotes.bean("Warehouse", d[0]).save() + + warehouse_account = dict(webnotes.conn.sql("""select master_name, name from tabAccount + where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) + return warehouse_account + + def update_gl_entries_after(self, warehouse_account=None): + from accounts.utils import get_stock_and_account_difference + future_stock_vouchers = self.get_future_stock_vouchers() + gle = self.get_voucherwise_gl_entries(future_stock_vouchers) + if not warehouse_account: + warehouse_account = self.get_warehouse_account() + + for voucher_type, voucher_no in future_stock_vouchers: + existing_gle = gle.get((voucher_type, voucher_no), []) + voucher_obj = webnotes.get_obj(voucher_type, voucher_no) + expected_gle = voucher_obj.get_gl_entries_for_stock(warehouse_account) + + if expected_gle: + matched = True + if existing_gle: + for entry in expected_gle: + for e in existing_gle: + if entry.account==e.account \ + and entry.against_account==e.against_account\ + and entry.cost_center==e.cost_center: + if entry.debit != e.debit or entry.credit != e.credit: + matched = False + break + else: + matched = False + + if not matched: + self.delete_gl_entries(voucher_type, voucher_no) + make_gl_entries(expected_gle) + else: + self.delete_gl_entries(voucher_type, voucher_no) + + + def get_future_stock_vouchers(self): + future_stock_vouchers = [] + + if hasattr(self, "fname"): + item_list = [d.item_code for d in self.doclist.get({"parentfield": self.fname})] + condition = ''.join(['and item_code in (\'', '\', \''.join(item_list) ,'\')']) + else: + condition = "" + + for d in webnotes.conn.sql("""select distinct sle.voucher_type, sle.voucher_no + from `tabStock Ledger Entry` sle + where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s + order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" % + ('%s', '%s', condition), (self.doc.posting_date, self.doc.posting_time), + as_dict=True): + future_stock_vouchers.append([d.voucher_type, d.voucher_no]) + + return future_stock_vouchers + + def get_voucherwise_gl_entries(self, future_stock_vouchers): + gl_entries = {} + if future_stock_vouchers: + for d in webnotes.conn.sql("""select * from `tabGL Entry` + where posting_date >= %s and voucher_no in (%s)""" % + ('%s', ', '.join(['%s']*len(future_stock_vouchers))), + tuple([self.doc.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): + gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) + + return gl_entries + + def delete_gl_entries(self, voucher_type, voucher_no): + webnotes.conn.sql("""delete from `tabGL Entry` + where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) + + def make_adjustment_entry(self, expected_gle, voucher_obj): + from accounts.utils import get_stock_and_account_difference + account_list = [d.account for d in expected_gle] + acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date) + + cost_center = self.get_company_default("cost_center") + stock_adjustment_account = self.get_company_default("stock_adjustment_account") + + gl_entries = [] + for account, diff in acc_diff.items(): + if diff: + gl_entries.append([ + # stock in hand account + voucher_obj.get_gl_dict({ + "account": account, + "against": stock_adjustment_account, + "debit": diff, + "remarks": "Adjustment Accounting Entry for Stock", + }), + + # account against stock in hand + voucher_obj.get_gl_dict({ + "account": stock_adjustment_account, + "against": account, + "credit": diff, + "cost_center": cost_center or None, + "remarks": "Adjustment Accounting Entry for Stock", + }), + ]) + + if gl_entries: + from accounts.general_ledger import make_gl_entries + make_gl_entries(gl_entries) + + def check_expense_account(self, item): + if item.fields.has_key("expense_account") and not item.expense_account: + msgprint(_("""Expense/Difference account is mandatory for item: """) + item.item_code, + raise_exception=1) + + if item.fields.has_key("expense_account") and not item.cost_center: + msgprint(_("""Cost Center is mandatory for item: """) + item.item_code, + raise_exception=1) + + def get_sl_entries(self, d, args): + sl_dict = { + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": self.doc.posting_date, + "posting_time": self.doc.posting_time, + "voucher_type": self.doc.doctype, + "voucher_no": self.doc.name, + "voucher_detail_no": d.name, + "actual_qty": (self.doc.docstatus==1 and 1 or -1)*flt(d.stock_qty), + "stock_uom": d.stock_uom, + "incoming_rate": 0, + "company": self.doc.company, + "fiscal_year": self.doc.fiscal_year, + "batch_no": cstr(d.batch_no).strip(), + "serial_no": d.serial_no, + "project": d.project_name, + "is_cancelled": self.doc.docstatus==2 and "Yes" or "No" + } + + sl_dict.update(args) + return sl_dict + + def make_sl_entries(self, sl_entries, is_amended=None): + from stock.stock_ledger import make_sl_entries + make_sl_entries(sl_entries, is_amended) def get_stock_ledger_entries(self, item_list=None, warehouse_list=None): out = {} @@ -47,8 +245,7 @@ class StockController(AccountsController): res = webnotes.conn.sql("""select item_code, voucher_type, voucher_no, voucher_detail_no, posting_date, posting_time, stock_value, warehouse, actual_qty as qty from `tabStock Ledger Entry` - where ifnull(`is_cancelled`, "No") = "No" and company = %s - and item_code in (%s) and warehouse in (%s) + where company = %s and item_code in (%s) and warehouse in (%s) order by item_code desc, warehouse desc, posting_date desc, posting_time desc, name desc""" % ('%s', ', '.join(['%s']*len(item_list)), ', '.join(['%s']*len(warehouse_list))), @@ -74,6 +271,5 @@ class StockController(AccountsController): def make_cancel_gl_entries(self): if webnotes.conn.sql("""select name from `tabGL Entry` where voucher_type=%s - and voucher_no=%s and ifnull(is_cancelled, 'No')='No'""", - (self.doc.doctype, self.doc.name)): + and voucher_no=%s""", (self.doc.doctype, self.doc.name)): self.make_gl_entries() \ No newline at end of file diff --git a/docs/docs.user.stock.accounting_for_stock.md b/docs/docs.user.stock.accounting_for_stock.md new file mode 100644 index 00000000000..bf91fe5ce24 --- /dev/null +++ b/docs/docs.user.stock.accounting_for_stock.md @@ -0,0 +1,42 @@ +--- +{ + "_label": "Accounting of Inventory / Stock" +} +--- + +The value of available inventory is treated as an Asset in company's Chart of Accounts. Depending on the type of items, it can be treated as Fixed Asset or Current Asset. To prepare Balance Sheet, you should make the accounting entries for those assets. +There are generally two different methods of accounting for inventory: + + +### **Auto / Perpetual Inventory** + +In this process, for each stock transactions, the system posts relevant accounting entries to sync stock balance and accounting balance. This is the default setting in ERPNext for new accounts. + +When you buy and receive items, those items are booked as the company’s assets (stock-in-hand / fixed-assets). When you sell and deliver those items, an expense (cost-of-goods-sold) equal to the buying cost of the items is booked. General Ledger entries are made after every stock transaction. As a result, the value as per Stock Ledger always remains same with the relevant account balance. This improves accuracy of Balance Sheet and Profit and Loss statement. + +To check accounting entries for a particular stock transaction, please check [**examples**](docs.user.stock.perpetual_inventory.html) + +#### **Advantages** + +Perpetual Inventory system will make it easier for you to maintain accuracy of company's asset and expense values. Stock balances will always be synced with relevant account balances, so no more periodic manual entry has to be done to balance them. + +In case of new back-dated stock transactions or cancellation/amendment of an existing transaction, all the future Stock Ledger entries and GL Entries will be recalculated for all items of that transaction. +The same is applicable if any cost is added to the submitted Purchase Receipt, later through the Landed Cost Wizard. + +>Note: Perpetual Inventory totally depends upon the item valuation rate. Hence, you have to be more careful entering valuation rate while making any incoming stock transactions like Purchase Receipt, Material Receipt, or Manufacturing / Repack. + +- + +### **Periodic Inventory** + +In this method, accounting entries are manually created periodically, to sync stock balance and relevant account balance. The system does not create accounting entries automatically for assets, at the time of material purchases or sales. + +In an accounting period, when you buy and receive items, an expense is booked in your accounting system. You sell and deliver some of these items. + +At the end of an accounting period, the total value of items to be sold, need to be booked as the company’s assets, often known as stock-in-hand. + +The difference between the value of the items remaining to be sold and the previous period’s stock-in-hand value can be positive or negative. If positive, this value is removed from expenses (cost-of-goods-sold) and is added to assets (stock-in-hand / fixed-assets). If negative, a reverse entry is passed. + +This complete process is called Periodic Inventory. + +If you are an existing user using Periodic Inventory and want to use Perpetual Inventory, you have to follow some steps to migrate. For details, check [**Migration From Periodic Inventory**](docs.user.stock.perpetual_inventory.html) diff --git a/docs/docs.user.stock.perpetual_inventory.md b/docs/docs.user.stock.perpetual_inventory.md new file mode 100644 index 00000000000..eb10326ecb4 --- /dev/null +++ b/docs/docs.user.stock.perpetual_inventory.md @@ -0,0 +1,313 @@ +--- +{ + "_label": "Perpetual Inventory" +} +--- + +In perpetual inventory, system creates accounting entries for each stock transactions, so that stock and account balance will always remain same. The account balance will be posted against their respective account heads for each Warehouse. On saving of a Warehouse, the system will automatically create an account head with the same name as warehouse. As account balance is maintained for each Warehouse, you should create Warehouses, based on the type of items (Current / Fixed Assets) it stores. + +At the time of items received in a particular warehouse, the balance of asset account (linked to that warehouse) will be increased. Similarly when you deliver some items from that warehouse, an expense will be booked and the asset account will be reduced, based on the valuation amount of those items. + + +## **Activation** + +1. Setup the following default accounts for each Company + - Stock Received But Not Billed + - Stock Adjustment Account + - Expenses Included In Valuation + - Cost Center + +2. In perpetual inventory, the system will maintain seperate account balance for each warehouse under separate account head. To create that account head, enter "Create Account Under" in Warehouse master. + +3. Activate Perpetual Inventory +> Setup > Accounts Settings > Make Accounting Entry For Every Stock Movement + + +- + +## **Example** + +Consider following Chart of Accounts and Warehouse setup for your company: + +#### Chart of Accounts + +- Assets (Dr) + - Current Assets + - Accounts Receivable + - Jane Doe + - Stock Assets + - Stores + - Finished Goods + - Work In Progress + - Tax Assets + - VAT + - Fixed Assets + - Fixed Asset Warehouse +- Liabilities (Cr) + - Current Liabilities + - Accounts Payable + - East Wind Inc. + - Stock Liabilities + - Stock Received But Not Billed + - Tax Liabilities + - Service Tax +- Income (Cr) + - Direct Income + - Sales Account +- Expenses (Dr) + - Direct Expenses + - Stock Expenses + - Cost of Goods Sold + - Expenses Included In Valuation + - Stock Adjustment + - Shipping Charges + - Customs Duty + +#### Warehouse - Account Configuration + +- Stores +- Work In Progress +- Finished Goods +- Fixed Asset Warehouse + +### **Purchase Receipt** + +Suppose you have purchased *10 nos* of item "RM0001" at *$200* and *5 nos* of item "Desktop" at **$100** from supplier "East Wind Inc". Following are the details of Purchase Receipt: + +Supplier: East Wind Inc. + +Items: + + + + + + + + + + + + + + + + +
ItemWarehouseQtyRateAmountValuation Amount
RM0001Stores1020020002200
DesktopFixed Asset Warehouse5100500550
+ +**Taxes:** + + + + + + + + + + +
AccountAmountCategory
Shipping Charges100Total and Valuation
VAT120Total
Customs Duty150Valuation
+ +**Stock Ledger** + +![pr_stock_ledger](img/accounting-for-stock-2.png) + +**General Ledger** + +![pr_general_ledger](img/accounting-for-stock-3.png) + +As stock balance increases through Purchase Receipt, "Store" and "Fixed Asset Warehouse" accounts are debited and a temporary account "Stock Receipt But Not Billed" account is credited, to maintain double entry accounting system. + + +-- + +### **Purchase Invoice** + +On receiving Bill from supplier, for the above Purchase Receipt, you will make Purchase Invoice for the same. The general ledger entries are as follows: + +**General Ledger** + +![pi_general_ledger](img/accounting-for-stock-4.png) + + +Here "Stock Received But Not Billed" account is debited and nullified the effect of Purchase Receipt. "Expenses Included In Valuation" account has been credited which ensures the valuation expense accounts are not booked (debited) twice (in Purchase Invoice and Delivery Note). + +-- + +### **Delivery Note** + +Lets say, you have an order from "Jane Doe" to deliver 5 nos of item "RM0001" at $300. Following are the details of Delivery Note: + +**Customer:** Jane Doe + +**Items:** + + + + + + + +
ItemWarehouseQtyRateAmount
RM0001Stores53001500
+ +**Taxes:** + + + + + + + + + +
AccountAmount
Service Tax150
VAT100
+ + +**Stock Ledger** + +![dn_stock_ledger](img/accounting-for-stock-5.png) + +**General Ledger** + +![dn_general_ledger](img/accounting-for-stock-6.png) + +As item is delivered from "Stores" warehouse, "Stores" account is credited and equal amount is debited to the expense account "Cost of Goods Sold". The debit/credit amount is equal to the total valuation amount (buying cost) of the selling items. And valuation amount is calculated based on your prefferred valuation method (FIFO / Moving Average) or actual cost of serialized items. +
+	
+In this example, we have considered valuation method as FIFO. 
+Valuation Rate 	= Purchase Rate + Charges Included in Valuation 
+				= 200 + (250 * (2000 / 2500) / 10) 
+				= 220
+Total Valuation Amount 	= 220 * 5 
+						= 1100
+	
+
+ +-- + +### **Sales Invoice with Update Stock** + +Lets say, you did not make Delivery Note against the above order and instead you have made Sales Invoice directly, with "Update Stock" options. The details of the Sales Invoice are same as the above Delivery Note. + +**Stock Ledger** + +![si_stock_ledger](img/accounting-for-stock-7.png) + +**General Ledger** + +![si_general_ledger](img/accounting-for-stock-8.png) + +Here, apart from normal account entries for invoice, "Stores" and "Cost of Goods Sold" accounts are also affected based on the valuation amount. + +-- + +### **Stock Entry (Material Receipt)** + +**Items:** + + + + + + + +
ItemTarget WarehouseQtyRateAmount
RM0001Stores5022011000
+ +**Stock Ledger** + +![mr_stock_ledger](img/accounting-for-stock-9.png) + +**General Ledger** + +![mr_stock_ledger](img/accounting-for-stock-10.png) + +-- + +### **Stock Entry (Material Issue)** + +**Items:** + + + + + + + +
ItemSource WarehouseQtyRateAmount
RM0001Stores102202200
+ +**Stock Ledger** + +![mi_stock_ledger](img/accounting-for-stock-11.png) + +**General Ledger** + +![mi_stock_ledger](img/accounting-for-stock-12.png) + +-- + +### **Stock Entry (Material Transfer)** + +**Items:** + + + + + + + + + +
ItemSource WarehouseTarget WarehouseQtyRateAmount
RM0001StoresWork In Progress102202200
+ +**Stock Ledger** + +![mtn_stock_ledger](img/accounting-for-stock-13.png) + +**General Ledger** + +![mtn_general_ledger](img/accounting-for-stock-14.png) + +-- + +### **Stock Entry (Sales Return - Sales Invoice booked)** + +**Items:** + + + + + + + +
ItemTarget WarehouseQtyRateAmount
RM0001Stores2200400
+ +**Stock Ledger** + +![sret_stock_ledger](img/accounting-for-stock-15.png) + +**General Ledger** + +![sret_general_ledger](img/accounting-for-stock-16.png) + + +-- + +### **Stock Entry (Purchase Return)** + +**Items:** + + + + + + + +
ItemSource WarehouseQtyRateAmount
RM0001Stores4220880
+ +**Stock Ledger** + +![pret_stock_ledger](img/accounting-for-stock-17.png) + +**General Ledger** + +![pret_general_ledger](img/accounting-for-stock-18.png) diff --git a/docs/user/accounts/docs.user.accounts.md b/docs/user/accounts/docs.user.accounts.md index 21a43e3693d..01ce42500bc 100644 --- a/docs/user/accounts/docs.user.accounts.md +++ b/docs/user/accounts/docs.user.accounts.md @@ -15,6 +15,9 @@ } --- +![Accounts](img/accounts-image.png) + + At end of the sales and purchase cycle is billing and payments. You may have an accountant in your team, or you may be doing accounting yourself or you may have outsourced your accounting. Financial accounting forms the core of any business management system like an ERP. diff --git a/docs/user/accounts/docs.user.accounts.opening_entry.md b/docs/user/accounts/docs.user.accounts.opening_entry.md new file mode 100644 index 00000000000..f8d0841acf6 --- /dev/null +++ b/docs/user/accounts/docs.user.accounts.opening_entry.md @@ -0,0 +1,10 @@ +--- +{ + "_label": "Opening Entry" +} +--- + +If you are a new company you can start using ERPNext accounting module by going to chart of accounts. + +However, if you are migrating from a legacy accounting system like Tally or a Fox Pro based software, please visit [Opening Entry](docs.user.setup.opening.html) + diff --git a/docs/user/accounts/docs.user.accounts.opening_stock.md b/docs/user/accounts/docs.user.accounts.opening_stock.md new file mode 100644 index 00000000000..3ba5025747a --- /dev/null +++ b/docs/user/accounts/docs.user.accounts.opening_stock.md @@ -0,0 +1,21 @@ +--- +{ + "_label": "Opening Stock" +} +--- + + +You can upload your opening stock in the system using Stock Reconciliation. Stock Reconciliation will update your stock for a given Item on a given date for a given Warehouse to the given quantity. + +Stock Reconciliation will make a “difference entry” (the difference between the system stock and the actual stock in your file) for the Item. + +Tip: Stock Reconciliation can also be used to update the “value” of an Item. + +To make a Stock Reconciliation, go to: + +> Stock > Stock Reconciliation > New Stock Reconciliation + +and follow the steps mentioned on the page. + +![Stock Reconciliation](img/stock-reconciliation1.png) + diff --git a/docs/user/accounts/docs.user.accounts.purchase_invoice.md b/docs/user/accounts/docs.user.accounts.purchase_invoice.md index 32be689df28..9f43d4ba5dd 100644 --- a/docs/user/accounts/docs.user.accounts.purchase_invoice.md +++ b/docs/user/accounts/docs.user.accounts.purchase_invoice.md @@ -44,7 +44,7 @@ To see entries in your Purchase Invoice after you “Submit”, click on “View --- -#### Is a purchase an “Expense” or “Asset”? +#### Is purchase an “Expense” or an “Asset”? If the Item is consumed immediately on purchase, or if it is a service, then the purchase becomes an “Expense”. For example, a telephone bill or travel bill is an “Expense” - it is already consumed. diff --git a/docs/user/customize/docs.user.customize.custom_field.md b/docs/user/customize/docs.user.customize.custom_field.md index 1dd2340b0ce..a457f074cc1 100644 --- a/docs/user/customize/docs.user.customize.custom_field.md +++ b/docs/user/customize/docs.user.customize.custom_field.md @@ -7,6 +7,10 @@ A very common customization is adding of custom fields. You can add Custom Field > Setup > Custom Field > New Custom Field +![Custome Field](img/custom-field.png) + + + In the form: - Select the Document on which you want to add the Custom Field. diff --git a/docs/user/customize/docs.user.customize.custom_form.md b/docs/user/customize/docs.user.customize.custom_form.md index 2e649ae4a5d..6b37bd12ee5 100644 --- a/docs/user/customize/docs.user.customize.custom_form.md +++ b/docs/user/customize/docs.user.customize.custom_form.md @@ -9,9 +9,13 @@ You can Customize Forms by changing its layout, making certain fields mandatory, > Setup > Customize ERPNext > Customize Forms + +![Customize Forms](img/customize-form-1.png) + + Select the Form you want to customize and the fields table will be updated with the fields from that form. Here you can: -- Change field types (for e.g. you want to increase the number of decimal places, you can convert come fields from Float to Currency). +- Change field types (for e.g. you want to increase the number of decimal places, you can convert some fields from Float to Currency). - Change labels to suit your industry / language. - Make certain fields mandatory. - Hide certain fields. @@ -20,4 +24,12 @@ Select the Form you want to customize and the fields table will be updated with You can also allow attachments, set max number of attachments and set the default Print Format. + +![Customize Forms](img/customize-form-2.png) + + +
+ + + > Though we want you to do everything you can to customize your ERP based on your business needs, we recommend that you do not make “wild” changes to the forms. This is because, these changes may affect certain operations and may mess up your forms. Make small changes and see its effect before doing some more. \ No newline at end of file diff --git a/docs/user/customize/docs.user.customize.custom_scripts.md b/docs/user/customize/docs.user.customize.custom_scripts.md index 9b6c91fd087..d885ba5fc99 100644 --- a/docs/user/customize/docs.user.customize.custom_scripts.md +++ b/docs/user/customize/docs.user.customize.custom_scripts.md @@ -3,4 +3,10 @@ "_label": "Custom Scripts" } --- +If you wish to change any ERPNext form formats, you can do so by using Custom Scripts. For example, if you wish to add a submit button after saving, to a Lead form, you can do so by creating your own script. + +> Setup > Customization > Custom Script + + +![Custom Script](img/custom-script.png) diff --git a/docs/user/customize/docs.user.customize.hide_unhide.md b/docs/user/customize/docs.user.customize.hide_unhide.md index ff64b243173..8edf8f6c721 100644 --- a/docs/user/customize/docs.user.customize.hide_unhide.md +++ b/docs/user/customize/docs.user.customize.hide_unhide.md @@ -7,7 +7,10 @@ As you have seen from this manual that ERPNext contains tons of features which you may not use. We have observed that most users start with using 20% of the features, though a different 20%. To hide fields belonging to features you dont require, go to: -> Setup > Customize ERPNext > Disable Features. +> Setup > Tools > Hide/Unhide Features + +![Hide Features](img/hide-features.png) + Check / uncheck the features you want to use and refresh your page for the changes to take effect. @@ -17,6 +20,9 @@ Check / uncheck the features you want to use and refresh your page for the chang To hide modules (icons) from the home page, go to: -Setup > Customize ERPNext > Modules Setup +Setup > Tools> Modules Setup + +![Hide/Unhide Modules](img/hide-module.png) + > Note: Modules are automatically hidden for users that have no permissions on the documents within that module. For example, if a user has no permissions on Purchase Order, Purchase Request, Supplier, the “Buying” module will automatically be hidden. diff --git a/docs/user/customize/docs.user.customize.print_format.md b/docs/user/customize/docs.user.customize.print_format.md index f8970c40daa..eed52bde355 100644 --- a/docs/user/customize/docs.user.customize.print_format.md +++ b/docs/user/customize/docs.user.customize.print_format.md @@ -8,11 +8,18 @@ Print Formats are the layouts that are generated when you want to Print or Email - The auto-generated “Standard” Print Format: This type of format follows the same layout as the form and is generated automatically by ERPNext. - Based on the Print Format document. There are templates in HTML that will be rendered with data. -ERPNext comes with a number of pre-defined templates in three styles: Modern, Classic and Spartan. You modify these templates or create their own. Editing ERPNext templates is not allowed because they may be over-written in an upcoming release. +ERPNext comes with a number of pre-defined templates in three styles: Modern, Classic and Spartan. You modify these templates or create your own. Editing ERPNext templates is not allowed because they may be over-written in an upcoming release. To create your own versions, open an existing template from: -> Setup > Branding and Printing > Print Formats +> Setup > Printing > Print Formats + + +![Print Format](img/print-format.png) + +
+ + Select the type of Print Format you want to edit and click on the “Copy” button on the right column. A new Print Format will open up with “Is Standard” set as “No” and you can edit the Print Format. diff --git a/docs/user/docs.user.md b/docs/user/docs.user.md index 03c0a73d1c4..d15a573e1a2 100644 --- a/docs/user/docs.user.md +++ b/docs/user/docs.user.md @@ -15,7 +15,8 @@ "docs.user.projects", "docs.user.website", "docs.user.tools", - "docs.user.customize" + "docs.user.customize", + "docs.user.knowledge" ], "_no_toc": 1 } @@ -88,6 +89,9 @@ Contents 1. [Sales Return](docs.user.stock.sales_return.html) 1. [Purchase Return](docs.user.stock.purchase_return.html) 1. [Projected Quantity](docs.user.stock.projected_quantity.html) + 1. [Accounting for Stock](docs.user.stock.accounting_for_stock.html) + 1. [Perpetual Inventory](docs.user.stock.perpetual_inventory.html) + 1. [Migrate to Perpetual Inventory](docs.user.stock.migrate_to_perpetual.html) 1. [Accounts](docs.user.accounts.html) 1. [Chart of Accounts](docs.user.setup.accounting.html) 1. [Chart of Cost Centers](docs.user.setup.cost_centers.html) @@ -96,9 +100,9 @@ Contents 1. [Payment Entry](docs.user.accounts.payments.html) 1. [Journal Voucher](docs.user.accounts.journal_voucher.html) 1. [Opening Entry](docs.user.accounts.opening_entry.html) + 1. [Opening Stock](docs.user.accounts.opening_stock.html) 1. [Period Closing](docs.user.accounts.closing.html) 1. [Accounting Reports](docs.user.accounts.reports.html) - 1. [Upload Journal Entries in Bulk](docs.user.accounts.voucher_import.html) 1. [Point of Sale (POS) Invoice](docs.user.accounts.pos.html) 1. [Human Resources (HR)](docs.user.hr.html) 1. [HR Setup](docs.user.hr.setup.html) @@ -118,10 +122,11 @@ Contents 1. [Maintenance Schedule](docs.user.support.maintenance_schedule.html) 1. [Maintenance Visit](docs.user.support.maintenance_visit.html) 1. [Maintenance Schedule](docs.user.support.maintenance_schedule.html) + 1. [Newsletter](docs.user.support.newsletter.html) 1. [Projects](docs.user.projects.html) 1. [Projects](docs.user.projects.projects.html) 1. [Task](docs.user.projects.task.html) - 1. [Time Log](docs.user.projects.time_log.html) + 1. [Time Log](docs.user.projects.timelog.html) 1. [Website](docs.user.website.html) 1. [Setup](docs.user.website.setup.html) 1. [Web Pages](docs.user.website.web_page.html) @@ -133,9 +138,16 @@ Contents 1. [Calendar](docs.user.tools.calendar.html) 1. [Assignments](docs.user.tools.assignment.html) 1. [Tags](docs.user.tools.tags.html) + 1. [Forms](docs.user.tools.form_tools.html) + 1. [Messages](docs.user.tools.messages.html) + 1. [Notes](docs.user.tools.notes.html) 1. [Customize](docs.user.customize.html) 1. [Custom Fields](docs.user.customize.custom_field.html) 1. [Customize Form](docs.user.customize.custom_form.html) 1. [Hide / Unhide modules](docs.user.customize.hide_unhide.html) 1. [Print Formats](docs.user.customize.print_formats.html) - 1. [Custom Scripts](docs.user.customize.custom_scripts.html) \ No newline at end of file + 1. [Custom Scripts](docs.user.customize.custom_scripts.html) +1. [Knowledge Library](docs.user.knowledge.html) + 1. [Fiscal Year](docs.user.knowledge.fiscal_year.html) + 1. [Accounting Knowledge](docs.user.knowledge.accounting.html) + 1. [Accounting Entries](docs.user.knowledge.accounting_entries.html) diff --git a/docs/user/hr/docs.user.hr.payroll.md b/docs/user/hr/docs.user.hr.payroll.md index 598be00fab9..9da590d35d4 100644 --- a/docs/user/hr/docs.user.hr.payroll.md +++ b/docs/user/hr/docs.user.hr.payroll.md @@ -17,7 +17,7 @@ The Salary Structure represents how Salaries are calculated based on Earnings an ![Salary Structure](img/salary-structure.png) -s + ### In the Salary Structure, diff --git a/docs/user/intro/docs.user.implement.concepts.md b/docs/user/intro/docs.user.implement.concepts.md index 31b43581429..9fc4012a979 100644 --- a/docs/user/intro/docs.user.implement.concepts.md +++ b/docs/user/intro/docs.user.implement.concepts.md @@ -13,7 +13,7 @@ Before you start implementation, lets get familiar with the terminology that is This represents the Company records for which ERPNext is setup. With this same setup, you can create multiple Company records, each representing a different legal entity. The accounting for each Company will be different, but they will share the Customer, Supplier and Item records. -> Setup > Company > Companies +> Setup > Company #### Customer @@ -59,11 +59,11 @@ A list of all Communication with a Contact or Lead. All emails sent from the sys #### Price List -A table of sale price for an Item. An Item can have multiple prices based on Customer / Supplier or Territory etc.. +A Price List is a place where different rate plans can be stored. It’s a name you give to a set of Item Prices stored under a particular List. -> Selling > Setup > Price List +> Selling > Price List -> Buying > Setup > Price List +> Buying > Price List --- @@ -73,7 +73,7 @@ A table of sale price for an Item. An Item can have multiple prices based on Cus Represents a Financial Year or Accounting Year. You can operate multiple Fiscal Years at the same time. Each Fiscal Year has a start date and an end date and transactions can only be recorded in this period. When you “close” a fiscal year, it's balances are transferred as “opening” balances for the next fiscal year. -> Setup > Company > Fiscal Years +> Setup > Company > Fiscal Year #### Cost Center @@ -85,7 +85,7 @@ A Cost Center is like an Account, but the only difference is that its structure A document that contains General Ledger (GL) entries and the sum of Debits and Credits of those entries is the same. In ERPNext you can update Payments, Returns etc using Journal Vouchers. -> Accounts > Journal Vouchers +> Accounts > Journal Voucher #### Sales Invoice @@ -103,7 +103,7 @@ A bill sent by a Supplier for delivery of Items (goods or services). ERPNext allows you to book transactions in multiple currencies. There is only one currency for your book of accounts though. While posting your Invoices, payments in different currencies, the amount is converted to the default currency by the specified conversion rate. -> Setup > Company > Currencies +> Setup > Currency --- @@ -113,7 +113,7 @@ ERPNext allows you to book transactions in multiple currencies. There is only on A classification of Customers, usually based on market segment. -> Selling > Setup (sidebar) > Customer Group +> Selling > Setup > Customer Group #### Lead @@ -215,19 +215,19 @@ A unified table for all material movement from one warehouse to another. This is Update Stock of multiple Items from a spreadsheet (CSV) file. -> Stock > Stock Reconciliation (in sidebar) +> Stock > Stock Reconciliation #### Quality Inspection A note prepared to record certain parameters of an Item at the time of Receipt from Supplier, or Delivery to Customer. -> Stock > Tools > Quality Inspection +> Stock > Quality Inspection #### Item Group A classification of Item. -> Stock > Setup (sidebar) > Item Group +> Stock > Setup > Item Group --- @@ -249,7 +249,7 @@ A record of an approved or rejected request for leave. A type of leave (for example, Sick Leave, Maternity Leave etc.) -> Human Resource > Leave and Attendance (sidebar) > Leave Type +> Human Resource > Leave and Attendance > Leave Type #### Salary Manager @@ -357,7 +357,7 @@ A title that can be set on a transaction just for printing. For example, you wan Text of your terms of contract. -> Selling > Setup > Terms and Conditions Template +> Selling > Setup > Terms and Conditions #### Unit of Measure (UOM) diff --git a/docs/user/intro/docs.user.implement.md b/docs/user/intro/docs.user.implement.md index 59f2c498a12..79c8576d88f 100644 --- a/docs/user/intro/docs.user.implement.md +++ b/docs/user/intro/docs.user.implement.md @@ -7,6 +7,11 @@ ] } --- + +![Implementation](img/implementation-image.png) + + + We have seen dozens of ERP implementations over the past few years and we realize that successful implementation is a lot about intangibles and attitude. **ERPs are not required.** diff --git a/docs/user/knowledge/docs.user.knowledge.accounting.md b/docs/user/knowledge/docs.user.knowledge.accounting.md new file mode 100644 index 00000000000..17304984cb0 --- /dev/null +++ b/docs/user/knowledge/docs.user.knowledge.accounting.md @@ -0,0 +1,110 @@ +--- +{ + "_label": "Accounting Knowledge" +} +--- + +Chart of Accounts represents a tree like representation of all accounting heads, used to represent company's financial information. There are two types of accounts: Balance Sheet and Profit and Loss Statement accounts. Balance Sheet consists of Assets and Liabilities accounts and Profit and Loss Statement accounts consists of Incomes and Expenses. + +**Assets:** Bank and Cash balances, outstanding amounts of customers and all other assets are recorded here. + +**Liabilities:** All the company's liabilities like shareholder's capital, outstanding amount to be paid to suppliers, taxes to be paid to concerned to authorities are recorded under this group. + +**Income:** Income from direct/indirect sales. + +**Expenses:** All the expenses to run the business, like salaries, purchases, rent etc. are recorded here. + +### Debit and Credit + +Each of these accounts are either "Debit" or "Credit" type. Assets, Expenses are "Debit" accounts and Liabilities, Incomes are "Credit" accounts. + +Accounting Entries + +The balance of account can be increased / decreased, depending on account type and transaction type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account TypeTransaction TypeEffect on account balance
DebitDebitIncreases
DebitCreditDecreases
CreditCreditIncreases
CreditDebitDecreases
+ +### Double Entry + +This means that every accounting entry has two parts, one debit and one credit and must affect two separate accounts. If you add or deduct to one account, some other account somewhere else must also be affected. See the example below: + +1. Company sells a laptop worth 50000 to Customer A and delivers that with an invoice. + +As the company will receive a payment from customer, the customer is considered as an asset account. For booking income, company maintains an account called "Sales of Laptop". So, entries will be done in the following manner: + + + + + + + + + + + + + + + + + + + + + +
AccountDebitCredit
Customer A50000
Sales of Laptop50000
+ +Customer A has made the payment, so customer balance should decreased based on the paid amount, which will increase "Cash" balance. + + + + + + + + + + + + + + + + + + + + + +
AccountDebitCredit
Customer A50000
Cash50000
+ diff --git a/docs/user/knowledge/docs.user.knowledge.accounting_entries.md b/docs/user/knowledge/docs.user.knowledge.accounting_entries.md new file mode 100644 index 00000000000..05b197ddac9 --- /dev/null +++ b/docs/user/knowledge/docs.user.knowledge.accounting_entries.md @@ -0,0 +1,38 @@ +--- +{ + "_label": "Accounting Entries" +} +--- +The concept of accounting is explained with an example given below: +We will take a "Tea Stall" as a company and see how to book accounting entries for the business. + +- Mama (The Tea-stall owner) invests Rs 25000 to start the business. + +![A&L](img/assets-1.png) + + +Analysis: Mama invested 25000 in company, hoping to get some profit. In other words, company is liable to pay 25000 to Mama in the future. So, account "Mama" is a liability account and it is credited. Company's cash balance will be increased due to the investment, "Cash" is an asset to the company and it will debited. + +- The company needs equipments (Stove, teapot, cups etc) and raw materials (tea, sugar, milk etc) immediately. He decides to buy from the nearest general store "Super Bazaar" who is a friend so that he gets some credit. Equipments cost him 2800 and raw materials worth of 2200. He pays 2000 out of total cost 5000. + +![A&L](img/assets-2.png) + +Analysis: Equipments are "Fixed Assets" (because they have a long life) of the company and raw materials "Current Assets" (since they are used for day-to-day business), of the company. So, "Equipments" and "Stock in Hand" accounts have been debited to increase the value. He pays 2000, so "Cash" account will be reduced by that amount, hence credited and he is liable to pay 3000 to "Super Bazaar" later, so Super Bazaar will be credited by 3000. + +- Mama (who takes care of all entries) decides to book sales at the end of the every day, so that he can analyze daily sales. At the end of the very first day, the tea stall sells 325 cups of tea, which gives net sales of Rs. 1575. The owner happily books his first day sales. + +![A&L](img/assets-3.png) + +Analysis: Income has been booked in "Sales of Tea" account which has been credited to increase the value and the same amount will be debited to "Cash" account. Lets say, to make 325 cups of tea, it costs Rs. 800, so "Stock in Hand" will be reduced (Cr) by 800 and expense will be booked in "Cost of goods sold" account by same amount. + +At the end of the month, the company paid the rent amount of stall (5000) and salary of one employee (8000), who joined from the very first day. + +![A&L](img/assets-4.png) + +### Booking Profit + +As month progress, company purchased more raw materials for the business. After a month he books profit to balance the "Balance Sheet" and "Profit and Loss Statements" statements. Profit belongs to Mama and not the company hence its a liability for the company (it has to pay it to Mama). When the Balance Sheet is not balanced i.e. Debit is not equal to Credit, the profit has not yet been booked. To book profit, the following entry has to be made: + +![A&L](img/assets-5.png) + +Explanation: Company's net sales and expenses are 40000 and 20000 respectively. So, company made a profit of 20000. To make the profit booking entry, "Profit or Loss" account has been debited and "Capital Account" has been credited. Company's net cash balance is 44000 and there is some raw materials available worth 1000 rupees. diff --git a/docs/user/knowledge/docs.user.knowledge.fiscal_year.md b/docs/user/knowledge/docs.user.knowledge.fiscal_year.md new file mode 100644 index 00000000000..980872ca9b3 --- /dev/null +++ b/docs/user/knowledge/docs.user.knowledge.fiscal_year.md @@ -0,0 +1,10 @@ +--- +{ + "_label": "Fiscal-Year" +} +--- + +A fiscal year is also known as a financial year or a budget year. It is used for calculating financial statements in businesses and other organisations. The fiscal year may or may not be the same as a calendar year. For tax purposes, companies can choose to be calendar-year taxpayers or fiscal-year taxpayers. In many jurisdictions, regulatory laws regarding accounting and taxation require such reports once per twelve months. However, it is not mandatory that the period should be a calendar year (that is, 1 January to 31 December). + +A fiscal year usually starts at the beginning of a quarter, such as April 1, July 1 or October 1. However, most companies' fiscal year also coincides with the calendar year, which starts January 1. For the most part, it is simpler and easier that way. For some organizations, there are advantages in starting the fiscal year at a different time. For example, businesses that are seasonal might start their fiscal year on July 1 or October 1. A business that has most of its income in the fall and most of its expenses in the spring might also choose to start its fiscal year on October 1. That way, they know what their income will be for that year, and can adjust their expenses to maintain their desired profit margins. + diff --git a/docs/user/knowledge/docs.user.knowledge.md b/docs/user/knowledge/docs.user.knowledge.md new file mode 100644 index 00000000000..5d98cdd4596 --- /dev/null +++ b/docs/user/knowledge/docs.user.knowledge.md @@ -0,0 +1,11 @@ +--- +{ + "_label": "Knowledge Library", + "_toc": [ + "docs.user.knowledge.fiscal_year", + "docs.user.knowledge.accounting", + "docs.user.knowledge.accounting_entries" + ] +} +--- +Knowledge Library contains definitions and explanations of various management concepts. This page is created for users who wish to elaborate their conceptual knowledge. \ No newline at end of file diff --git a/docs/user/mfg/docs.user.mfg.bom.md b/docs/user/mfg/docs.user.mfg.bom.md index 7d1085de3bd..9436f869c2b 100644 --- a/docs/user/mfg/docs.user.mfg.bom.md +++ b/docs/user/mfg/docs.user.mfg.bom.md @@ -3,16 +3,26 @@ "_label": "Bill of Materials" } --- -At the heart of the Manufacturing system is the **Bill of Materials** (BOM). The **BOM** is a list of all material (either bought or made) and operations that go into a finished product or sub-Item. In ERPNext, the component could have its own BOM hence forming a tree of Items with multiple levels. +At the heart of the Manufacturing system is the **Bill of Materials** (BOM). The **BOM** is a list of all materials (either bought or made) and operations that go into a finished product or sub-Item. In ERPNext, the component could have its own BOM hence forming a tree of Items with multiple levels. -To make accurate Purchase Requests, you must always maintain your correct BOMs. To make a new BOM: +To make accurate Purchase Requests, you must always maintain correct BOMs. To make a new BOM: > Manufacturing > Bill of Materials > New BOM + +![Bill of Materials](img/bom.png) + + + In the BOM form: - Select the Item for which you want to make the BOM. - Add the operations that you have to go through to make that particular Item in the “Operations” table. For each operation, you will be asked to enter a Workstation. You must create new Workstations as and when necessary.
 + +![Bill of Materials with Operations](img/mfg-bom-3.png) + + + - Add the list of Items you require for each operation, with its quantity. This Item could be a purchased Item or a sub-assembly with its own BOM. If the row Item is a manufactured Item and has multiple BOMs, select the appropriate BOM.

You can also define if a part of the Item goes into scrap. Workstations are defined only for product costing purposes not inventory. Inventory is tracked in Warehouses not Workstations. diff --git a/docs/user/mfg/docs.user.mfg.md b/docs/user/mfg/docs.user.mfg.md index 84bf08d3782..5522ec8910f 100644 --- a/docs/user/mfg/docs.user.mfg.md +++ b/docs/user/mfg/docs.user.mfg.md @@ -8,13 +8,13 @@ ] } --- -The Manufacturing module in ERPNext helps you to maintain multi-level Bill of Materials (BOMs) for your Items. It helps you in Product Costing, planing your production via Production Plan, creating Production Orders for your manufacturing shop floor,s and planing your inventory by getting your material requirement via BOMs (also called Material Requirements Planning MRP). +The Manufacturing module in ERPNext helps you to maintain multi-level Bill of Materials (BOMs) for your Items. It helps you in Product Costing, planing your production via Production Plan, creating Production Orders for your manufacturing shop floors and planing your inventory by getting your material requirement via BOMs (also called Material Requirements Planning MRP). ### Types of Production Planning Broadly there are three types of Production Planning Systems -- Make-to-Stock: In these systems, production is planned based on a forecast and then the Items are sold to distributors or customers. All fast moving consumer goods that are sold in retail shops like soaps, packaged water etc and electronics like phones etc are Made to Stock. +- Make-to-Stock: In these systems, production is planned based on a forecast and the Items are then sold to distributors or customers. All fast moving consumer goods that are sold in retail shops like soaps, packaged water etc and electronics like phones etc are Made to Stock. - Make-to-Order: In these systems, manufacturing takes place after a firm order is placed by a Customer. - Engineer-to-Order: In this case each sale is a separate Project and has to be designed and engineered to the requirements of the Customer. Common examples of this are any custom business like furniture, machine tools, speciality devices, metal fabrication etc. @@ -26,7 +26,7 @@ For engineer-to-order systems, the Manufacturing module should be used along wit  You can track work-in-progress by creating work-in-progress Warehouses. -ERPNext will help you track material movement by automatically creating Stock Entries from your Production Orders by building form Bill of Materials. +ERPNext will help you track material movement by automatically creating Stock Entries from your Production Orders by building from Bill of Materials. --- @@ -35,7 +35,7 @@ ERPNext will help you track material movement by automatically creating Stock En The earliest ERP systems were made for manufacturing. The earliest adopters were automobile companies who had thousands of raw materials and sub-assemblies and found it very hard to keep track of requirements and plan purchases. They started using computers to build the material requirements from forecasts and Bill of Materials. -Later these systems were expanded to include Financial, Payroll, Order Processing and Purchasing and became the more generic Enterprise Resource Systems (ERP). More recently Customer Relationship Management (CRM) was added as a function and is now an integral part of ERP systems. +Later these systems were expanded to include Finances, Payroll, Order Processing, and Purchasing and thus became the more generic Enterprise Resource Systems (ERP). More recently Customer Relationship Management (CRM) was added as a function and is now an integral part of ERP systems. These days the term ERP is used to describe systems that help manage any kind of organization like education institutes (Education ERP) or Hospitals (Hospital ERP) and so on. @@ -43,7 +43,7 @@ These days the term ERP is used to describe systems that help manage any kind of ### Best Practice: Lean Manufacturing -The state of art manufacturing philosophy (the rationale behind the planning processes) comes from Japanese auto major Toyota. At the time when American manufacturers depended on MRP systems to plan their manufacturing based on their sales forecasts, they turned the problem on its head and discovered a leaner way of planning their production. They realized that: +The state of art manufacturing philosophy (the rationale behind the planning processes) comes from Japanese auto major Toyota. At the time when American manufacturers depended on MRP systems to plan their manufacturing based on their sales forecasts, they turned around the problem by discovering a leaner way of planning their production. They realized that: The biggest cause of wastage in manufacturing is variation (in product and quantity). @@ -53,6 +53,4 @@ Their card signaling system kanban, would notify all their suppliers to stop pro They combined this system with neatly managed factories with well labeled racks. -Like we discussed before, small manufacturing companies are usually make-to-order or engineer-to-order and can hardly afford to have a high level of standardization. But that should be the aim. Small manufacturing businesses should aim for repeatability by innovating processes so that there is a common platform for products. - - +Small manufacturing companies are usually make-to-order or engineer-to-order and can hardly afford to have a high level of standardization. Thus small manufacturing businesses should aim for repeatability by innovating processes and creating a common platform for products. \ No newline at end of file diff --git a/docs/user/mfg/docs.user.mfg.planning.md b/docs/user/mfg/docs.user.mfg.planning.md index 31de0712efc..8d43ab8142a 100644 --- a/docs/user/mfg/docs.user.mfg.planning.md +++ b/docs/user/mfg/docs.user.mfg.planning.md @@ -14,6 +14,11 @@ To use the Production Planning Tool, go to:
 > Manufacturing > Production Planning Tool + +![Production Planning Tool](img/production-planning-tool.png) + + + The Production Planning Tool is used in two stages: - Selection of Open Sales Orders for the period based on “Expected Delivery Date”. diff --git a/docs/user/mfg/docs.user.mfg.production_order.md b/docs/user/mfg/docs.user.mfg.production_order.md index 46e719c7ed3..5cbcf844284 100644 --- a/docs/user/mfg/docs.user.mfg.production_order.md +++ b/docs/user/mfg/docs.user.mfg.production_order.md @@ -9,6 +9,11 @@ The **Production Order** is generated directly from the **Production Planning To > Manufacturing > Production Order > New Production Order + +![Production Order](img/production-order.png) + + + - Select the Item to be produced (must have a Bill of Materials). - Select the BOM - Select Quantities @@ -19,9 +24,13 @@ and “Submit” the Production Order. Once you “Submit”, you will see two more buttons: -1. Make Transfer: This will create a Stock Entry with all the Items required to complete this Production Order to be added to the WIP Warehouse. (this will add sub-Items with BOM as one Item or explode their children based on your setting above).
 -1. Back Flush: This will create a Stock Entry that will deduct all the sub-Items from the WIP Warehouse and add them to the Finished Goods Warehouse. +![Production Order](img/production-order-2.png) -When you Back Flush your Items back to the Finished Goods Warehouse, the “Produced Quantity” will be updated in the Production Order. -> Tip: You can also partially complete a Production Order by updating the Finished Goods stock creating a Stock Entry and selecting “Back flush” as the type. \ No newline at end of file +1. Transfer Raw Material: This will create a Stock Entry with all the Items required to complete this Production Order to be added to the WIP Warehouse. (this will add sub-Items with BOM as one Item or explode their children based on your setting above).
 +1. Update Finished Goods: This will create a Stock Entry that will deduct all the sub-Items from the WIP Warehouse and add them to the Finished Goods Warehouse. + +> Tip: You can also partially complete a Production Order by updating the Finished Goods stock creating a Stock Entry. + +When you Update Finished Goods to the Finished Goods Warehouse, the “Produced Quantity” will be updated in the Production Order. + diff --git a/docs/user/selling/docs.user.selling.md b/docs/user/selling/docs.user.selling.md index 5c0a5ae5704..97945567240 100644 --- a/docs/user/selling/docs.user.selling.md +++ b/docs/user/selling/docs.user.selling.md @@ -17,6 +17,11 @@ ] } --- + +![Selling](img/selling-image.png) + + + Selling is the communication that happens with the customer prior to and during the sale. You might be managing all the communication yourself or you may have a small team of sales people to handle this. ERPNext helps you track the communication leading up to the sale, by keeping all your documents in an organized and searchable manner. ERPNext helps you track business **Opportunities** from **Leads** and **Customers**, send them **Quotations** and make confirmed **Sales Orders**. diff --git a/docs/user/selling/docs.user.selling.quotation.md b/docs/user/selling/docs.user.selling.quotation.md index d7743a2ff1e..ae20af6091a 100644 --- a/docs/user/selling/docs.user.selling.quotation.md +++ b/docs/user/selling/docs.user.selling.quotation.md @@ -9,11 +9,28 @@ To create a new Quotation go to: > Selling > Quotation > New Quotation +#### Step 1: Enter Customer Details and Order Type. +
![Quotation](img/quotation.png) +
+ +#### Step 2: Add Currency and Price List + +
+ +![Quotation](img/quotation-1.png) + + + +#### Step #: Enter Item and Tax Details. +
+ + +![Quotation](img/quotation-2.png) @@ -41,7 +58,8 @@ The rates you quote may depend on two things. ### Taxes -To add taxes to your Quotation, you can either select a tax template, Sales Taxes and Charges Master or add the taxes on your own. +To add taxes to your Quotation, you can either select a tax template, Sales Taxes and Charges Master or add the taxes on your own. To understand taxes in detail visit [Taxes](docs.user.setup.taxes.html) + You can add taxes in the same manner as the Sales Taxes and Charges Master. @@ -50,7 +68,7 @@ You can add taxes in the same manner as the Sales Taxes and Charges Master. Each Quotation must ideally contain a set of terms, of your contract. It is usually a good idea to make templates of your Terms and Conditions, so that you have a standard set of terms. You can do this by going to: -> Selling > Terms and Conditions (right sidebar) +> Selling > Terms and Conditions #### What should Terms and Conditions Contain? diff --git a/docs/user/setup/docs.user.setup.cost_centers.md b/docs/user/setup/docs.user.setup.cost_centers.md index e4841d87959..3df26e16a0b 100644 --- a/docs/user/setup/docs.user.setup.cost_centers.md +++ b/docs/user/setup/docs.user.setup.cost_centers.md @@ -23,12 +23,19 @@ You may not have shipping expenses for your walk-in customers, and no shop-rent Thus when you do your analysis you get a better understanding as to which side of your business is doing better. Since ERPNext has an option to add multiple Companies, you can create Cost Centers for each Company and manage it separately. +To understand chart of cost centers in detail visit [Accounting Knowledge](docs.user.knowledge.accounting.html) + + ### Chart of Cost Centers To setup your Chart of Cost Centers go to: > Accounts > Chart of Cost Centers + +![Chart of Cost Center](img/chart-of-cost-centers.png) + + Cost centers help you in one more activity, budgeting. ### Budgeting diff --git a/docs/user/setup/docs.user.setup.first.md b/docs/user/setup/docs.user.setup.first.md index 5e1c5d87e0a..4784a691233 100644 --- a/docs/user/setup/docs.user.setup.first.md +++ b/docs/user/setup/docs.user.setup.first.md @@ -11,7 +11,7 @@ This form will create your first **Company** and **Fiscal Year** (accounting or Some definitions: -- Fiscal Year: This is your financial year, the fiscal year usually starts on 1st Jan or 1st March for most regions. If you are not sure, consult an accountant. +- [Fiscal Year](docs.user.knowledge.fiscal_year.html): This is your financial year, the fiscal year usually starts on 1st Jan or 1st March for most regions. If you are not sure, consult an accountant. - Abbreviation: In a multi-company setup, the abbreviation is appended to each account, so keep it small (2-4) characters and all caps. This will also set the default **Currency** and time zone for your account. Once you complete this, your first **Company** and **Chart of Accounts** will be created. diff --git a/docs/user/setup/docs.user.setup.md b/docs/user/setup/docs.user.setup.md index bd5c499745a..fa9680a0629 100644 --- a/docs/user/setup/docs.user.setup.md +++ b/docs/user/setup/docs.user.setup.md @@ -25,6 +25,7 @@ ] } --- + Setting up an ERP system is like starting your business all over again, although in the virtual world. Thankfully it is not as hard as the real business and you get to do a trial too! Implementation requires the implementer to take a step back and set aside some time to do this right. This is usually not a couple-of-hours, after-work kind of a project. diff --git a/docs/user/setup/docs.user.setup.opening.md b/docs/user/setup/docs.user.setup.opening.md index dcb2320a9cd..4c30306b052 100644 --- a/docs/user/setup/docs.user.setup.opening.md +++ b/docs/user/setup/docs.user.setup.opening.md @@ -36,8 +36,26 @@ Note: Make sure to set “Is Opening” as “Yes” in the More Info section. > Setup > Opening Accounts and Stock > Opening Accounting Entries. + +#### Step 1: Select Opening Entry in the Voucher Type section. + ![Opening Entry](img/opening-entry.png) +
+ +#### Step 2: Complete Journal Entries on the Debit and Credit side. + + +![Opening Entry](img/opening-entry-1.png) + +
+ + +#### Step 2: Select Yes in the "Is Opening " record under More Info. + + +![Opening Entry](img/opening-entry-2.png) + You can make two Opening Journal Vouchers: @@ -45,6 +63,13 @@ You can make two Opening Journal Vouchers: - For all assets (excluding Accounts Receivables): This entry will contain all your assets except the amounts you are expecting from your Customers against outstanding Sales Invoices. You will have to update your receivables by making an individual entry for each Invoice (this is because, the system will help you track the invoices which are yet to be paid). Since all the entries in this voucher will be of type “Debit”, you can credit the sum of all these debits against the “Temp Opening Liabilities” account. - For all liabilities: Similarly you need to pass a Journal Voucher for your Opening Liabilities (except for the bills you have to pay). This can be made against the “Temp Opening Assets” account. +After completing the accounting entries, the trial balance report will look like the one given below: + + +![Trial Balance](img/trial-balance-1.png) + + + #### Outstanding Invoices After your Opening Journal Vouchers are made, you will need to enter each Sales Invoice and Purchase Invoice that is yet to be paid. @@ -57,21 +82,3 @@ If you don’t care what items are in that invoice, just make a dummy item entry Once all your invoices are entered, your “Temp Opening Assets” will be same as “Temp Opening Liabilities” and you can pass another “Is Opening” type of Journal Voucher and cancel them to zero! ---- - -## Opening Stock - -You can upload your opening stock in the system using Stock Reconciliation. Stock Reconciliation will update your stock for a given Item on a given date for a given Warehouse to the given quantity. - -Stock Reconciliation will make a “difference entry” (the difference between the system stock and the actual stock in your file) for the Item. - -Tip: Stock Reconciliation can also be used to update the “value” of an Item. - -To make a Stock Reconciliation, go to: - -> Stock > Stock Reconciliation > New Stock Reconciliation - -and follow the steps mentioned on the page. - - -![Stock Reconciliation](img/stock-reconciliation1.png) diff --git a/docs/user/setup/docs.user.setup.permissions.md b/docs/user/setup/docs.user.setup.permissions.md index 5e3507851ee..34de10752ce 100644 --- a/docs/user/setup/docs.user.setup.permissions.md +++ b/docs/user/setup/docs.user.setup.permissions.md @@ -3,7 +3,7 @@ "_label": "Setting up Permissions" } --- -ERPNext has a role-based permission system, which means that you can assign Roles to Users, and permissions on Roles. +ERPNext has a role-based permission system, which means that you can assign Roles to Users, and Permissions on Roles. ERPNext has a very powerful permission structure that will allow you to set permissions right up to the field level. @@ -40,6 +40,7 @@ To delete a rule, just uncheck all the boxes of the row and click on “Update To set “match” rules, select the drop-down in the last column.

For example, you want to restrict Users of Role “Sales User” by Territories in Sales Order. + 1. Select Sales Order in “Set Permissions For” 1. In the row for Role “Sales User”, in the last column “Restrict By”, select “territory”. 1. To assign Territories to Users, click on “Set Users / Roles” @@ -50,7 +51,41 @@ To set “match” rules, select the drop-down in the last column.

For exam In the same way, add a row for each user. +#### Step 1: Select Sales Order in "Set Permissions For" + + +![Permissions Manager](img/permission-manager-1.png) + +#### Step 2: Select restriction option as Territory + +
+![Permissions Manager](img/permission-manager-2.png) + +
+ +#### Step 3: To assign Territories to users, click on "Set Users/Roles" + +
+ +![Permissions Manager](img/permission-manager-3.png) + + +#### Step 3: Restrict User by selecting Territory + + +![Permission Manager](img/permission-manager-4.png) + + +#### Step 4: Select the User to assign to a Territory + + +![Permission Manager](img/permission-manager-5.png) + + > **Note 1:** The “match” rules apply to all documents that you have matched by Territory. > **Note 2:** You can set multiple rules for the same User. In this example, you can set a User to access more than one Territories. 
 + +If you have more than two companies, we recommend that you should maintain seperate databases for these companies (2/3 ERPnext accounts). This avoids data leak from one company to another. + diff --git a/docs/user/setup/docs.user.setup.pos_setting.md b/docs/user/setup/docs.user.setup.pos_setting.md index 8ea863bb3d8..bd2a1a85403 100644 --- a/docs/user/setup/docs.user.setup.pos_setting.md +++ b/docs/user/setup/docs.user.setup.pos_setting.md @@ -18,4 +18,5 @@ Set default values as defined. ![POS Setting](img/pos-setting.png) -> Important : If you specify a particular User, the POS setting will be applied only to that User. If the User option is left blank, the setting will be set for all users. +> Important : If you specify a particular User, the POS setting will be applied only to that User. If the User option is left blank, the setting will be set for all users. To understand POS in detail visit [Point of Sale](docs.user.accounts.pos.html) + diff --git a/docs/user/setup/docs.user.setup.taxes.md b/docs/user/setup/docs.user.setup.taxes.md index 52c82bca787..91ed5916e3c 100644 --- a/docs/user/setup/docs.user.setup.taxes.md +++ b/docs/user/setup/docs.user.setup.taxes.md @@ -7,7 +7,14 @@ One of the primary motivator for compulsory use of accounting tools is calculati ### Tax Accounts -For Tax Accounts that you want to use in the tax templates, you must mention them as type “Tax” in your Chart of Accounts. +For Tax Accounts that you want to use in the tax templates, you must mention them as type “Tax” in your Chart of Accounts. Some Item-tax features are given below : + +- **Discount**: The maximum Discount that can be applied on an Item can be fixed in the Item master. Read [Discount](docs.user.selling.discount.html) +- **Inclusive and Exclusive Tax**: ERPNext allows you to enter Item rates which are tax inclusive. +- **Flat Discount**: This feature will be added soon. +- **Exception to the rule**: Item tax settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account + + ## Sales Taxes and Charges Master diff --git a/docs/user/stock/docs.user.stock.accounting_for_stock.md b/docs/user/stock/docs.user.stock.accounting_for_stock.md new file mode 100644 index 00000000000..8348c8002bd --- /dev/null +++ b/docs/user/stock/docs.user.stock.accounting_for_stock.md @@ -0,0 +1,42 @@ +--- +{ + "_label": "Accounting of Inventory / Stock" +} +--- + +The value of available inventory is treated as an Asset in company's Chart of Accounts. Depending on the type of items, it can be treated as Fixed Asset or Current Asset. To prepare Balance Sheet, you should make the accounting entries for those assets. +There are generally two different methods of accounting for inventory: + + +### **Auto / Perpetual Inventory** + +In this process, for each stock transactions, the system posts relevant accounting entries to sync stock balance and accounting balance. This is the default setting in ERPNext for new accounts. + +When you buy and receive items, those items are booked as the company’s assets (stock-in-hand / fixed-assets). When you sell and deliver those items, an expense (cost-of-goods-sold) equal to the buying cost of the items is booked. General Ledger entries are made after every stock transaction. As a result, the value as per Stock Ledger always remains same with the relevant account balance. This improves accuracy of Balance Sheet and Profit and Loss statement. + +To check accounting entries for a particular stock transaction, please check [**examples**](docs.user.stock.perpetual_inventory.html) + +#### **Advantages** + +Perpetual Inventory system will make it easier for you to maintain accuracy of company's asset and expense values. Stock balances will always be synced with relevant account balances, so no more periodic manual entry has to be done to balance them. + +In case of new back-dated stock transactions or cancellation/amendment of an existing transaction, all the future Stock Ledger entries and GL Entries will be recalculated for all items of that transaction. +The same is applicable if any cost is added to the submitted Purchase Receipt, later through the Landed Cost Wizard. + +>Note: Perpetual Inventory totally depends upon the item valuation rate. Hence, you have to be more careful entering valuation rate while making any incoming stock transactions like Purchase Receipt, Material Receipt, or Manufacturing / Repack. + +- + +### **Periodic Inventory** + +In this method, accounting entries are manually created periodically, to sync stock balance and relevant account balance. The system does not create accounting entries automatically for assets, at the time of material purchases or sales. + +In an accounting period, when you buy and receive items, an expense is booked in your accounting system. You sell and deliver some of these items. + +At the end of an accounting period, the total value of items to be sold, need to be booked as the company’s assets, often known as stock-in-hand. + +The difference between the value of the items remaining to be sold and the previous period’s stock-in-hand value can be positive or negative. If positive, this value is removed from expenses (cost-of-goods-sold) and is added to assets (stock-in-hand / fixed-assets). If negative, a reverse entry is passed. + +This complete process is called Periodic Inventory. + +If you are an existing user using Periodic Inventory and want to use Perpetual Inventory, you have to follow some steps to migrate. For details, check [**Migration From Periodic Inventory**](docs.user.stock.periodic_to_perpetual.html) diff --git a/docs/user/stock/docs.user.stock.item.md b/docs/user/stock/docs.user.stock.item.md index c5c58bd0cfd..c98afd29dd8 100644 --- a/docs/user/stock/docs.user.stock.item.md +++ b/docs/user/stock/docs.user.stock.item.md @@ -29,7 +29,7 @@ To upload an image for your icon that will appear in all transactions, save the ### Item Pricing -Item Price and Price Lists: ERPNext lets you maintain multiple selling prices for an Item using Price Lists. A Price List is a place where different rate plans can be stored. It’s a name you can give to a set of Item prices. In case you have different zones (based on the shipping costs), for different currencies etc, you can maintain different Price Lists. A Price List is formed when you create different Item Prices. To import Item Price visit “Import Item Price”. +Item Price and Price Lists: ERPNext lets you maintain multiple selling prices for an Item using Price Lists. A Price List is a place where different rate plans can be stored. It’s a name you can give to a set of Item prices. In case you have different zones (based on the shipping costs), for different currencies etc, you can maintain different Price Lists. A Price List is formed when you create different Item Prices. To import Item Price see [Importing Data](docs.user.data_import.md). ## Inventory : Warehouse and Stock Setting @@ -56,10 +56,42 @@ These numbers help to track individual units or batches of Items which you sell. ### Item Tax -These settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account. For example, If you have a tax Account, “VAT 10%” and this particular Item is exempted from tax, then you select “VAT 10%” in the first column, and set “0” as the tax rate in the second column. +These settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account. For example, If you have a tax Account, “VAT 10%” and this particular Item is exempted from tax, then you select “VAT 10%” in the first column, and set “0” as the tax rate in the second column. + +Go to [Setting Up Taxes](docs.user.setup.taxes.html) to understand this topic in detail. ### Inspection Inspection Required: If an incoming inspection (at the time of delivery from the Supplier) is mandatory for this Item, mention “Inspection Required” as “Yes”. The system will ensure that a Quality Inspection will be prepared and approved before a Purchase Receipt is submitted. Inspection Criteria: If a Quality Inspection is prepared for this Item, then this template of criteria will automatically be updated in the Quality Inspection table of the Quality Inspection. Examples of Criteria are: Weight, Length, Finish etc. + +### Purchase Details + +![Purchase Details](img/item-purchase.png) + + +**Lead time days:** Lead time days are the number of days required for the Item to reach the warehouse. + + +**Default Expense Account:** It is the account in which cost of the Item will be debited. + +**Default Cost Centre:** It is used for tracking expense for this Item. + +### Sales Details + + +![Sales Details](img/item-sales.png) + + +**Default Income Account:** Income account selected here will be fetched automatically in sales invoice for this item. + +**Cost Centre:** Cost center selected here will be fetched automatically in sales invoice for this item. + +### Manufacturing And Website + +![Manufacturing](img/item-manufacturing-website.png) + + + +Visit [Manufacturing](docs.user.mfg.html) and [Website](docs.user.website.html) to understand these topics in detail. diff --git a/docs/user/stock/docs.user.stock.md b/docs/user/stock/docs.user.stock.md index 240e44c2001..08f16f84308 100644 --- a/docs/user/stock/docs.user.stock.md +++ b/docs/user/stock/docs.user.stock.md @@ -12,7 +12,10 @@ "docs.user.stock.material_issue", "docs.user.stock.sales_return", "docs.user.stock.purchase_return", - "docs.user.stock.projected_quantity" + "docs.user.stock.projected_quantity", + "docs.user.stock.accounting_for_stock", + "docs.user.stock.perpetual_inventory", + "docs.user.stock.migrate_to_perpetual" ] } --- diff --git a/docs/user/stock/docs.user.stock.migrate_to_perpetual.md b/docs/user/stock/docs.user.stock.migrate_to_perpetual.md new file mode 100644 index 00000000000..d4e8ab9beed --- /dev/null +++ b/docs/user/stock/docs.user.stock.migrate_to_perpetual.md @@ -0,0 +1,31 @@ +--- +{ + "_label": "Migrate to Perpetual Inventory" +} +--- + + Existing Users, need to follow some steps to activate the new Perpetual Inventory system. As Perpetual Inventory always maintains a sync between stock and account balance, it is not possible to enable it with existing Warehouse setup. You have to create a whole new set of Warehouses, each linked to relevant account. + +Steps: + +- Nullify the balance of account heads (stock-in-hand / fixed-asset) which you are using to maintain available stock value, through a Journal Voucher. + +- As existing warehouses are linked to stock transactions which does not have corresponding accounting entries, those warehouses can not be used for perpetual inventory. You have to create new warehouses for the future stock transactions which will be linked to their respective accounts. While creating new warehouses, select an account group under which the child account for the warehouse will be created. + +- Setup the following default accounts for each Company + - Stock Received But Not Billed + - Stock Adjustment Account + - Expenses Included In Valuation + - Cost Center + +- Activate Perpetual Inventory +> Setup > Accounts Settings > Make Accounting Entry For Every Stock Movement + +![Activation](img/accounting-for-stock-1.png) +

+ +- Create Stock Entry (Material Transfer) to transfer available stock from existing warehouse to new warehouse. As stock will be available in the new warehouse, you should select the new warehouse for all the future transactions. + +System will not post any accounting entries for existing stock transactions submitted prior to the activation of Perpetual Inventory as those old warehouses will not be linked to any account. If you create any new transaction or modify/amend existing transactions, with old warehouse, there will be no corresponding accounting entries. You have to manually sync stock and account balance through Journal Voucher. + +> Note: If you are already using old Perpetual Inventory system, it will be deactivated automatically. You need to follow the above steps to reactivate it. \ No newline at end of file diff --git a/docs/user/stock/docs.user.stock.perpetual_inventory.md b/docs/user/stock/docs.user.stock.perpetual_inventory.md new file mode 100644 index 00000000000..eb10326ecb4 --- /dev/null +++ b/docs/user/stock/docs.user.stock.perpetual_inventory.md @@ -0,0 +1,313 @@ +--- +{ + "_label": "Perpetual Inventory" +} +--- + +In perpetual inventory, system creates accounting entries for each stock transactions, so that stock and account balance will always remain same. The account balance will be posted against their respective account heads for each Warehouse. On saving of a Warehouse, the system will automatically create an account head with the same name as warehouse. As account balance is maintained for each Warehouse, you should create Warehouses, based on the type of items (Current / Fixed Assets) it stores. + +At the time of items received in a particular warehouse, the balance of asset account (linked to that warehouse) will be increased. Similarly when you deliver some items from that warehouse, an expense will be booked and the asset account will be reduced, based on the valuation amount of those items. + + +## **Activation** + +1. Setup the following default accounts for each Company + - Stock Received But Not Billed + - Stock Adjustment Account + - Expenses Included In Valuation + - Cost Center + +2. In perpetual inventory, the system will maintain seperate account balance for each warehouse under separate account head. To create that account head, enter "Create Account Under" in Warehouse master. + +3. Activate Perpetual Inventory +> Setup > Accounts Settings > Make Accounting Entry For Every Stock Movement + + +- + +## **Example** + +Consider following Chart of Accounts and Warehouse setup for your company: + +#### Chart of Accounts + +- Assets (Dr) + - Current Assets + - Accounts Receivable + - Jane Doe + - Stock Assets + - Stores + - Finished Goods + - Work In Progress + - Tax Assets + - VAT + - Fixed Assets + - Fixed Asset Warehouse +- Liabilities (Cr) + - Current Liabilities + - Accounts Payable + - East Wind Inc. + - Stock Liabilities + - Stock Received But Not Billed + - Tax Liabilities + - Service Tax +- Income (Cr) + - Direct Income + - Sales Account +- Expenses (Dr) + - Direct Expenses + - Stock Expenses + - Cost of Goods Sold + - Expenses Included In Valuation + - Stock Adjustment + - Shipping Charges + - Customs Duty + +#### Warehouse - Account Configuration + +- Stores +- Work In Progress +- Finished Goods +- Fixed Asset Warehouse + +### **Purchase Receipt** + +Suppose you have purchased *10 nos* of item "RM0001" at *$200* and *5 nos* of item "Desktop" at **$100** from supplier "East Wind Inc". Following are the details of Purchase Receipt: + +Supplier: East Wind Inc. + +Items: + + + + + + + + + + + + + + + + +
ItemWarehouseQtyRateAmountValuation Amount
RM0001Stores1020020002200
DesktopFixed Asset Warehouse5100500550
+ +**Taxes:** + + + + + + + + + + +
AccountAmountCategory
Shipping Charges100Total and Valuation
VAT120Total
Customs Duty150Valuation
+ +**Stock Ledger** + +![pr_stock_ledger](img/accounting-for-stock-2.png) + +**General Ledger** + +![pr_general_ledger](img/accounting-for-stock-3.png) + +As stock balance increases through Purchase Receipt, "Store" and "Fixed Asset Warehouse" accounts are debited and a temporary account "Stock Receipt But Not Billed" account is credited, to maintain double entry accounting system. + + +-- + +### **Purchase Invoice** + +On receiving Bill from supplier, for the above Purchase Receipt, you will make Purchase Invoice for the same. The general ledger entries are as follows: + +**General Ledger** + +![pi_general_ledger](img/accounting-for-stock-4.png) + + +Here "Stock Received But Not Billed" account is debited and nullified the effect of Purchase Receipt. "Expenses Included In Valuation" account has been credited which ensures the valuation expense accounts are not booked (debited) twice (in Purchase Invoice and Delivery Note). + +-- + +### **Delivery Note** + +Lets say, you have an order from "Jane Doe" to deliver 5 nos of item "RM0001" at $300. Following are the details of Delivery Note: + +**Customer:** Jane Doe + +**Items:** + + + + + + + +
ItemWarehouseQtyRateAmount
RM0001Stores53001500
+ +**Taxes:** + + + + + + + + + +
AccountAmount
Service Tax150
VAT100
+ + +**Stock Ledger** + +![dn_stock_ledger](img/accounting-for-stock-5.png) + +**General Ledger** + +![dn_general_ledger](img/accounting-for-stock-6.png) + +As item is delivered from "Stores" warehouse, "Stores" account is credited and equal amount is debited to the expense account "Cost of Goods Sold". The debit/credit amount is equal to the total valuation amount (buying cost) of the selling items. And valuation amount is calculated based on your prefferred valuation method (FIFO / Moving Average) or actual cost of serialized items. +
+	
+In this example, we have considered valuation method as FIFO. 
+Valuation Rate 	= Purchase Rate + Charges Included in Valuation 
+				= 200 + (250 * (2000 / 2500) / 10) 
+				= 220
+Total Valuation Amount 	= 220 * 5 
+						= 1100
+	
+
+ +-- + +### **Sales Invoice with Update Stock** + +Lets say, you did not make Delivery Note against the above order and instead you have made Sales Invoice directly, with "Update Stock" options. The details of the Sales Invoice are same as the above Delivery Note. + +**Stock Ledger** + +![si_stock_ledger](img/accounting-for-stock-7.png) + +**General Ledger** + +![si_general_ledger](img/accounting-for-stock-8.png) + +Here, apart from normal account entries for invoice, "Stores" and "Cost of Goods Sold" accounts are also affected based on the valuation amount. + +-- + +### **Stock Entry (Material Receipt)** + +**Items:** + + + + + + + +
ItemTarget WarehouseQtyRateAmount
RM0001Stores5022011000
+ +**Stock Ledger** + +![mr_stock_ledger](img/accounting-for-stock-9.png) + +**General Ledger** + +![mr_stock_ledger](img/accounting-for-stock-10.png) + +-- + +### **Stock Entry (Material Issue)** + +**Items:** + + + + + + + +
ItemSource WarehouseQtyRateAmount
RM0001Stores102202200
+ +**Stock Ledger** + +![mi_stock_ledger](img/accounting-for-stock-11.png) + +**General Ledger** + +![mi_stock_ledger](img/accounting-for-stock-12.png) + +-- + +### **Stock Entry (Material Transfer)** + +**Items:** + + + + + + + + + +
ItemSource WarehouseTarget WarehouseQtyRateAmount
RM0001StoresWork In Progress102202200
+ +**Stock Ledger** + +![mtn_stock_ledger](img/accounting-for-stock-13.png) + +**General Ledger** + +![mtn_general_ledger](img/accounting-for-stock-14.png) + +-- + +### **Stock Entry (Sales Return - Sales Invoice booked)** + +**Items:** + + + + + + + +
ItemTarget WarehouseQtyRateAmount
RM0001Stores2200400
+ +**Stock Ledger** + +![sret_stock_ledger](img/accounting-for-stock-15.png) + +**General Ledger** + +![sret_general_ledger](img/accounting-for-stock-16.png) + + +-- + +### **Stock Entry (Purchase Return)** + +**Items:** + + + + + + + +
ItemSource WarehouseQtyRateAmount
RM0001Stores4220880
+ +**Stock Ledger** + +![pret_stock_ledger](img/accounting-for-stock-17.png) + +**General Ledger** + +![pret_general_ledger](img/accounting-for-stock-18.png) diff --git a/docs/user/stock/docs.user.stock.warehouse.md b/docs/user/stock/docs.user.stock.warehouse.md index dd8273ab4d3..38a559e29cd 100644 --- a/docs/user/stock/docs.user.stock.warehouse.md +++ b/docs/user/stock/docs.user.stock.warehouse.md @@ -18,13 +18,12 @@ To go to Warehouse, click on Stock and go to Warehouse under Masters. -In ERPNext, every different company can have a separate Warehouse. Every Warehouse will belong to a specific company. User can get company wise accurate stock balance. The Warehouses are saved with their respective company’s abbreviations. This facilitates in identifying which Warehouse belongs to which company, at a glance. +In ERPNext, every Warehouse must belong to a specific company, to maintain company wise stock balance. The Warehouses are saved with their respective company’s abbreviations. This facilitates in identifying which Warehouse belongs to which company, at a glance. You can include user restrictions for these Warehouses. In case you do not wish a particular user to operate on a particular Warehouse, you can refrain the user from accessing that Warehouse. ### Merge Warehouse -In day to day transactions, if duplicate entries are done by mistake resulting in duplicate Warehouse, these mistakes can be rectified. Duplicate records can be merged into a single Warehouse. Enter the place where you want to keep all the warehouse records. Click on the Merge button. Once this transaction is done, delete the empty Warehouse. - -ERPNext system maintains stock balance for every distinct combination of Item and Warehouse. Thus you can get stock balance for any specific Item in a particular Warehouse on any particular date. +In day to day transactions, duplicate entries are done by mistake, resulting in duplicate Warehouses. Duplicate records can be merged into a single Warehouse. Enter the correct Warehouse and click on the Merge button. The system will replace all the links of wrong Warehouse with the correct Warehouse, in all transactions. Also, the available quantity (actual qty, reserved qty, ordered qty etc) of all items in the duplicate warehouse will be transferred to the correct warehouse. Once merging is done, delete the duplicate Warehouse. +> Note: ERPNext system maintains stock balance for every distinct combination of Item and Warehouse. Thus you can get stock balance for any specific Item in a particular Warehouse on any particular date. \ No newline at end of file diff --git a/docs/user/support/docs.user.support.customer_issue.md b/docs/user/support/docs.user.support.customer_issue.md index 92cb29a2f2c..4c5afd1a22a 100644 --- a/docs/user/support/docs.user.support.customer_issue.md +++ b/docs/user/support/docs.user.support.customer_issue.md @@ -3,7 +3,7 @@ "_label": "Customer Issue" } --- -If you are selling **Items** under warranty or if you have sold and extended service contract Annual Maintenance Contract (AMC), your **Customer** may call you about an issue or a bread-down and give you the Serial No of this Item. +If you are selling **Items** under warranty or if you have sold and extended service contract like the Annual Maintenance Contract (AMC), your **Customer** may call you about an issue or a break-down and give you the Serial No of this Item. To record this, you can create a new **Customer Issue** and add the **Customer** and **Item** / **Serial No**. The system will then automatically fetch the Serial No’s details and indicate whether this is under warranty or AMC. @@ -13,5 +13,10 @@ To create a new **Customer Issue**: > Support > Customer Issue > New Customer Issue + +![Customer Issue](img/customer-issue.png) + + + If a Customer visit is required to address the issue, you can create a new Maintenance Visit record from this. diff --git a/docs/user/support/docs.user.support.maintenance_schedule.md b/docs/user/support/docs.user.support.maintenance_schedule.md index 82bf8f910e1..a2bf5eae6a7 100644 --- a/docs/user/support/docs.user.support.maintenance_schedule.md +++ b/docs/user/support/docs.user.support.maintenance_schedule.md @@ -11,6 +11,26 @@ To create a new Maintenance Schedule go to: > Support > Maintenance Schedule > New Maintenance Schedule +#### Step 1: Enter Customer Details + + +![Maintenance Schedule](img/maintenance-schedule-1.png) + + + +
+ + + +#### Step 2: Fill Item and Maintenance Details + + + +![Maintenance Schedule](img/maintenance-schedule-2.png) + + +
+ In the Maintenance Schedule, there are two sections: In the first section, you select the Items for which you want to generate the schedule and set how frequently you want to plan a visit or a maintenance. These can be optionally fetched from a Sales Order. After selecting the Items, “Save” the record. diff --git a/docs/user/support/docs.user.support.maintenance_visit.md b/docs/user/support/docs.user.support.maintenance_visit.md index dc115e2a6f8..210277af375 100644 --- a/docs/user/support/docs.user.support.maintenance_visit.md +++ b/docs/user/support/docs.user.support.maintenance_visit.md @@ -7,6 +7,10 @@ A Maintenance Visit is a record for a visit made by an engineer to a Customer’ > Support > Maintenance Visit > New Maintenance Visit + +![Maintenance Visit](img/maintenance-visit.png) + + The Maintenance Visit contains information about the: - Customer. diff --git a/docs/user/support/docs.user.support.md b/docs/user/support/docs.user.support.md index 775c030c019..acd693fa632 100644 --- a/docs/user/support/docs.user.support.md +++ b/docs/user/support/docs.user.support.md @@ -5,10 +5,11 @@ "docs.user.support.support_ticket", "docs.user.support.customer_issue", "docs.user.support.maintenance_visit", - "docs.user.support.maintenance_schedule" + "docs.user.support.maintenance_schedule", + "docs.user.support.newsletter" ] } --- -Great customer support and maintenance is at the heart of any successful small business and ERPNext gives you the tools to track all incoming requests and issues from your customers so that you can respond quickly. Your database of incoming queries will also help you track where are the biggest opportunities for improvements. +Great customer support and maintenance is at the heart of any successful small business.ERPNext gives you the tools to track all incoming requests and issues from your customers so that you can respond quickly. Your database of incoming queries will also help you track where the biggest opportunities are for improvements. -In this module, you can track incoming queries from your email using Support Ticket, keep track on Customer Issues raised by Customers on specific Serial No and respond to them based on their warranty and other information, make Maintenance Schedules for Serial Nos and keep a record of all Maintenance Visits made to your Customers. +In this module, you can track incoming queries from your email using Support Ticket. You can keep track on Customer Issues raised by Customers on specific Serial No and respond to them based on their warranty and other information. You can also make Maintenance Schedules for Serial Nos and keep a record of all Maintenance Visits made to your Customers. diff --git a/docs/user/support/docs.user.support.newsletter.md b/docs/user/support/docs.user.support.newsletter.md new file mode 100644 index 00000000000..443a6e55e31 --- /dev/null +++ b/docs/user/support/docs.user.support.newsletter.md @@ -0,0 +1,14 @@ +--- +{ + "_label": "Newsletter" +} +--- + +A newsletter is a short written report that tells about the recent activities of an organization. It is generally sent to members of the organization, potential clients customers or potential leads. + +In ERPNext, you can use this UI to send any type of communication to a large number of audience. The process of sending bulk email to a target audience is very simple and easy. + +Select the list that you want to send the email to. Fill in your content in the message box, and send your newsletter.If you wish to test your email, to see how it looks to the recepient, you can use the test function.Save the document before testing. A test email will be sent to your email id. You can send the email to all the intended receipients by clicking on the send button. + + +![Newsletter](img/newsletter.png) diff --git a/docs/user/support/docs.user.support.support_ticket.md b/docs/user/support/docs.user.support.support_ticket.md index b4a496b3588..c75046297b1 100644 --- a/docs/user/support/docs.user.support.support_ticket.md +++ b/docs/user/support/docs.user.support.support_ticket.md @@ -3,15 +3,26 @@ "_label": "Support Ticket" } --- -Support Ticket is an incoming query from your Customer, usually via email of from the “Contact” section of your website. (To fully integrate the Support Ticket to email, see the Email Settings section). +Support Ticket is an incoming query from your Customer, usually via email or from the “Contact” section of your website. (To fully integrate the Support Ticket to email, see the Email Settings section). > Tip: A dedicated support email id is a good way to integrate incoming queries via email. For example, you can send support queries to ERPNext at support@erpnext.com and it will automatically create a Support Ticket in the Web Notes system. +
+ + +> Support > Support Ticket > New Support Ticket + + + +![Support Ticket](img/support-ticket.png) + + + #### Discussion Thread When a new email is fetched from your mailbox, a new Support Ticket record is created and an automatic reply is sent to the sender indicating the Support Ticket Number. The sender can send additional information to this email. All subsequent emails containing this Support Ticket number in the subject will be added to this Support Ticket thread. The sender can also add attachments to the email. -Support Ticket maintains all the emails sent back and forth against this issue in the system so that you can track what transpired between the sender and the person responding. +Support Ticket maintains all the emails which are sent back and forth against this issue in the system so that you can track what transpired between the sender and the person responding. #### Status @@ -19,7 +30,7 @@ When a new Support Ticket is created, its status is “Open”, when it is repli #### Closing -You can either “Close” the Support Ticket manually by clicking on “Close Ticket” in the toolbar or if its status is “Waiting For Reply” and the sender did not reply in 7 days, then the Support Ticket closes automatically. +You can either “Close” the Support Ticket manually by clicking on “Close Ticket” in the toolbar or if its status is “Waiting For Reply” . If the sender does not reply in 7 days, then the Support Ticket closes automatically. #### Allocation diff --git a/docs/user/tools/docs.user.tools.assignment.md b/docs/user/tools/docs.user.tools.assignment.md new file mode 100644 index 00000000000..8ec04ea34d2 --- /dev/null +++ b/docs/user/tools/docs.user.tools.assignment.md @@ -0,0 +1,22 @@ +--- +{ + "_label": "Assignment" +} +--- + +You can assign any transaction to any user by clicking on the “Assign” button on the right hand column and adding a user. + +#### Step 1: Click on the Assign To Button + +![Assigned To](img/assigned-to.png) + + + +#### Step 2: Add the User and other details + + +![Assign User](img/assignment.png) + + +This transaction will appear in the To-do list of the user in “My List” section. It will also appear +in the “Assigned by me” section of the user, who has assigned this activity. diff --git a/docs/user/tools/docs.user.tools.form_tools.md b/docs/user/tools/docs.user.tools.form_tools.md index 65943d70ed1..1c88f5fb1f4 100644 --- a/docs/user/tools/docs.user.tools.form_tools.md +++ b/docs/user/tools/docs.user.tools.form_tools.md @@ -3,9 +3,15 @@ "_label": "Collaborating around Forms" } --- -### Email +### Assigned to + +You can email any transaction from the system by clicking on the “Assigned to” button in the right sidebar. A log of all your sent emails will be maintained in Communication. + + + +![Forms](img/forms.png) + -You can email any transaction from the system by clicking on the “Email” button in the right sidebar. A log of all your sent emails will be maintained in Communication. ### Comments @@ -13,4 +19,5 @@ Comments are a great way to add information about a transaction that is not a pa ### Tags -Like Assignments and Comments, you can also add your own tags to each type of transactions. These tags can help you search a document and also classify it. ERPNext will also show you all the important tags in the document list. +[Tags](docs.user.tools.tags.html) + diff --git a/docs/user/tools/docs.user.tools.md b/docs/user/tools/docs.user.tools.md index 891b756406d..68abb963b7a 100644 --- a/docs/user/tools/docs.user.tools.md +++ b/docs/user/tools/docs.user.tools.md @@ -5,7 +5,10 @@ "docs.user.tools.todo", "docs.user.tools.form_tools", "docs.user.tools.messages", - "docs.user.tools.notes" + "docs.user.tools.notes", + "docs.user.tools.calendar", + "docs.user.tools.assignment", + "docs.user.tools.tags" ] } --- diff --git a/docs/user/tools/docs.user.tools.notes.md b/docs/user/tools/docs.user.tools.notes.md index fcb8fce72f5..8cad69a42ec 100644 --- a/docs/user/tools/docs.user.tools.notes.md +++ b/docs/user/tools/docs.user.tools.notes.md @@ -2,4 +2,21 @@ { "_label": "Notes" } ---- \ No newline at end of file +--- + +You can store your long notes under this section. It can contain your partner lists, frequently used passwords, terms and conditions , or any other document which needs to be shared. + +#### Step 1: Enter Title and Content + + +![Notes](img/notes.png) + +
+ + +#### Step 2: Set Permissions to select Users + + + +![Notes](img/notes-1.png) + diff --git a/docs/user/tools/docs.user.tools.tags.md b/docs/user/tools/docs.user.tools.tags.md new file mode 100644 index 00000000000..34e7f273628 --- /dev/null +++ b/docs/user/tools/docs.user.tools.tags.md @@ -0,0 +1,10 @@ +--- +{ + "_label": "Tags" +} +--- + +Like Assignments and Comments, you can also add your own tags to each type of transactions. These tags can help you search a document and also classify it. ERPNext will also show you all the important tags in the document list. + + +![Tags](img/tags.png) diff --git a/docs/user/tools/docs.user.tools.todo.md b/docs/user/tools/docs.user.tools.todo.md index 131af69b756..46be2e2c3d7 100644 --- a/docs/user/tools/docs.user.tools.todo.md +++ b/docs/user/tools/docs.user.tools.todo.md @@ -1,23 +1,12 @@ --- { - "_label": "Assignment and To Do" + "_label": "To Do" } --- -### Assignment -You can assign any transaction to any user by clicking on the “Assign” button on the right hand column and adding a user. - -Step 1: Click on the Assign To Button - -Step 2: Add the User and other details - -This transaction will appear in: - -The To-do list of the user whom this is assigned to in “My List” section -In the “Assigned by me” section of the user who has assigned this activity. - -### To Do To Do is a simple tool where all the activities assigned to you and assigned by you are listed. You can also add your own to-do items in the list. +![To Do](img/to-do.png) + diff --git a/docs/user/website/docs.user.website.blog.md b/docs/user/website/docs.user.website.blog.md index 4e2a782c68b..ec035a03440 100644 --- a/docs/user/website/docs.user.website.blog.md +++ b/docs/user/website/docs.user.website.blog.md @@ -11,6 +11,10 @@ To create a new blog, just create a new Blog from: > Website > Blog > New Blog + +![Blog](img/blog.png) + + You can format the blog using the same Markdown format You can access your blog by going to the page “blog.html” diff --git a/docs/user/website/docs.user.website.md b/docs/user/website/docs.user.website.md index e3fa4496480..b628e710c18 100644 --- a/docs/user/website/docs.user.website.md +++ b/docs/user/website/docs.user.website.md @@ -11,8 +11,8 @@ --- Websites are a core component of any business and having a good website usually means: -- Lot of money. -- Hard to update. +- Invest lot of money. +- Difficult to update. - Not interactive. Unless you are a web designer yourself. @@ -29,4 +29,4 @@ We will soon be adding a shopping cart facility so that your customers can place Though not necessary, to make a good website, you might have to know a bit of HTML / CSS or hire the services of a professional. The good part is that once this is setup, you can add and edit content, blogs and products directly from your ERP. -> The ERPNext website (www.erpnext.com) is generated from the Website Module! In the world of startups, its called eating-your-own-dog-food! \ No newline at end of file +> The ERPNext website (www.erpnext.com) is generated from the Website Module! In the world of startups, it's called eating-your-own-dog-food! \ No newline at end of file diff --git a/docs/user/website/docs.user.website.setup.md b/docs/user/website/docs.user.website.setup.md index 23df9280164..3249637c2d3 100644 --- a/docs/user/website/docs.user.website.setup.md +++ b/docs/user/website/docs.user.website.setup.md @@ -29,6 +29,12 @@ To define the Top Bar Menus, Brand, Footers and Home Page, go to: > Website > Website Settings + +![Website Setting](img/website-settings.png) + +
+ + #### Top Menu Add each top menu item on a new link. @@ -45,4 +51,4 @@ To set your brand, create a logo with a transparent background and white foregro ##### What is Website Analytics? -Website analytics help you track each visitor on your website. The analytics will tell you from which country, at what time, and what pages the visitor viewed. This will help you understand who your visitors are and what are they looking for. There are many analytics engines available and the most popular and Free service is Google Analytics. We definitely recommend using one of them to gain insight into your website visitors. \ No newline at end of file +Website analytics helps you to track each visitor on your website. The analytics will tell you from which country, at what time, and which pages the visitor viewed. This will help you understand who your visitors are and what they are looking for. There are many analytics engines available and the most popular and Free service is Google Analytics. We definitely recommend using one of them to gain insight into your website visitors. \ No newline at end of file diff --git a/docs/user/website/docs.user.website.web_page.md b/docs/user/website/docs.user.website.web_page.md index e8567752031..a8f0e13e258 100644 --- a/docs/user/website/docs.user.website.web_page.md +++ b/docs/user/website/docs.user.website.web_page.md @@ -7,6 +7,10 @@ Static Content like your Home Page, About Us, Contact Us, Terms pages can be cre > Website > Web Page > New Web Page +![Web Page](img/webpage.png) + + + #### Title The first thing to set is the title of your page. The title has the maximum weight for search engines so choose a title that reflects the keywords that you are targeting for your audience. diff --git a/hr/doctype/expense_claim/expense_claim.txt b/hr/doctype/expense_claim/expense_claim.txt index 2b1882fc5b8..45ff19b377c 100644 --- a/hr/doctype/expense_claim/expense_claim.txt +++ b/hr/doctype/expense_claim/expense_claim.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-10 16:34:14", "docstatus": 0, - "modified": "2013-07-05 14:36:59", + "modified": "2013-09-25 11:36:11", "modified_by": "Administrator", "owner": "harshada@webnotestech.com" }, @@ -41,6 +41,7 @@ }, { "default": "Draft", + "depends_on": "eval:!doc.__islocal", "doctype": "DocField", "fieldname": "approval_status", "fieldtype": "Select", @@ -50,7 +51,7 @@ "no_copy": 1, "oldfieldname": "approval_status", "oldfieldtype": "Select", - "options": "\nDraft\nApproved\nRejected", + "options": "Draft\nApproved\nRejected", "search_index": 1 }, { diff --git a/manufacturing/doctype/production_order/production_order.py b/manufacturing/doctype/production_order/production_order.py index b9d15fdf962..a280a822029 100644 --- a/manufacturing/doctype/production_order/production_order.py +++ b/manufacturing/doctype/production_order/production_order.py @@ -116,10 +116,12 @@ class DocType: """update planned qty in bin""" args = { "item_code": self.doc.production_item, + "warehouse": self.doc.fg_warehouse, "posting_date": nowdate(), "planned_qty": flt(qty) } - get_obj('Warehouse', self.doc.fg_warehouse).update_bin(args) + from stock.utils import update_bin + update_bin(args) @webnotes.whitelist() def get_item_details(item): diff --git a/patches/april_2012/__init__.py b/patches/april_2012/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/patches/april_2012/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/patches/april_2012/after_sync_cleanup.py b/patches/april_2012/after_sync_cleanup.py deleted file mode 100644 index 5e93ce1ea4a..00000000000 --- a/patches/april_2012/after_sync_cleanup.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model import delete_doc - webnotes.conn.sql("update `tabDocType` set module = 'Utilities' where name in ('Question', 'Answer')") - delete_doc('Module Def', 'Knowledge Base') diff --git a/patches/april_2012/delete_about_contact.py b/patches/april_2012/delete_about_contact.py deleted file mode 100644 index 2655e3c06eb..00000000000 --- a/patches/april_2012/delete_about_contact.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model import delete_doc - delete_doc('DocType', 'About Us Team') - delete_doc('DocType', 'About Us Settings') - delete_doc('DocType', 'Contact Us Settings') \ No newline at end of file diff --git a/patches/april_2012/naming_series_patch.py b/patches/april_2012/naming_series_patch.py deleted file mode 100644 index 8b086f7b2a1..00000000000 --- a/patches/april_2012/naming_series_patch.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.code import get_obj - ns_list = webnotes.conn.sql("""\ - SELECT `tabDocField`.`parent`, `tabDocField`.`options` - FROM `tabDocField`, `tabDocType` - WHERE `tabDocField`.`fieldname` = 'naming_series' - AND `tabDocType`.name=`tabDocField`.parent""") - ns_obj = get_obj('Naming Series') - for ns in ns_list: - if ns[0] and isinstance(ns[1], basestring): - ns_obj.set_series_for(ns[0], ns[1].split("\n")) diff --git a/patches/april_2012/reload_c_form.py b/patches/april_2012/reload_c_form.py deleted file mode 100644 index afb8b6acfdd..00000000000 --- a/patches/april_2012/reload_c_form.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.modules import reload_doc - reload_doc('accounts', 'doctype', 'c_form') diff --git a/patches/april_2012/remove_default_from_rv_detail.py b/patches/april_2012/remove_default_from_rv_detail.py deleted file mode 100644 index 7716e6ce3e8..00000000000 --- a/patches/april_2012/remove_default_from_rv_detail.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("update `tabDocField` set `default` = '' where fieldname = 'cost_center' and parent = 'RV Detail' and `default` = 'Purchase - TC'") diff --git a/patches/april_2012/repost_stock_for_posting_time.py b/patches/april_2012/repost_stock_for_posting_time.py deleted file mode 100644 index 0626205f0c2..00000000000 --- a/patches/april_2012/repost_stock_for_posting_time.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - res = webnotes.conn.sql("""select distinct item_code, warehouse from `tabStock Ledger Entry` - where posting_time > '00:00:00' and posting_time < '00:01:00'""", as_dict=1) - webnotes.conn.sql("update `tabStock Ledger Entry` set posting_time = '00:00:00' where posting_time > '00:00:00' and posting_time < '00:01:00'") - - from stock.stock_ledger import update_entries_after - for d in res: - update_entries_after({ - "item_code": d.item_code, - "warehouse": d.warehouse, - }) diff --git a/patches/april_2012/update_appraisal_permission.py b/patches/april_2012/update_appraisal_permission.py deleted file mode 100644 index 1c07822bd26..00000000000 --- a/patches/april_2012/update_appraisal_permission.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.doc import addchild - from webnotes.model.code import get_obj - - webnotes.conn.sql("delete from `tabDocPerm` where role = 'All' and permlevel = 0 and parent in ('Appraisal', 'Ticket', 'Project')") - - appr = get_obj('DocType', 'Appraisal', with_children=1) - ch = addchild(appr.doc, 'permissions', 'DocPerm') - ch.permlevel = 0 - ch.role = 'Employee' - ch.read = 1 - ch.write = 1 - ch.save() diff --git a/patches/april_2012/update_permlevel_in_address.py b/patches/april_2012/update_permlevel_in_address.py deleted file mode 100644 index a4b84de8fee..00000000000 --- a/patches/april_2012/update_permlevel_in_address.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("update `tabDocPerm` set permlevel = 0 where parent = 'Address'") diff --git a/patches/april_2012/update_role_in_address.py b/patches/april_2012/update_role_in_address.py deleted file mode 100644 index 33fecbea3c7..00000000000 --- a/patches/april_2012/update_role_in_address.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.doc import addchild - from webnotes.model.code import get_obj - - webnotes.conn.sql("delete from `tabDocPerm` where role = 'All' and parent = 'Address'") - - role1 = ['Sales User', 'Purchase User', 'Accounts User', 'Maintenance User'] - role2 = ['Sales Manager', 'Sales Master Manager', 'Purchase Manager', 'Purchase Master Manager', 'Accounts Manager', 'Maintenance Manager'] - - addr = get_obj('DocType', 'Address', with_children=1) - for d in role1+role2: - ch = addchild(addr.doc, 'permissions', 'DocPerm') - ch.role = d - ch.read = 1 - ch.write = 1 - ch.create = 1 - if d in role2: - ch.cancel = 1 - - ch.save() diff --git a/patches/august_2012/__init__.py b/patches/august_2012/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/patches/august_2012/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/patches/august_2012/change_profile_permission.py b/patches/august_2012/change_profile_permission.py deleted file mode 100644 index 5944c03148a..00000000000 --- a/patches/august_2012/change_profile_permission.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - import webnotes.model.doc - webnotes.conn.sql("delete from `tabDocPerm` where parent='Profile' and permlevel=1") - new_perms = [ - { - 'parent': 'Profile', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'Administrator', - 'permlevel': 1, - 'read': 1, - 'write': 1 - }, - { - 'parent': 'Profile', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'System Manager', - 'permlevel': 1, - 'read': 1, - 'write': 1 - }, - - ] - for perms in new_perms: - doc = webnotes.model.doc.Document('DocPerm') - doc.fields.update(perms) - doc.save() - webnotes.conn.commit() - webnotes.conn.begin() - - webnotes.reload_doc('core', 'doctype', 'profile') \ No newline at end of file diff --git a/patches/august_2012/repost_billed_amt.py b/patches/august_2012/repost_billed_amt.py deleted file mode 100644 index b545c2e621c..00000000000 --- a/patches/august_2012/repost_billed_amt.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.code import get_obj - from selling.doctype.sales_common.sales_common import StatusUpdater - - invoices = webnotes.conn.sql("select name from `tabSales Invoice` where docstatus = 1") - for inv in invoices: - inv_obj = get_obj('Sales Invoice', inv[0], with_children=1) - StatusUpdater(inv_obj, 1).update_all_qty() \ No newline at end of file diff --git a/patches/august_2012/task_allocated_to_assigned.py b/patches/august_2012/task_allocated_to_assigned.py deleted file mode 100644 index 5d736bf8722..00000000000 --- a/patches/august_2012/task_allocated_to_assigned.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes - -def execute(): - from webnotes.widgets.form.assign_to import add - for t in webnotes.conn.sql("""select * from tabTask - where ifnull(allocated_to, '')!=''""", as_dict=1): - add({ - 'doctype': "Task", - 'name': t['name'], - 'assign_to': t['allocated_to'], - 'assigned_by': t['owner'], - 'description': t['subject'], - 'date': t['creation'], - "no_notification": True - }) \ No newline at end of file diff --git a/patches/august_2013/p01_auto_accounting_for_stock_patch.py b/patches/august_2013/p01_auto_accounting_for_stock_patch.py new file mode 100644 index 00000000000..c6a22dd2dce --- /dev/null +++ b/patches/august_2013/p01_auto_accounting_for_stock_patch.py @@ -0,0 +1,6 @@ +import webnotes +from webnotes.utils import cint + +def execute(): + import patches.march_2013.p08_create_aii_accounts + patches.march_2013.p08_create_aii_accounts.execute() \ No newline at end of file diff --git a/patches/august_2013/p02_rename_price_list.py b/patches/august_2013/p02_rename_price_list.py index 0b1c4d0047a..41efb273069 100644 --- a/patches/august_2013/p02_rename_price_list.py +++ b/patches/august_2013/p02_rename_price_list.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import webnotes def execute(): - webnotes.reload_doc("website", "doctype", "shopping_cart_price_list") + webnotes.reload_doc("selling", "doctype", "shopping_cart_price_list") for t in [ ("Supplier Quotation", "price_list_name", "buying_price_list"), diff --git a/patches/august_2013/p06_deprecate_is_cancelled.py b/patches/august_2013/p06_deprecate_is_cancelled.py new file mode 100644 index 00000000000..ad196bd1407 --- /dev/null +++ b/patches/august_2013/p06_deprecate_is_cancelled.py @@ -0,0 +1,11 @@ +import webnotes +def execute(): + webnotes.reload_doc("stock", "doctype", "stock_ledger_entry") + webnotes.reload_doc("stock", "doctype", "serial_no") + webnotes.reload_doc("stock", "report", "stock_ledger") + + webnotes.conn.sql("""delete from `tabStock Ledger Entry` + where ifnull(is_cancelled, 'No') = 'Yes'""") + + webnotes.reload_doc("accounts", "doctype", "gl_entry") + webnotes.conn.sql("""delete from `tabGL Entry` where ifnull(is_cancelled, 'No') = 'Yes'""") \ No newline at end of file diff --git a/patches/july_2012/__init__.py b/patches/july_2012/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/patches/july_2012/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/patches/july_2012/address_contact_perms.py b/patches/july_2012/address_contact_perms.py deleted file mode 100644 index 8c41dbc8e8b..00000000000 --- a/patches/july_2012/address_contact_perms.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("""\ - delete from `tabDocPerm` - where parent in ('Address', 'Contact')""") - webnotes.conn.commit() - - webnotes.reload_doc('utilities', 'doctype', 'address') - webnotes.reload_doc('utilities', 'doctype', 'contact') - webnotes.conn.begin() \ No newline at end of file diff --git a/patches/july_2012/auth_table.py b/patches/july_2012/auth_table.py deleted file mode 100644 index 718243d6de2..00000000000 --- a/patches/july_2012/auth_table.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -def execute(): - webnotes.conn.commit() - - from webnotes.install_lib.install import Installer - Installer(None, None).create_auth_table() - - webnotes.conn.begin() - - for user, password in webnotes.conn.sql("""select name, password from tabProfile"""): - if password: - webnotes.conn.sql("""insert into __Auth (user, `password`) values (%s, %s)""", - (user, password)) diff --git a/patches/july_2012/default_freeze_account.py b/patches/july_2012/default_freeze_account.py deleted file mode 100644 index 5255aa0df83..00000000000 --- a/patches/july_2012/default_freeze_account.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("""update tabAccount set freeze_account='No' where freeze_account is null""") - webnotes.conn.sql("""update `tabPurchase Taxes and Charges` - set category='Valuation and Total' where category='For Both'""") - webnotes.conn.sql("""update `tabPurchase Taxes and Charges` - set category='Valuation' where category='For Valuation'""") - webnotes.conn.sql("""update `tabPurchase Taxes and Charges` - set category='Total' where category='For Total'""") \ No newline at end of file diff --git a/patches/july_2012/deprecate_bulk_rename.py b/patches/july_2012/deprecate_bulk_rename.py deleted file mode 100644 index 5cb25a75e06..00000000000 --- a/patches/july_2012/deprecate_bulk_rename.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model import delete_doc - delete_doc('DocType', 'Bulk Rename Tool') - webnotes.conn.commit() - webnotes.conn.sql("drop table `tabBulk Rename Tool`") - webnotes.conn.begin() \ No newline at end of file diff --git a/patches/july_2012/deprecate_import_data_control.py b/patches/july_2012/deprecate_import_data_control.py deleted file mode 100644 index f060587ccc5..00000000000 --- a/patches/july_2012/deprecate_import_data_control.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """ - deprecate: - * doctype - import data control - * page - import data (old) - """ - import webnotes - from webnotes.model import delete_doc - delete_doc('DocType', 'Import Data Control') - delete_doc('Page', 'Import Data') \ No newline at end of file diff --git a/patches/july_2012/packing_list_cleanup_and_serial_no.py b/patches/july_2012/packing_list_cleanup_and_serial_no.py deleted file mode 100644 index b91ef810f1f..00000000000 --- a/patches/july_2012/packing_list_cleanup_and_serial_no.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.utils import flt - wrong_plist = webnotes.conn.sql(""" - select dnpi.name, dnpi.parent, dnpi.docstatus, dnpi.serial_no - from `tabDelivery Note Packing Item` dnpi - where ifnull(dnpi.parent, '') != '' - and ifnull(dnpi.parent, '') not like 'old_par%' - and dnpi.parenttype = 'Delivery Note' - and not exists ( - select * from `tabDelivery Note Item` dni - where dni.item_code = dnpi.parent_item and - dni.name = dnpi.parent_detail_docname and - dni.parent = dnpi.parent - ) - """, as_dict=1) - - for d in wrong_plist: - if d['docstatus'] == 2 and d['serial_no']: - for s in d['serial_no'].splitlines(): - sle = webnotes.conn.sql(""" - select actual_qty, warehouse, voucher_no - from `tabStock Ledger Entry` - where ( - serial_no like '%s\n%%' - or serial_no like '%%\n%s' - or serial_no like '%%\n%s\n%%' - or serial_no = '%s' - ) - and voucher_no != '%s' - and ifnull(is_cancelled, 'No') = 'No' - order by name desc - limit 1 - """% (s, s, s, s, d['parent']), as_dict=1) - - status = 'Not in Use' - if sle and flt(sle[0]['actual_qty']) > 0: - status = 'Available' - elif sle and flt(sle[0]['actual_qty']) < 0: - status = 'Delivered' - - webnotes.conn.sql("update `tabSerial No` set status = %s, warehouse = %s where name = %s", (status, sle[0]['warehouse'], s)) - - webnotes.conn.sql("delete from `tabDelivery Note Packing Item` where name = %s", d['name']) \ No newline at end of file diff --git a/patches/july_2012/project_patch_repeat.py b/patches/july_2012/project_patch_repeat.py deleted file mode 100644 index c17e9c448b7..00000000000 --- a/patches/july_2012/project_patch_repeat.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("""update `tabPurchase Order Item` t1, `tabPurchase Order` t2 - set t1.project_name = t2.project_name where t1.parent = t2.name - and ifnull(t1.project_name, '') = ''""") - webnotes.conn.sql("""update `tabPurchase Invoice Item` t1, `tabPurchase Invoice` t2 - set t1.project_name = t2.project_name where t1.parent = t2.name - and ifnull(t1.project_name, '') = ''""") - webnotes.conn.sql("""update `tabPurchase Receipt Item` t1, `tabPurchase Receipt` t2 - set t1.project_name = t2.project_name where t1.parent = t2.name - and ifnull(t1.project_name, '') = ''""") - - webnotes.conn.commit() - webnotes.reload_doc("buying", "doctype", "purchase_order") - webnotes.reload_doc("accounts", "doctype", "purchase_invoice") - webnotes.conn.begin() \ No newline at end of file diff --git a/patches/july_2012/remove_event_role_owner_match.py b/patches/july_2012/remove_event_role_owner_match.py deleted file mode 100644 index cd5f489036e..00000000000 --- a/patches/july_2012/remove_event_role_owner_match.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("""\ - update `tabDocPerm` set `match`=NULL - where parent='Event' and role='All' and permlevel=0""") \ No newline at end of file diff --git a/patches/july_2012/repost_stock_due_to_wrong_packing_list.py b/patches/july_2012/repost_stock_due_to_wrong_packing_list.py deleted file mode 100644 index 315b4edd207..00000000000 --- a/patches/july_2012/repost_stock_due_to_wrong_packing_list.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -from stock.stock_ledger import update_entries_after - -def execute(): - # add index - webnotes.conn.commit() - try: - webnotes.conn.sql("""alter table `tabDelivery Note Packing Item` - add index item_code_warehouse (item_code, warehouse)""") - except: - pass - webnotes.conn.begin() - - webnotes.conn.auto_commit_on_many_writes = 1 - repost_reserved_qty() - cleanup_wrong_sle() - webnotes.conn.auto_commit_on_many_writes = 0 - -def repost_reserved_qty(): - from webnotes.utils import flt - bins = webnotes.conn.sql("select item_code, warehouse, name, reserved_qty from `tabBin`") - i = 0 - for d in bins: - i += 1 - reserved_qty = webnotes.conn.sql(""" - select - sum((dnpi_qty / so_item_qty) * (so_item_qty - so_item_delivered_qty)) - from - ( - select - qty as dnpi_qty, - ( - select qty from `tabSales Order Item` - where name = dnpi.parent_detail_docname - ) as so_item_qty, - ( - select ifnull(delivered_qty, 0) from `tabSales Order Item` - where name = dnpi.parent_detail_docname - ) as so_item_delivered_qty - from - ( - select qty, parent_detail_docname - from `tabDelivery Note Packing Item` dnpi_in - where item_code = %s and warehouse = %s - and parenttype="Sales Order" - and exists (select * from `tabSales Order` so - where name = dnpi_in.parent and docstatus = 1 and status != 'Stopped') - ) dnpi - ) tab - where - so_item_qty >= so_item_delivered_qty - """, (d[0], d[1])) - - if flt(d[3]) != flt(reserved_qty[0][0]): - webnotes.conn.sql(""" - update `tabBin` set reserved_qty = %s where name = %s - """, (reserved_qty and reserved_qty[0][0] or 0, d[2])) - -def cleanup_wrong_sle(): - sle = webnotes.conn.sql(""" - select item_code, warehouse, voucher_no, name - from `tabStock Ledger Entry` sle - where voucher_type = 'Delivery Note' - and not exists( - select name from `tabDelivery Note Packing Item` - where item_code = sle.item_code - and qty = abs(sle.actual_qty) - and parent = sle.voucher_no - ) and not exists ( - select name from `tabDelivery Note Item` - where item_code = sle.item_code - and qty = abs(sle.actual_qty) - and parent = sle.voucher_no - ) - """) - if sle: - for d in sle: - webnotes.conn.sql("update `tabStock Ledger Entry` set is_cancelled = 'Yes' where name = %s", d[3]) - create_comment(d[3]) - update_entries_after({ - "item_code": d[0], - "warehouse": d[1], - "posting_date": "2012-07-01", - "posting_time": "12:05" - }) - -def create_comment(dn): - from webnotes.model.doc import Document - cmt = Document('Comment') - cmt.comment = 'Cancelled by administrator due to wrong entry in packing list' - cmt.comment_by = 'Administrator' - cmt.comment_by_fullname = 'Administrator' - cmt.comment_doctype = 'Stock Ledger Entry' - cmt.comment_docname = dn - cmt.save(1) - \ No newline at end of file diff --git a/patches/july_2012/unicode_conf.py b/patches/july_2012/unicode_conf.py deleted file mode 100644 index cbbe5daeb18..00000000000 --- a/patches/july_2012/unicode_conf.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -def execute(): - """appends from __future__ import unicode_literals to py files if necessary""" - import wnf - wnf.append_future_import() \ No newline at end of file diff --git a/patches/july_2012/update_purchase_tax.py b/patches/july_2012/update_purchase_tax.py deleted file mode 100644 index 28f2ca7d8c3..00000000000 --- a/patches/july_2012/update_purchase_tax.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("""update `tabPurchase Taxes and Charges` - set category='Valuation and Total' where category='For Both'""") - webnotes.conn.sql("""update `tabPurchase Taxes and Charges` - set category='Valuation' where category='For Valuation'""") - webnotes.conn.sql("""update `tabPurchase Taxes and Charges` - set category='Total' where category='For Total'""") \ No newline at end of file diff --git a/patches/june_2012/__init__.py b/patches/june_2012/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/patches/june_2012/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/patches/june_2012/alter_tabsessions.py b/patches/june_2012/alter_tabsessions.py deleted file mode 100644 index 29ae17796f6..00000000000 --- a/patches/june_2012/alter_tabsessions.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.commit() - webnotes.conn.sql("alter table `tabSessions` modify user varchar(180)") - webnotes.conn.begin() \ No newline at end of file diff --git a/patches/june_2012/barcode_in_feature_setup.py b/patches/june_2012/barcode_in_feature_setup.py deleted file mode 100644 index 630f50f7891..00000000000 --- a/patches/june_2012/barcode_in_feature_setup.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.code import get_obj - fs = get_obj('Features Setup') - fs.doc.fs_item_barcode = 0 - fs.doc.save() - fs.validate() \ No newline at end of file diff --git a/patches/june_2012/cms2.py b/patches/june_2012/cms2.py deleted file mode 100644 index a9171d754ea..00000000000 --- a/patches/june_2012/cms2.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - - # sync doctypes required for the patch - webnotes.reload_doc('website', 'doctype', 'web_page') - webnotes.reload_doc('website', 'doctype', 'website_settings') - webnotes.reload_doc('stock', 'doctype', 'item') - - cleanup() - - save_pages() - - save_website_settings() - -def cleanup(): - import webnotes - - # delete pages from `tabPage` of module Website or of type Webpage - webnotes.conn.sql("""\ - delete from `tabPage` - where module='Website' and ifnull(web_page, 'No') = 'Yes'""") - - # change show_in_website value in item table to 0 or 1 - webnotes.conn.sql("""\ - update `tabItem` - set show_in_website = if(show_in_website = 'Yes', 1, 0) - where show_in_website is not null""") - - # move comments from comment_doctype Page to Blog - webnotes.conn.sql("""\ - update `tabComment` comm, `tabBlog` blog - set comm.comment_doctype = 'Blog', comm.comment_docname = blog.name - where comm.comment_docname = blog.page_name""") - - # delete deprecated pages - import webnotes.model - for page in ['products', 'contact', 'blog', 'about']: - try: - webnotes.model.delete_doc('Page', page) - except Exception, e: - webnotes.modules.patch_handler.log(unicode(e)) - - import os - import conf - # delete other html files - exception_list = ['app.html', 'unsupported.html', 'blank.html'] - conf_dir = os.path.dirname(os.path.abspath(conf.__file__)) - public_path = os.path.join(conf_dir, 'public') - for f in os.listdir(public_path): - if f.endswith('.html') and f not in exception_list: - os.remove(os.path.join(public_path, f)) - -def save_pages(): - """save all web pages, blogs to create content""" - query_map = { - 'Web Page': """select name from `tabWeb Page` where docstatus=0""", - 'Blog': """\ - select name from `tabBlog` - where docstatus = 0 and ifnull(published, 0) = 1""", - 'Item': """\ - select name from `tabItem` - where docstatus = 0 and ifnull(show_in_website, 0) = 1""", - } - - import webnotes - from webnotes.model.bean import Bean - import webnotes.modules.patch_handler - - for dt in query_map: - for result in webnotes.conn.sql(query_map[dt], as_dict=1): - try: - Bean(dt, result['name'].encode('utf-8')).save() - except Exception, e: - webnotes.modules.patch_handler.log(unicode(e)) - -def save_website_settings(): - from webnotes.model.code import get_obj - - # rewrite pages - get_obj('Website Settings').on_update() - - ss = get_obj('Style Settings') - ss.validate() - ss.doc.save() - ss.on_update() \ No newline at end of file diff --git a/patches/june_2012/copy_uom_for_pur_inv_item.py b/patches/june_2012/copy_uom_for_pur_inv_item.py deleted file mode 100644 index 947d11014d4..00000000000 --- a/patches/june_2012/copy_uom_for_pur_inv_item.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - - webnotes.reload_doc('buying', 'doctype', 'purchase_order_item') - webnotes.reload_doc('accounts', 'doctype', 'purchase_invoice_item') - webnotes.reload_doc('stock', 'doctype', 'purchase_receipt_item') - - webnotes.conn.sql("update `tabPurchase Invoice Item` t1, `tabPurchase Order Item` t2 set t1.uom = t2.uom where ifnull(t1.po_detail, '') != '' and t1.po_detail = t2.name") - webnotes.conn.sql("update `tabPurchase Invoice Item` t1, `tabPurchase Receipt Item` t2 set t1.uom = t2.uom where ifnull(t1.pr_detail, '') != '' and t1.pr_detail = t2.name") \ No newline at end of file diff --git a/patches/june_2012/delete_old_parent_entries.py b/patches/june_2012/delete_old_parent_entries.py deleted file mode 100644 index 384df775a5d..00000000000 --- a/patches/june_2012/delete_old_parent_entries.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """delete entries of child table having parent like old_par%% or ''""" - import webnotes - res = webnotes.conn.sql("""\ - select dt.name from `tabDocType` dt - where ifnull(dt.istable, 0)=1 and - exists ( - select * from `tabDocField` df - where df.fieldtype='Table' and - df.options=dt.name - )""") - for r in res: - if r[0]: - webnotes.conn.sql("""\ - delete from `tab%s` - where (ifnull(parent, '')='' or parent like "old_par%%") and - ifnull(parenttype, '')!=''""" % r[0]) \ No newline at end of file diff --git a/patches/june_2012/fetch_organization_from_lead.py b/patches/june_2012/fetch_organization_from_lead.py deleted file mode 100644 index 85b14364a9b..00000000000 --- a/patches/june_2012/fetch_organization_from_lead.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("update `tabQuotation` t1, `tabLead` t2 set t1.organization = t2.company_name where ifnull(t1.lead, '') != '' and t1.quotation_to = 'Lead' and ifnull(t1.organization, '') = '' and t1.lead = t2.name") \ No newline at end of file diff --git a/patches/june_2012/reports_list_permission.py b/patches/june_2012/reports_list_permission.py deleted file mode 100644 index c70b659656a..00000000000 --- a/patches/june_2012/reports_list_permission.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """allow read permission to all for report list""" - import webnotes - webnotes.conn.sql("""\ - delete from `tabDocPerm` - where parent in ('Report', 'Search Criteria')""") - - webnotes.reload_doc('core', 'doctype', 'report') diff --git a/patches/june_2012/series_unique_patch.py b/patches/june_2012/series_unique_patch.py deleted file mode 100644 index 4bea9f0f600..00000000000 --- a/patches/june_2012/series_unique_patch.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """add unique constraint to series table's name column""" - import webnotes - webnotes.conn.commit() - webnotes.conn.sql("alter table `tabSeries` add unique (name)") - webnotes.conn.begin() \ No newline at end of file diff --git a/patches/june_2012/set_recurring_type.py b/patches/june_2012/set_recurring_type.py deleted file mode 100644 index c7d4d63fcbb..00000000000 --- a/patches/june_2012/set_recurring_type.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.reload_doc('accounts', 'doctype', 'sales_invoice') - - webnotes.conn.sql("update `tabSales Invoice` set recurring_type = 'Monthly' where ifnull(convert_into_recurring_invoice, 0) = 1") \ No newline at end of file diff --git a/patches/june_2012/support_ticket_autoreply.py b/patches/june_2012/support_ticket_autoreply.py deleted file mode 100644 index a9c8d82d6f0..00000000000 --- a/patches/june_2012/support_ticket_autoreply.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """New Send Autoreply checkbox in Email Settings""" - import webnotes - import webnotes.utils - - webnotes.conn.commit() - webnotes.reload_doc('setup', 'doctype', 'email_settings') - webnotes.conn.begin() - - sync_support_mails = webnotes.utils.cint(webnotes.conn.get_value('Email Settings', - None, 'sync_support_mails')) - - if sync_support_mails: - webnotes.conn.set_value('Email Settings', None, 'send_autoreply', 1) \ No newline at end of file diff --git a/patches/june_2013/p08_shopping_cart_settings.py b/patches/june_2013/p08_shopping_cart_settings.py index 9378c389bd0..479a6961e26 100644 --- a/patches/june_2013/p08_shopping_cart_settings.py +++ b/patches/june_2013/p08_shopping_cart_settings.py @@ -4,7 +4,7 @@ import webnotes def execute(): - webnotes.reload_doc("website", "doctype", "shopping_cart_settings") + webnotes.reload_doc("selling", "doctype", "shopping_cart_settings") # create two default territories, one for home country and one named Rest of the World from setup.doctype.setup_control.setup_control import create_territories diff --git a/patches/june_2013/p09_update_global_defaults.py b/patches/june_2013/p09_update_global_defaults.py index 0f8131a663c..0fe9247f968 100644 --- a/patches/june_2013/p09_update_global_defaults.py +++ b/patches/june_2013/p09_update_global_defaults.py @@ -6,7 +6,6 @@ import webnotes def execute(): from_global_defaults = { "credit_controller": "Accounts Settings", - "auto_inventory_accounting": "Accounts Settings", "acc_frozen_upto": "Accounts Settings", "bde_auth_role": "Accounts Settings", "auto_indent": "Stock Settings", diff --git a/patches/mar_2012/__init__.py b/patches/mar_2012/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/patches/mar_2012/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/patches/mar_2012/add_fieldnames.py b/patches/mar_2012/add_fieldnames.py deleted file mode 100644 index 52cf6b7f821..00000000000 --- a/patches/mar_2012/add_fieldnames.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -# do not run this patch -from __future__ import unicode_literals -def execute(): - import webnotes - import webnotes.modules - forbidden = ['%', "'", '"', '#', '*', '?', '`', '(', ')', '<', '>', '-', - '\\', '/', '.', '&', '!', '@', '$', '^', '+'] - doctype_list = webnotes.conn.sql("SELECT name, module FROM `tabDocType`") - for doctype, module in doctype_list: - docfield_list = webnotes.conn.sql("""\ - SELECT name, label, fieldtype FROM `tabDocField` - WHERE parent = %s AND IFNULL(fieldname, '') = ''""", doctype) - field_type_count = {} - count = 0 - for name, label, fieldtype in docfield_list: - fieldname = None - if label: - temp_label = label - if len(temp_label)==1: - temp_label = fieldtype + temp_label - - fieldname = temp_label.lower().replace(' ', '_') - if "<" in fieldname: - count = field_type_count.setdefault(fieldtype, 0) - fieldname = fieldtype.lower().replace(' ', '_') + str(count) - field_type_count[fieldtype] = count + 1 - elif fieldtype: - count = field_type_count.setdefault(fieldtype, 0) - fieldname = fieldtype.lower().replace(' ', '_') + str(count) - field_type_count[fieldtype] = count + 1 - - if fieldname: - for f in forbidden: fieldname = fieldname.replace(f, '') - fieldname = fieldname.replace('__', '_') - if fieldname.endswith('_'): - fieldname = fieldname[:-1] - if fieldname.startswith('_'): - fieldname = fieldname[1:] - #print fieldname - webnotes.conn.sql("""\ - UPDATE `tabDocField` SET fieldname = %s - WHERE name = %s""", (fieldname, name)) - webnotes.modules.export_doc('DocType', doctype) diff --git a/patches/mar_2012/clean_property_setter.py b/patches/mar_2012/clean_property_setter.py deleted file mode 100644 index b4ad9096158..00000000000 --- a/patches/mar_2012/clean_property_setter.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes - -def execute(): - """ - * Remove unnecessary doctype properties - * Remove docfield property setters if fieldname doesn't exist - * Remove prev_field properties if value fieldname doesn't exist - """ - change_property_setter_fieldnames() - clean_doctype_properties() - clean_docfield_properties() - -def change_property_setter_fieldnames(): - webnotes.reload_doc('core', 'doctype', 'property_setter') - docfield_list = webnotes.conn.sql("""\ - SELECT name, fieldname FROM `tabDocField`""", as_list=1) - custom_field_list = webnotes.conn.sql("""\ - SELECT name, fieldname FROM `tabCustom Field`""", as_list=1) - field_list = docfield_list + custom_field_list - property_setter_list = webnotes.conn.sql("""\ - SELECT name, doc_name, value, property - FROM `tabProperty Setter` - WHERE doctype_or_field='DocField'""") - field_dict = dict(field_list) - for name, doc_name, value, prop in property_setter_list: - if doc_name in field_dict: - webnotes.conn.sql("""\ - UPDATE `tabProperty Setter` - SET field_name = %s - WHERE name = %s""", (field_dict.get(doc_name), name)) - if value in field_dict and prop=='previous_field': - webnotes.conn.sql("""\ - UPDATE `tabProperty Setter` - SET value = %s - WHERE name = %s""", (field_dict.get(value), name)) - -def clean_doctype_properties(): - desc = webnotes.conn.sql("DESC `tabDocType`", as_dict=1) - property_list = '", "'.join([d.get('Field') for d in desc]) - webnotes.conn.sql("""\ - DELETE FROM `tabProperty Setter` - WHERE doctype_or_field = 'DocType' - AND property NOT IN ("%s")""" % property_list) - -def clean_docfield_properties(): - delete_list_1 = webnotes.conn.sql("""\ - SELECT name FROM `tabProperty Setter` ps - WHERE doctype_or_field = 'DocField' - AND NOT EXISTS ( - SELECT fieldname FROM `tabDocField` df - WHERE df.parent = ps.doc_type - AND df.fieldname = ps.field_name - ) AND NOT EXISTS ( - SELECT fieldname FROM `tabCustom Field` cf - WHERE cf.dt = ps.doc_type - AND cf.fieldname = ps.field_name - )""") - - delete_list_2 = webnotes.conn.sql("""\ - SELECT name FROM `tabProperty Setter` ps - WHERE doctype_or_field = 'DocField' - AND property = 'previous_field' - AND NOT EXISTS ( - SELECT fieldname FROM `tabDocField` df - WHERE df.parent = ps.doc_type - AND df.fieldname = ps.value - ) AND NOT EXISTS ( - SELECT fieldname FROM `tabCustom Field` cf - WHERE cf.dt = ps.doc_type - AND cf.fieldname = ps.value - )""") - - delete_list = [d[0] for d in delete_list_1] + [d[0] for d in delete_list_2] - - webnotes.conn.sql("""\ - DELETE FROM `tabProperty Setter` - WHERE NAME IN ("%s")""" % '", "'.join(delete_list)) diff --git a/patches/mar_2012/cleanup_control_panel.py b/patches/mar_2012/cleanup_control_panel.py deleted file mode 100644 index 6692bc441e0..00000000000 --- a/patches/mar_2012/cleanup_control_panel.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -def execute(): - webnotes.conn.sql("""\ - DELETE FROM `tabSingles` - WHERE doctype = 'Control Panel' - AND field IN ("sync_with_gateway", "mail_password", "auto_email_id", - "mail_port", "outgoing_mail_server", "mail_login", "use_ssl")""") diff --git a/patches/mar_2012/create_custom_fields.py b/patches/mar_2012/create_custom_fields.py deleted file mode 100644 index b9fe2f04378..00000000000 --- a/patches/mar_2012/create_custom_fields.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -# do not run this patch -from __future__ import unicode_literals -field_list = [ -['Contact', 'notes'], -['Contact', 'birthday'], -['Contact', 'anniversary'], -['Customer', 'state_tax_type'], -['Customer', 'tin_no'], -['Customer', 'excise_registration_number'], -['Customer', 'customer_discount_details'], -['Customer', 'notes'], -['Customer', 'follow_up_section'], -['Customer', 'follow_up'], -['Delivery Note', 'shipping_contact_no'], -['Delivery Note', 'shipping_tin_no'], -['Delivery Note', 'shipping_excise_no'], -['Delivery Note', 'tin_no'], -['Delivery Note', 'excise_no'], -['Delivery Note Detail', 'cetsh_number'], -['Item', 'base_material'], -['Item', 'tool_type'], -['Item', 'no_of_flutes'], -['Item', 'special_treatment'], -['Item', 'length'], -['Item', 'width'], -['Item', 'height_dia'], -['Item', 'pl_item'], -['Item', 'cetsh_number'], -['Item', 'stock_maintained'], -['Item', 'is_rm'], -['Journal Voucher Detail', 'line_remarks'], -['Lead', 'designation'], -['Purchase Order', 'challan_number'], -['Quotation', 'cust_enq_no'], -['Quotation', 'enq_date'], -['Quotation', 'quote_valid'], -['Quotation', 'due_date'], -['Receivable Voucher', 'voucher_time'], -['Receivable Voucher', 'removal_time'], -['Receivable Voucher', 'removal_date'], -['Receivable Voucher', 'shipping_address'], -['Receivable Voucher', 'shipping_location'], -['Receivable Voucher', 'ship_to'], -['Receivable Voucher', 'shipping_contact_no'], -['Receivable Voucher', 'shipping_excise_no'], -['Receivable Voucher', 'shipping_tin_no'], -['Receivable Voucher', 'po_no'], -['Receivable Voucher', 'po_date'], -['Receivable Voucher', 'lr_no'], -['Receivable Voucher', 'transporters'], -['Receivable Voucher', 'ship_terms'], -['Receivable Voucher', 'tin_no'], -['Receivable Voucher', 'excise_no'], -['RV Detail', 'cetsh_number'], -['Sales Order', 'shipping_contact_no'], -['Sales Order', 'shipping_tin_no'], -['Sales Order', 'shipping_excise_no'], -['Sales Order', 'tin_no'], -['Sales Order', 'excise_number'], -['Sales Order Detail', 'cetsh_number'], -['Sales Order Detail', 'prd_notes'], -['Shipping Address', 'phone_no'], -['Shipping Address', 'tin_no'], -['Shipping Address', 'excise_no'], -['Stock Entry', 'process_custom'], -['Stock Entry', 'city'], -['Stock Entry', 'address_line_2'], -['Stock Entry', 'address_line_1'], -['Stock Entry', 'comp_other'], -['Stock Entry', 'mobile_no'], -['Stock Entry', 'phone_no'], -['Stock Entry', 'country'], -['Stock Entry', 'state'], -['Stock Entry', 'challan_number'], -['Stock Entry Detail', 'machine'], -['Stock Entry Detail', 'worker'], -['Supplier', 'notes'], -['Supplier', 'purchase_other_charges'], -['Supplier', 'tax_details'], -['Supplier', 'tin_number'], -['Supplier', 'excise_regd_number'], -['Supplier', 'service_tax_regd_number'], -['Warehouse', 'comp_other'], -['Warehouse', 'process'], -['Warehouse', 'country'], -['Warehouse', 'tax_registration_number'], -['Warehouse Type', 'process'], -['Workstation', 'maintenance_data'], -] - - -import webnotes -from webnotes.model.code import get_obj -from webnotes.model.doc import Document - -def execute(): - webnotes.reload_doc('core', 'doctype', 'custom_field') - for f in field_list: - res = webnotes.conn.sql("""SELECT name FROM `tabCustom Field` - WHERE dt=%s AND fieldname=%s""", (f[0], f[1])) - if res: continue - docfield = webnotes.conn.sql("""SELECT * FROM `tabDocField` - WHERE parent=%s AND fieldname=%s""", (f[0], f[1]), as_dict=1) - if not docfield: continue - custom_field = docfield[0] - - # scrub custom field dict - custom_field['dt'] = custom_field['parent'] - del custom_field['parent'] - - d = Document('Custom Field', fielddata=custom_field) - d.name = custom_field['dt'] + '-' + custom_field['fieldname'] - d.save(1, ignore_fields=1) - #obj = get_obj(doc=d) - #obj.on_update() diff --git a/patches/mar_2012/delete_docformat.py b/patches/mar_2012/delete_docformat.py deleted file mode 100644 index b6ee2ebd81a..00000000000 --- a/patches/mar_2012/delete_docformat.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("DELETE FROM `tabDocField` WHERE options='DocFormat'") - webnotes.conn.sql("DELETE FROM `tabDocField` WHERE parent='DocFormat'") - webnotes.conn.sql("DELETE FROM `tabDocType` WHERE name='DocFormat'") - webnotes.conn.commit() - webnotes.conn.sql("DROP TABLE `tabDocFormat`") - webnotes.conn.begin() diff --git a/patches/mar_2012/doctype_get_refactor.py b/patches/mar_2012/doctype_get_refactor.py deleted file mode 100644 index 0900b9b78ee..00000000000 --- a/patches/mar_2012/doctype_get_refactor.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -def execute(): - """ - * Custom Field changes - * Add file_list to required tables - * Change floats/currency to decimal(18, 6) - * Remove DocFormat from DocType's fields - * Remove 'no_column' from DocField - * Drop table DocFormat - """ - import webnotes.model.sync - webnotes.model.sync.sync_all(force=1) - - handle_custom_fields() - create_file_list() - - # do at last - needs commit due to DDL statements - change_to_decimal() - -def handle_custom_fields(): - """ - * Assign idx to custom fields - * Create property setter entry of previous field - * Remove custom fields from tabDocField - """ - cf = get_cf() - assign_idx(cf) - create_prev_field_prop_setter(cf) - remove_custom_from_docfield(cf) - -def get_cf(): - return webnotes.conn.sql("""\ - SELECT * FROM `tabCustom Field` - WHERE docstatus < 2""", as_dict=1) - -def assign_idx(cf): - from webnotes.model.doctype import get - from webnotes.utils import cint - #print len(cf) - for f in cf: - #print f.get('dt'), f.get('name') - if f.get('idx'): continue - temp_doclist = get(f.get('dt'), form=0) - #print len(temp_doclist) - max_idx = max(d.idx for d in temp_doclist if d.doctype=='DocField') - if not max_idx: continue - webnotes.conn.sql("""\ - UPDATE `tabCustom Field` SET idx=%s - WHERE name=%s""", (cint(max_idx)+1, f.get('name'))) - -def create_prev_field_prop_setter(cf): - from webnotes.model.doc import Document - from core.doctype.custom_field.custom_field import get_fields_label - for f in cf: - idx_label_list, field_list = get_fields_label(f.get('dt'), 0) - temp_insert_after = (f.get('insert_after') or '').split(" - ") - if len(temp_insert_after)<=1: continue - similar_idx_label = [il for il in idx_label_list \ - if temp_insert_after[0] in il] - if not similar_idx_label: continue - label_index = idx_label_list.index(similar_idx_label[0]) - if label_index==-1: return - - webnotes.conn.sql("""\ - UPDATE `tabCustom Field` - SET insert_after = %s - WHERE name = %s""", (similar_idx_label[0], f.get('name'))) - - prev_field = field_list[label_index] - res = webnotes.conn.sql("""\ - SELECT name FROM `tabProperty Setter` - WHERE doc_type = %s - AND field_name = %s - AND property = 'previous_field'""", (f.get('dt'), f.get('fieldname'))) - - if not res: - ps = Document('Property Setter', fielddata = { - 'doctype_or_field': 'DocField', - 'doc_type': f.get('dt'), - 'field_name': f.get('fieldname'), - 'property': 'previous_field', - 'value': prev_field, - 'property_type': 'Data', - 'select_doctype': f.get('dt') - }) - ps.save(1) - -def remove_custom_from_docfield(cf): - for f in cf: - webnotes.conn.sql("""\ - DELETE FROM `tabDocField` - WHERE parent=%s AND fieldname=%s""", (f.get('dt'), - f.get('fieldname'))) - -def create_file_list(): - should_exist = ['Website Settings', 'Web Page', 'Timesheet', 'Task', - 'Support Ticket', 'Supplier', 'Style Settings', 'Stock Reconciliation', - 'Stock Entry', 'Serial No', 'Sales Order', 'Sales Invoice', - 'Quotation', 'Question', 'Purchase Receipt', 'Purchase Order', - 'Project', 'Profile', 'Production Order', 'Product', 'Print Format', - 'Price List', 'Purchase Invoice', 'Page', - 'Maintenance Visit', 'Maintenance Schedule', 'Letter Head', - 'Leave Application', 'Lead', 'Journal Voucher', 'Item', 'Material Request', - 'Expense Claim', 'Opportunity', 'Employee', 'Delivery Note', - 'Customer Issue', 'Customer', 'Contact Us Settings', 'Company', - 'Bulk Rename Tool', 'Blog', 'BOM', 'About Us Settings'] - - from webnotes.model.code import get_obj - - for dt in should_exist: - obj = get_obj('DocType', dt, with_children=1) - obj.doc.allow_attach = 1 - obj.doc.save() - obj.make_file_list() - from webnotes.model.db_schema import updatedb - updatedb(obj.doc.name) - - webnotes.clear_cache(doctype=obj.doc.name) - -def change_to_decimal(): - webnotes.conn.commit() - tables = webnotes.conn.sql("SHOW TABLES") - alter_tables_list = [] - for tab in tables: - if not tab: continue - desc = webnotes.conn.sql("DESC `%s`" % tab[0], as_dict=1) - flist = [] - for d in desc: - if d.get('Type')=='decimal(14,2)': - flist.append(d.get('Field')) - if flist: - #print tab[0], flist - statements = ("MODIFY `%s` decimal(18,6)" % f for f in flist) - statements = ", \n".join(statements) - alter_tables_list.append("ALTER TABLE `%s` \n%s\n" % (tab[0], - statements)) - - #print "\n\n".join(alter_tables_list) - for at in alter_tables_list: - webnotes.conn.sql(at) - - webnotes.conn.begin() - diff --git a/patches/mar_2012/earning_deduction_type_patch.py b/patches/mar_2012/earning_deduction_type_patch.py deleted file mode 100644 index 43a14338b97..00000000000 --- a/patches/mar_2012/earning_deduction_type_patch.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql(""" - UPDATE `tabDocField` - SET fieldtype = 'Link', options = 'Deduction Type' - WHERE parent = 'Deduction Detail' - AND fieldname = 'd_type' - """) - webnotes.conn.sql(""" - UPDATE `tabDocField` - SET fieldtype = 'Link', options = 'Earning Type' - WHERE parent = 'Earning Detail' - AND fieldname = 'e_type' - """) diff --git a/patches/mar_2012/is_submittable_patch.py b/patches/mar_2012/is_submittable_patch.py deleted file mode 100644 index b2da16cb2ac..00000000000 --- a/patches/mar_2012/is_submittable_patch.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -# dont run this patch -from __future__ import unicode_literals -def execute(): - import webnotes - import webnotes.model.doctype - from webnotes.utils import cint - from webnotes.model.doc import Document - from webnotes.model.code import get_obj - doctype_list = webnotes.conn.sql("SELECT name FROM `tabDocType`") - for dt in doctype_list: - doclist = webnotes.model.doctype.get(dt[0], form=0) - is_submittable = 0 - for d in doclist: - if d.doctype == 'DocPerm' and d.fields.get('permlevel') == 0 \ - and cint(d.fields.get('submit')) == 1: - is_submittable = 1 - break - if is_submittable: - dt_doc = Document('DocType', doclist[0].name) - dt_doc.is_submittable = 1 - dt_doc.save() - obj = get_obj(doc=dt_doc) - obj.make_amendable() - obj.on_update() diff --git a/patches/mar_2012/pos_invoice_fix.py b/patches/mar_2012/pos_invoice_fix.py deleted file mode 100644 index 148b3739879..00000000000 --- a/patches/mar_2012/pos_invoice_fix.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - from webnotes.modules import reload_doc - reload_doc('accounts', 'Print Format', 'POS Invoice') diff --git a/patches/mar_2012/usertags.py b/patches/mar_2012/usertags.py deleted file mode 100644 index 4cb9b14cd70..00000000000 --- a/patches/mar_2012/usertags.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - doctype_list = webnotes.conn.sql("""SELECT name FROM `tabDocType` - WHERE docstatus<2 AND IFNULL(issingle, 0)=0 - AND IFNULL(istable, 0)=0""") - webnotes.conn.commit() - for d in doctype_list: - add_col = True - desc = webnotes.conn.sql("DESC `tab%s`" % d[0], as_dict=1) - for td in desc: - if td.get('Field')=='_user_tags': - add_col = False - - if add_col: - webnotes.conn.sql("alter table `tab%s` add column `_user_tags` varchar(180)" % d[0]) - webnotes.conn.begin() - diff --git a/patches/march_2013/p03_update_buying_amount.py b/patches/march_2013/p03_update_buying_amount.py deleted file mode 100644 index ddb52e0277b..00000000000 --- a/patches/march_2013/p03_update_buying_amount.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -import webnotes -from webnotes.utils import now_datetime - -def execute(): - webnotes.reload_doc("stock", "doctype", "delivery_note_item") - webnotes.reload_doc("accounts", "doctype", "sales_invoice_item") - - webnotes.conn.auto_commit_on_many_writes = True - for company in webnotes.conn.sql("select name from `tabCompany`"): - stock_ledger_entries = webnotes.conn.sql("""select item_code, voucher_type, voucher_no, - voucher_detail_no, posting_date, posting_time, stock_value, - warehouse, actual_qty as qty from `tabStock Ledger Entry` - where ifnull(`is_cancelled`, "No") = "No" and company = %s - order by item_code desc, warehouse desc, - posting_date desc, posting_time desc, name desc""", company[0], as_dict=True) - - dn_list = webnotes.conn.sql("""select name from `tabDelivery Note` - where docstatus < 2 and company = %s""", company[0]) - - for dn in dn_list: - dn = webnotes.get_obj("Delivery Note", dn[0], with_children = 1) - dn.set_buying_amount(stock_ledger_entries) - - si_list = webnotes.conn.sql("""select name from `tabSales Invoice` - where docstatus < 2 and company = %s""", company[0]) - for si in si_list: - si = webnotes.get_obj("Sales Invoice", si[0], with_children = 1) - si.set_buying_amount(stock_ledger_entries) - - webnotes.conn.auto_commit_on_many_writes = False \ No newline at end of file diff --git a/patches/march_2013/p08_create_aii_accounts.py b/patches/march_2013/p08_create_aii_accounts.py index 03ba36ca7e5..c02b5a2e837 100644 --- a/patches/march_2013/p08_create_aii_accounts.py +++ b/patches/march_2013/p08_create_aii_accounts.py @@ -3,12 +3,12 @@ import webnotes def execute(): + webnotes.reload_doc("stock", "doctype", "warehouse") webnotes.reload_doc("setup", "doctype", "company") webnotes.reload_doc("accounts", "doctype", "cost_center") create_chart_of_accounts_if_not_exists() add_group_accounts() add_ledger_accounts() - add_aii_cost_center() set_default_accounts() def set_default_accounts(): @@ -45,7 +45,6 @@ def add_group_accounts(): def add_ledger_accounts(): accounts_to_add = [ - ["Stock In Hand", "Stock Assets", "Ledger", ""], ["Cost of Goods Sold", "Stock Expenses", "Ledger", "Expense Account"], ["Stock Adjustment", "Stock Expenses", "Ledger", "Expense Account"], ["Expenses Included In Valuation", "Stock Expenses", "Ledger", "Expense Account"], @@ -79,31 +78,19 @@ def add_accounts(accounts_to_add, check_fn=None): "company": company }) account.insert() - -def add_aii_cost_center(): - for company, abbr in webnotes.conn.sql("""select name, abbr from `tabCompany`"""): - if not webnotes.conn.sql("""select name from `tabCost Center` where cost_center_name = - 'Auto Inventory Accounting' and company = %s""", company): - parent_cost_center = webnotes.conn.get_value("Cost Center", - {"parent_cost_center['']": '', "company": company}) - - if not parent_cost_center: - webnotes.errprint("Company " + company + "does not have a root cost center") - continue - - cc = webnotes.bean({ - "doctype": "Cost Center", - "cost_center_name": "Auto Inventory Accounting", - "parent_cost_center": parent_cost_center, - "group_or_ledger": "Ledger", - "company": company - }) - cc.insert() + def create_chart_of_accounts_if_not_exists(): for company in webnotes.conn.sql("select name from `tabCompany`"): if not webnotes.conn.sql("select * from `tabAccount` where company = %s", company[0]): webnotes.conn.sql("""update `tabCompany` set receivables_group = '', - payables_group = '' where name = %s""", company[0]) + payables_group = '', default_bank_account = '', default_cash_account = '', + default_expense_account = '', default_income_account = '', cost_center = '', + stock_received_but_not_billed = '', stock_adjustment_account = '', + expenses_included_in_valuation = '' where name = %s""", company[0]) + + webnotes.conn.sql("""update `tabCompany` set domain = 'Services' + where name = %s and ifnull(domain, '') = ''""", company[0]) + webnotes.bean("Company", company[0]).save() \ No newline at end of file diff --git a/patches/may_2012/__init__.py b/patches/may_2012/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/patches/may_2012/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/patches/may_2012/cleanup_notification_control.py b/patches/may_2012/cleanup_notification_control.py deleted file mode 100644 index 054f7ec9070..00000000000 --- a/patches/may_2012/cleanup_notification_control.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql(""" - delete from `tabSingles` - where doctype='Notification Control' - and field in ( - 'payable_voucher', - 'payment_received_message', - 'payment_sent_message', - 'enquiry') - """) - ren_list = [ - ['expense_voucher', 'expense_claim'], - ['receivable_voucher', 'sales_invoice'], - ['enquiry', 'opportunity'], - ] - for r in ren_list: - webnotes.conn.sql(""" - update `tabSingles` - set field=%s - where field=%s - and doctype='Notification Control' - """, (r[1], r[0])) - - webnotes.conn.commit() - webnotes.conn.begin() - webnotes.reload_doc('setup', 'doctype', 'notification_control') \ No newline at end of file diff --git a/patches/may_2012/cleanup_property_setter.py b/patches/may_2012/cleanup_property_setter.py deleted file mode 100644 index b793e9d7bb5..00000000000 --- a/patches/may_2012/cleanup_property_setter.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("delete from `tabProperty Setter` where property in ('width', 'previous_field')") - - webnotes.conn.sql("delete from `tabSingles` where field = 'footer_font_color' and doctype = 'Style Settings'") diff --git a/patches/may_2012/clear_session_cache.py b/patches/may_2012/clear_session_cache.py deleted file mode 100644 index 3e63c3b2fc8..00000000000 --- a/patches/may_2012/clear_session_cache.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("delete from __SessionCache") \ No newline at end of file diff --git a/patches/may_2012/create_report_manager_role.py b/patches/may_2012/create_report_manager_role.py deleted file mode 100644 index 56f2aa9cd91..00000000000 --- a/patches/may_2012/create_report_manager_role.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.doc import Document - - if not webnotes.conn.sql("select name from `tabRole` where name = 'Report Manager'"): - r = Document('Role') - r.role_name = 'Report Manager' - r.module = 'Core' - r.save() \ No newline at end of file diff --git a/patches/may_2012/cs_server_readonly.py b/patches/may_2012/cs_server_readonly.py deleted file mode 100644 index 378a73da11d..00000000000 --- a/patches/may_2012/cs_server_readonly.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """Make server custom script readonly for system manager""" - import webnotes.model.doc - new_perms = [ - { - 'parent': 'Custom Script', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'System Manager', - 'permlevel': 1, - 'read': 1, - }, - { - 'parent': 'Custom Script', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'Administrator', - 'permlevel': 1, - 'read': 1, - 'write': 1 - }, - ] - for perms in new_perms: - doc = webnotes.model.doc.Document('DocPerm') - doc.fields.update(perms) - doc.save() - webnotes.conn.commit() - webnotes.conn.begin() - webnotes.reload_doc('core', 'doctype', 'custom_script') \ No newline at end of file diff --git a/patches/may_2012/customize_form_cleanup.py b/patches/may_2012/customize_form_cleanup.py deleted file mode 100644 index ab86f0583cc..00000000000 --- a/patches/may_2012/customize_form_cleanup.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.conn.sql("delete from `tabCustomize Form Field`") - webnotes.conn.sql("""delete from `tabSingles` - where doctype='Customize Form'""") \ No newline at end of file diff --git a/patches/may_2012/page_role_series_fix.py b/patches/may_2012/page_role_series_fix.py deleted file mode 100644 index c2b18f4d7d1..00000000000 --- a/patches/may_2012/page_role_series_fix.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - sr = webnotes.conn.sql("select max(name) from `tabPage Role`") - if sr and sr[0][0].startswith('PR'): - webnotes.conn.sql("update tabSeries set current = %s where name = 'PR'", int(sr[0][0][2:])) diff --git a/patches/may_2012/profile_perm_patch.py b/patches/may_2012/profile_perm_patch.py deleted file mode 100644 index 26fc1ab03cc..00000000000 --- a/patches/may_2012/profile_perm_patch.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """Make profile readonly for role All""" - import webnotes.model.doc - webnotes.conn.sql("delete from `tabDocPerm` where parent='Profile' and role='All'") - new_perms = [ - { - 'parent': 'Profile', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'All', - 'permlevel': 0, - 'read': 1, - }, - ] - for perms in new_perms: - doc = webnotes.model.doc.Document('DocPerm') - doc.fields.update(perms) - doc.save() - webnotes.conn.commit() - webnotes.conn.begin() - webnotes.reload_doc('core', 'doctype', 'profile') \ No newline at end of file diff --git a/patches/may_2012/reload_sales_invoice_pf.py b/patches/may_2012/reload_sales_invoice_pf.py deleted file mode 100644 index 72a8e4100e4..00000000000 --- a/patches/may_2012/reload_sales_invoice_pf.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - import webnotes.modules - res = webnotes.conn.sql("""\ - select module, name, standard from `tabPrint Format` - where name like 'Sales Invoice%'""", as_dict=1) - for r in res: - if r.get('standard')=='Yes' and \ - r.get('name') in [ - 'Sales Invoice Classic', - 'Sales Invoice Spartan', - 'Sales Invoice Modern' - ]: - webnotes.modules.reload_doc(r.get('module'), 'Print Format', r.get('name')) - \ No newline at end of file diff --git a/patches/may_2012/remove_communication_log.py b/patches/may_2012/remove_communication_log.py deleted file mode 100644 index 0c06f8d6bb4..00000000000 --- a/patches/may_2012/remove_communication_log.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - webnotes.reload_doc('support', 'doctype', 'communication') - webnotes.conn.commit() - webnotes.conn.begin() - - # change doctype property setter and custom fields, and save them - move_customizations() - - try: - remove_communication_log() - except Exception, e: - if e.args[0] != 1146: - raise e - -def move_customizations(): - import webnotes.model.doc - import webnotes.model.doctype - - res = webnotes.conn.sql("""\ - delete from `tabProperty Setter` - where property='previous_field' - and doc_type = 'Communication Log'""") - - res = webnotes.conn.sql("""\ - select name from `tabCustom Field` - where dt='Communication Log'""") - for r in res: - d = webnotes.model.doc.Document('Custom Field', r[0]) - d.dt = 'Communication' - d.save() - from webnotes.model.db_schema import updatedb - updatedb('Communication') - - res = webnotes.conn.sql("""\ - select field_name from `tabProperty Setter` - where doc_type='Communication Log' and field_name is not null""") - - doclist = webnotes.model.doctype.get('Communication', 0) - field_list = [d.fieldname for d in doclist if d.doctype=='DocField'] - for r in res: - if r[0] in field_list: - webnotes.conn.sql("""\ - update `tabProperty Setter` - set doc_type = 'Communication' - where field_name=%s and doc_type='Communication Log'""", r[0]) - - webnotes.conn.sql("""\ - delete from `tabProperty Setter` - where doc_type='Communication Log'""") - - webnotes.clear_cache(doctype="Communication") - -def remove_communication_log(): - import webnotes - import webnotes.model - import webnotes.model.doc - import webnotes.model.doctype - - webnotes.conn.auto_commit_on_many_writes = True - - # get all communication log records - comm_log_list = webnotes.conn.sql("select * from `tabCommunication Log`", - as_dict=1) - - field_list = [d.fieldname for d in \ - webnotes.model.doctype.get('Communication', 0) \ - if d.doctype=='DocField'] - - # copy it to communication - for comm_log in comm_log_list: - d = webnotes.model.doc.Document('Communication') - - for key in comm_log.keys(): - if key not in webnotes.model.default_fields: - d.fields[key] = comm_log[key] - - parenttype = (comm_log.get('parenttype') or '').lower() - if parenttype in field_list: - d.fields[parenttype] = comm_log.get('parent') - - d.naming_series = 'COMM-' - d.subject = 'Follow Up' - d.content = comm_log.get('notes') or '' - d.medium = comm_log.get('follow_up_type') or '' - d.sales_person = comm_log.get('follow_up_by') - d.communication_date = comm_log.get('date') - d.category = 'Miscellaneous' - d.action = 'No Action' - d.save(ignore_fields=1) - - # delete records with parent type "Customer", "Lead", "Supplier" - webnotes.conn.sql("""\ - delete from `tabCommunication Log` - where parenttype in ('Customer', 'Lead', 'Supplier', - 'Opportunity', 'Quotation')""") - - # if all records deleted, drop table communication log - # and delete doctype communication log - # if for some reason, records remain, dont drop table and dont delete doctype - count = webnotes.conn.sql("select count(*) from `tabCommunication Log`")[0][0] - if not count: - webnotes.model.delete_doc('DocType', 'Communication Log') - webnotes.conn.commit() - webnotes.conn.sql("drop table `tabCommunication Log`") - webnotes.conn.begin() \ No newline at end of file diff --git a/patches/may_2012/remove_euro_currency.py b/patches/may_2012/remove_euro_currency.py deleted file mode 100644 index 02e439b63a0..00000000000 --- a/patches/may_2012/remove_euro_currency.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """ - * Replace EURO with EUR - * Delete EURO from tabCurrency - """ - import webnotes - tables = webnotes.conn.sql("show tables") - for (tab,) in tables: - desc = webnotes.conn.sql("desc `%s`" % tab, as_dict=1) - for d in desc: - if "currency" in d.get('Field'): - field = d.get('Field') - webnotes.conn.sql("""\ - update `%s` set `%s`='EUR' - where `%s`='EURO'""" % (tab, field, field)) - webnotes.conn.sql("update `tabSingles` set value='EUR' where value='EURO'") - webnotes.conn.sql("delete from `tabCurrency` where name='EURO'") \ No newline at end of file diff --git a/patches/may_2012/rename_prev_doctype.py b/patches/may_2012/rename_prev_doctype.py deleted file mode 100644 index 81a2578169a..00000000000 --- a/patches/may_2012/rename_prev_doctype.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - dt_list = webnotes.conn.sql("select parent, fieldname from `tabDocField` where fieldname in ('against_doctype', 'prevdoc_doctype')") - - ren_dt = { - 'Indent' : 'Material Request', - 'Enquiry' : 'Opportunity', - 'Receivable Voucher' : 'Sales Invoice', - 'Payable Voucher' : 'Purchase Invoice' - } - - for d in ren_dt: - for dt in dt_list: - webnotes.conn.sql("update `tab%s` set %s = '%s' where %s = '%s'" % (dt[0], dt[1], ren_dt[d], dt[1], d)) diff --git a/patches/may_2012/same_purchase_rate_patch.py b/patches/may_2012/same_purchase_rate_patch.py deleted file mode 100644 index edb135f8bdc..00000000000 --- a/patches/may_2012/same_purchase_rate_patch.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.model.code import get_obj - gd = get_obj('Global Defaults') - gd.doc.maintain_same_rate = 1 - gd.doc.save() - gd.on_update() - \ No newline at end of file diff --git a/patches/may_2012/std_pf_readonly.py b/patches/may_2012/std_pf_readonly.py deleted file mode 100644 index f2e7261f634..00000000000 --- a/patches/may_2012/std_pf_readonly.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - """Make standard print formats readonly for system manager""" - import webnotes.model.doc - new_perms = [ - { - 'parent': 'Print Format', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'System Manager', - 'permlevel': 1, - 'read': 1, - }, - { - 'parent': 'Print Format', - 'parentfield': 'permissions', - 'parenttype': 'DocType', - 'role': 'Administrator', - 'permlevel': 1, - 'read': 1, - 'write': 1 - }, - ] - for perms in new_perms: - doc = webnotes.model.doc.Document('DocPerm') - doc.fields.update(perms) - doc.save() - webnotes.conn.commit() - webnotes.conn.begin() - webnotes.reload_doc('core', 'doctype', 'print_format') \ No newline at end of file diff --git a/patches/may_2012/stock_reco_patch.py b/patches/may_2012/stock_reco_patch.py deleted file mode 100644 index a3c702f2f73..00000000000 --- a/patches/may_2012/stock_reco_patch.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -def execute(): - import webnotes - from webnotes.modules import reload_doc - reload_doc('stock', 'doctype', 'stock_reconciliation') - - sr = webnotes.conn.sql("select name, file_list from `tabStock Reconciliation` where docstatus = 1") - for d in sr: - if d[1]: - filename = d[1].split(',')[1] - - from webnotes.utils import file_manager - fn, content = file_manager.get_file(filename) - - if not isinstance(content, basestring) and hasattr(content, 'tostring'): - content = content.tostring() - - webnotes.conn.sql("update `tabStock Reconciliation` set diff_info = %s where name = %s and ifnull(diff_info, '') = ''", (content, d[0])) diff --git a/patches/may_2013/p01_conversion_factor_and_aii.py b/patches/may_2013/p01_conversion_factor_and_aii.py deleted file mode 100644 index 17af01b639d..00000000000 --- a/patches/may_2013/p01_conversion_factor_and_aii.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. -# License: GNU General Public License v3. See license.txt - -import webnotes -from webnotes.utils import cint -from accounts.utils import create_stock_in_hand_jv - -def execute(): - webnotes.conn.auto_commit_on_many_writes = True - - aii_enabled = cint(webnotes.conn.get_value("Global Defaults", None, - "auto_inventory_accounting")) - - if aii_enabled: - create_stock_in_hand_jv(reverse = True) - - webnotes.conn.sql("""update `tabPurchase Invoice Item` pi_item - set conversion_factor = (select ifnull(if(conversion_factor=0, 1, conversion_factor), 1) - from `tabUOM Conversion Detail` - where parent = pi_item.item_code and uom = pi_item.uom limit 1 - ) - where ifnull(conversion_factor, 0)=0""") - - if aii_enabled: - create_stock_in_hand_jv() - - webnotes.conn.auto_commit_on_many_writes = False - - \ No newline at end of file diff --git a/patches/may_2013/p03_update_support_ticket.py b/patches/may_2013/p03_update_support_ticket.py index 005c9a3899b..d8e372362fe 100644 --- a/patches/may_2013/p03_update_support_ticket.py +++ b/patches/may_2013/p03_update_support_ticket.py @@ -11,4 +11,4 @@ def execute(): tic = webnotes.get_obj("Support Ticket", d.name) tic.set_lead_contact(d.raised_by) webnotes.conn.sql("""update `tabSupport Ticket` set lead = %s, contact = %s, company = %s - where name = %s""", (tic.doc.lead, tic.doc.contact, tic.doc.company, d.name)) \ No newline at end of file + where name = %s""", (tic.doc.lead, tic.doc.contact, tic.doc.company, d.name \ No newline at end of file diff --git a/patches/may_2013/p05_update_cancelled_gl_entries.py b/patches/may_2013/p05_update_cancelled_gl_entries.py index ece5bb2db31..18cefc8a02a 100644 --- a/patches/may_2013/p05_update_cancelled_gl_entries.py +++ b/patches/may_2013/p05_update_cancelled_gl_entries.py @@ -6,8 +6,7 @@ import webnotes from webnotes.utils import cint def execute(): - aii_enabled = cint(webnotes.conn.get_value("Global Defaults", None, - "auto_inventory_accounting")) + aii_enabled = cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) if aii_enabled: webnotes.conn.sql("""update `tabGL Entry` gle set is_cancelled = 'Yes' diff --git a/patches/patch_list.py b/patches/patch_list.py index 1a14a5f0b55..7041ba88585 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -10,60 +10,6 @@ patch_list = [ "execute:webnotes.reload_doc('core', 'doctype', 'docperm') #2013-07-16", "execute:webnotes.reload_doc('core', 'doctype', 'page') #2013-07-16", "execute:webnotes.reload_doc('core', 'doctype', 'report') #2013-07-16", - "patches.mar_2012.clean_property_setter", - "patches.april_2012.naming_series_patch", - "patches.mar_2012.cleanup_control_panel", - "patches.mar_2012.doctype_get_refactor", - "patches.mar_2012.delete_docformat", - "patches.mar_2012.usertags", - "patches.april_2012.reload_c_form", - "patches.april_2012.after_sync_cleanup", - "patches.april_2012.remove_default_from_rv_detail", - "patches.april_2012.update_role_in_address", - "patches.april_2012.update_permlevel_in_address", - "patches.april_2012.update_appraisal_permission", - "patches.april_2012.repost_stock_for_posting_time", - "patches.may_2012.cleanup_property_setter", - "patches.may_2012.rename_prev_doctype", - "patches.may_2012.cleanup_notification_control", - "patches.may_2012.stock_reco_patch", - "patches.may_2012.page_role_series_fix", - "patches.may_2012.reload_sales_invoice_pf", - "patches.may_2012.std_pf_readonly", - "patches.may_2012.customize_form_cleanup", - "patches.may_2012.cs_server_readonly", - "patches.may_2012.clear_session_cache", - "patches.may_2012.same_purchase_rate_patch", - "patches.may_2012.create_report_manager_role", - "patches.may_2012.profile_perm_patch", - "patches.may_2012.remove_euro_currency", - "patches.may_2012.remove_communication_log", - "patches.june_2012.barcode_in_feature_setup", - "patches.june_2012.copy_uom_for_pur_inv_item", - "patches.june_2012.fetch_organization_from_lead", - "patches.june_2012.reports_list_permission", - "patches.june_2012.support_ticket_autoreply", - "patches.june_2012.series_unique_patch", - "patches.june_2012.set_recurring_type", - "patches.june_2012.alter_tabsessions", - "patches.june_2012.delete_old_parent_entries", - "patches.april_2012.delete_about_contact", - "patches.july_2012.reload_pr_po_mapper", - "patches.july_2012.address_contact_perms", - "patches.july_2012.packing_list_cleanup_and_serial_no", - "patches.july_2012.deprecate_import_data_control", - "patches.july_2012.default_freeze_account", - "patches.july_2012.update_purchase_tax", - "patches.june_2012.cms2", - "patches.july_2012.auth_table", - "patches.july_2012.remove_event_role_owner_match", - "patches.july_2012.deprecate_bulk_rename", - "patches.july_2012.bin_permission", - "patches.july_2012.project_patch_repeat", - "patches.july_2012.repost_stock_due_to_wrong_packing_list", - "patches.august_2012.task_allocated_to_assigned", - "patches.august_2012.change_profile_permission", - "patches.august_2012.repost_billed_amt", "patches.september_2012.stock_report_permissions_for_accounts", "patches.september_2012.communication_delete_permission", "patches.september_2012.all_permissions_patch", @@ -200,7 +146,6 @@ patch_list = [ "patches.april_2013.rebuild_sales_browser", "patches.may_2013.p01_selling_net_total_export", "patches.may_2013.repost_stock_for_no_posting_time", - "patches.may_2013.p01_conversion_factor_and_aii", "patches.may_2013.p02_update_valuation_rate", "patches.may_2013.p03_update_support_ticket", "patches.may_2013.p04_reorder_level", @@ -248,14 +193,17 @@ patch_list = [ "patches.july_2013.p10_change_partner_user_to_website_user", "patches.july_2013.p11_update_price_list_currency", "execute:webnotes.bean('Selling Settings').save() #2013-07-29", + "execute:webnotes.reload_doc('accounts', 'doctype', 'accounts_settings') # 2013-09-24", + "patches.august_2013.p01_auto_accounting_for_stock_patch", "patches.august_2013.p01_hr_settings", "patches.august_2013.p02_rename_price_list", "patches.august_2013.p03_pos_setting_replace_customer_account", "patches.august_2013.p05_update_serial_no_status", "patches.august_2013.p05_employee_birthdays", "execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-08-16", + "execute:webnotes.delete_doc('DocType', 'Stock Ledger')", + "patches.august_2013.p06_deprecate_is_cancelled", "patches.august_2013.p06_fix_sle_against_stock_entry", - "execute:webnotes.bean('Style Settings').save() #2013-08-20", "patches.september_2013.p01_add_user_defaults_from_pos_setting", "execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-09-02", "patches.september_2013.p01_fix_buying_amount_gl_entries", @@ -263,6 +211,11 @@ patch_list = [ "execute:webnotes.reload_doc('setup', 'doctype', 'features_setup') # 2013-09-05", "patches.september_2013.p02_fix_serial_no_status", "patches.september_2013.p03_modify_item_price_include_in_price_list", + "patches.august_2013.p06_deprecate_is_cancelled", + "execute:webnotes.delete_doc('DocType', 'Budget Control')", "patches.september_2013.p03_update_stock_uom_in_sle", "patches.september_2013.p03_move_website_to_framework", + "execute:webnotes.bean('Style Settings').save() #2013-09-19", + "execute:webnotes.conn.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'Accounts Manager') # 2013-09-24", + "patches.september_2013.p04_unsubmit_serial_nos", ] \ No newline at end of file diff --git a/patches/september_2013/p02_fix_serial_no_status.py b/patches/september_2013/p02_fix_serial_no_status.py index 714cd7a3778..58c29578156 100644 --- a/patches/september_2013/p02_fix_serial_no_status.py +++ b/patches/september_2013/p02_fix_serial_no_status.py @@ -17,7 +17,7 @@ def execute(): serial_nos = d.serial_no.split("\n") for sr in serial_nos: serial_no = sr.strip() - if serial_no: + if serial_no and webnotes.conn.exists("Serial No", serial_no): serial_bean = webnotes.bean("Serial No", serial_no) if serial_bean.doc.status == "Not Available": latest_sle = webnotes.conn.sql("""select voucher_no from `tabStock Ledger Entry` diff --git a/patches/july_2012/bin_permission.py b/patches/september_2013/p04_unsubmit_serial_nos.py similarity index 63% rename from patches/july_2012/bin_permission.py rename to patches/september_2013/p04_unsubmit_serial_nos.py index 3f9b65cd93f..ded4e4ded5e 100644 --- a/patches/july_2012/bin_permission.py +++ b/patches/september_2013/p04_unsubmit_serial_nos.py @@ -2,6 +2,6 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import webnotes def execute(): - import webnotes - webnotes.conn.sql("update `tabDocPerm` set permlevel = 0 where parent = 'Bin'") \ No newline at end of file + webnotes.conn.sql("""update `tabSerial No` set docstatus=0 where docstatus=1""") \ No newline at end of file diff --git a/public/js/complete_setup.js b/public/js/complete_setup.js index b661e025d06..e565621a2ae 100644 --- a/public/js/complete_setup.js +++ b/public/js/complete_setup.js @@ -122,5 +122,5 @@ $.extend(erpnext.complete_setup, { fy_start_list: ['', '1st Jan', '1st Apr', '1st Jul', '1st Oct'], - domains: ['', "Manufacturing", "Retail", "Distribution", "Services"], + domains: ['', "Manufacturing", "Retail", "Distribution", "Services", "Other"], }); \ No newline at end of file diff --git a/public/js/controllers/stock_controller.js b/public/js/controllers/stock_controller.js index a5b51079e5d..e4b03191a10 100644 --- a/public/js/controllers/stock_controller.js +++ b/public/js/controllers/stock_controller.js @@ -20,7 +20,7 @@ erpnext.stock.StockController = wn.ui.form.Controller.extend({ }, show_general_ledger: function() { var me = this; - if(this.frm.doc.docstatus===1 && cint(wn.defaults.get_default("auto_inventory_accounting"))) { + if(this.frm.doc.docstatus===1 && cint(wn.defaults.get_default("auto_accounting_for_stock"))) { cur_frm.add_custom_button('Accounting Ledger', function() { wn.route_options = { "voucher_no": me.frm.doc.name, diff --git a/selling/doctype/customer/customer.py b/selling/doctype/customer/customer.py index 78fad0034fb..18c8a34d8cd 100644 --- a/selling/doctype/customer/customer.py +++ b/selling/doctype/customer/customer.py @@ -67,9 +67,9 @@ class DocType(TransactionBase): ac_bean.ignore_permissions = True ac_bean.insert() - msgprint("Account Head: %s created" % ac_bean.doc.name) + msgprint(_("Account Head") + ": " + ac_bean.doc.name + _(" created")) else : - msgprint("Please Select Company under which you want to create account head") + msgprint(_("Please Select Company under which you want to create account head")) def update_credit_days_limit(self): webnotes.conn.sql("""update tabAccount set credit_days = %s, credit_limit = %s diff --git a/selling/doctype/lead/lead.txt b/selling/doctype/lead/lead.txt index a429c967e45..d96880bc755 100644 --- a/selling/doctype/lead/lead.txt +++ b/selling/doctype/lead/lead.txt @@ -2,7 +2,7 @@ { "creation": "2013-04-10 11:45:37", "docstatus": 0, - "modified": "2013-09-10 10:52:20", + "modified": "2013-09-19 10:38:58", "modified_by": "Administrator", "owner": "Administrator" }, @@ -158,10 +158,77 @@ "doctype": "DocField", "fieldname": "communication_history", "fieldtype": "Section Break", - "label": "Communication History", + "label": "Communication", "options": "icon-comments", "print_hide": 1 }, + { + "default": "__user", + "doctype": "DocField", + "fieldname": "lead_owner", + "fieldtype": "Link", + "in_filter": 1, + "label": "Lead Owner", + "oldfieldname": "lead_owner", + "oldfieldtype": "Link", + "options": "Profile", + "search_index": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "description": "Date on which the lead was last contacted", + "doctype": "DocField", + "fieldname": "last_contact_date", + "fieldtype": "Date", + "label": "Last Contact Date", + "no_copy": 1, + "oldfieldname": "last_contact_date", + "oldfieldtype": "Date", + "print_hide": 1, + "read_only": 1 + }, + { + "doctype": "DocField", + "fieldname": "col_break123", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "allow_on_submit": 0, + "description": "Your sales person who will contact the lead in future", + "doctype": "DocField", + "fieldname": "contact_by", + "fieldtype": "Link", + "hidden": 0, + "in_filter": 1, + "label": "Next Contact By", + "oldfieldname": "contact_by", + "oldfieldtype": "Link", + "options": "Profile", + "print_hide": 0, + "reqd": 0, + "width": "100px" + }, + { + "allow_on_submit": 0, + "description": "Your sales person will get a reminder on this date to contact the lead", + "doctype": "DocField", + "fieldname": "contact_date", + "fieldtype": "Date", + "in_filter": 1, + "label": "Next Contact Date", + "no_copy": 1, + "oldfieldname": "contact_date", + "oldfieldtype": "Date", + "reqd": 0, + "width": "100px" + }, + { + "doctype": "DocField", + "fieldname": "sec_break123", + "fieldtype": "Section Break", + "options": "Simple" + }, { "allow_on_submit": 0, "doctype": "DocField", @@ -274,18 +341,6 @@ "oldfieldtype": "Select", "options": "\nClient\nChannel Partner\nConsultant" }, - { - "default": "__user", - "doctype": "DocField", - "fieldname": "lead_owner", - "fieldtype": "Link", - "in_filter": 1, - "label": "Lead Owner", - "oldfieldname": "lead_owner", - "oldfieldtype": "Link", - "options": "Profile", - "search_index": 1 - }, { "doctype": "DocField", "fieldname": "market_segment", @@ -347,49 +402,6 @@ "oldfieldtype": "Link", "options": "Quotation Lost Reason" }, - { - "allow_on_submit": 0, - "description": "Your sales person who will contact the lead in future", - "doctype": "DocField", - "fieldname": "contact_by", - "fieldtype": "Link", - "hidden": 0, - "in_filter": 1, - "label": "Next Contact By", - "oldfieldname": "contact_by", - "oldfieldtype": "Link", - "options": "Profile", - "print_hide": 0, - "reqd": 0, - "width": "100px" - }, - { - "allow_on_submit": 0, - "description": "Your sales person will get a reminder on this date to contact the lead", - "doctype": "DocField", - "fieldname": "contact_date", - "fieldtype": "Date", - "in_filter": 1, - "label": "Next Contact Date", - "no_copy": 1, - "oldfieldname": "contact_date", - "oldfieldtype": "Date", - "reqd": 0, - "width": "100px" - }, - { - "depends_on": "eval:!doc.__islocal", - "description": "Date on which the lead was last contacted", - "doctype": "DocField", - "fieldname": "last_contact_date", - "fieldtype": "Date", - "label": "Last Contact Date", - "no_copy": 1, - "oldfieldname": "last_contact_date", - "oldfieldtype": "Date", - "print_hide": 1, - "read_only": 1 - }, { "doctype": "DocField", "fieldname": "company", diff --git a/selling/doctype/sales_common/sales_common.py b/selling/doctype/sales_common/sales_common.py index baa8850a589..8271c828740 100644 --- a/selling/doctype/sales_common/sales_common.py +++ b/selling/doctype/sales_common/sales_common.py @@ -140,7 +140,7 @@ class DocType(TransactionBase): for p in getlist(obj.doclist, 'packing_details'): if p.parent_detail_docname == d.name and p.parent_item == d.item_code: # the packing details table's qty is already multiplied with parent's qty - il.append({ + il.append(webnotes._dict({ 'warehouse': p.warehouse, 'reserved_warehouse': reserved_warehouse, 'item_code': p.item_code, @@ -150,9 +150,9 @@ class DocType(TransactionBase): 'batch_no': cstr(p.batch_no).strip(), 'serial_no': cstr(p.serial_no).strip(), 'name': d.name - }) + })) else: - il.append({ + il.append(webnotes._dict({ 'warehouse': d.warehouse, 'reserved_warehouse': reserved_warehouse, 'item_code': d.item_code, @@ -162,7 +162,7 @@ class DocType(TransactionBase): 'batch_no': cstr(d.batch_no).strip(), 'serial_no': cstr(d.serial_no).strip(), 'name': d.name - }) + })) return il def get_already_delivered_qty(self, dn, so, so_detail): @@ -311,7 +311,8 @@ class DocType(TransactionBase): acc_head = webnotes.conn.sql("select name from `tabAccount` where company = '%s' and master_name = '%s'"%(obj.doc.company, obj.doc.customer)) if acc_head: tot_outstanding = 0 - dbcr = webnotes.conn.sql("select sum(debit), sum(credit) from `tabGL Entry` where account = '%s' and ifnull(is_cancelled, 'No')='No'" % acc_head[0][0]) + dbcr = webnotes.conn.sql("""select sum(debit), sum(credit) from `tabGL Entry` + where account = %s""", acc_head[0][0]) if dbcr: tot_outstanding = flt(dbcr[0][0])-flt(dbcr[0][1]) @@ -336,7 +337,6 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): return webnotes.conn.sql("""select batch_no from `tabStock Ledger Entry` sle where item_code = '%(item_code)s' and warehouse = '%(warehouse)s' - and ifnull(is_cancelled, 'No') = 'No' and batch_no like '%(txt)s' and exists(select * from `tabBatch` where name = sle.batch_no diff --git a/selling/doctype/sales_order/sales_order.py b/selling/doctype/sales_order/sales_order.py index c9ef5d3bbab..40567429cc6 100644 --- a/selling/doctype/sales_order/sales_order.py +++ b/selling/doctype/sales_order/sales_order.py @@ -266,17 +266,19 @@ class DocType(SellingController): def update_stock_ledger(self, update_stock, is_stopped = 0): + from stock.utils import update_bin for d in self.get_item_list(is_stopped): if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes": args = { "item_code": d['item_code'], + "warehouse": d['reserved_warehouse'], "reserved_qty": flt(update_stock) * flt(d['reserved_qty']), "posting_date": self.doc.transaction_date, "voucher_type": self.doc.doctype, "voucher_no": self.doc.name, "is_amended": self.doc.amended_from and 'Yes' or 'No' } - get_obj('Warehouse', d['reserved_warehouse']).update_bin(args) + update_bin(args) def get_item_list(self, is_stopped): diff --git a/selling/doctype/sms_center/sms_center.py b/selling/doctype/sms_center/sms_center.py index 6f16798d8b7..eb331c206eb 100644 --- a/selling/doctype/sms_center/sms_center.py +++ b/selling/doctype/sms_center/sms_center.py @@ -41,7 +41,7 @@ class DocType: for d in rec: rec_list += d[0] + ' - ' + d[1] + '\n' self.doc.receiver_list = rec_list - webnotes.errprint(rec_list) + def get_receiver_nos(self): receiver_nos = [] for d in self.doc.receiver_list.split('\n'): diff --git a/selling/utils/product.py b/selling/utils/product.py index 93a71946c74..3432170a015 100644 --- a/selling/utils/product.py +++ b/selling/utils/product.py @@ -27,7 +27,7 @@ def get_product_info(item_code): else: in_stock = -1 - price = price_list and webnotes.conn.sql("""select ip.ref_rate, pl.ref_currency from + price = price_list and webnotes.conn.sql("""select ip.ref_rate, pl.currency from `tabItem Price` ip, `tabPrice List` pl where ip.parent = pl.name and ip.item_code=%s and ip.parent=%s""", (item_code, price_list), as_dict=1) or [] @@ -36,10 +36,10 @@ def get_product_info(item_code): qty = 0 if price: - price["formatted_price"] = fmt_money(price["ref_rate"], currency=price["ref_currency"]) + price["formatted_price"] = fmt_money(price["ref_rate"], currency=price["currency"]) - price["ref_currency"] = not cint(webnotes.conn.get_default("hide_currency_symbol")) \ - and (webnotes.conn.get_value("Currency", price.ref_currency, "symbol") or price.ref_currency) \ + price["currency"] = not cint(webnotes.conn.get_default("hide_currency_symbol")) \ + and (webnotes.conn.get_value("Currency", price.currency, "symbol") or price.currency) \ or "" if webnotes.session.user != "Guest": diff --git a/setup/doctype/company/company.js b/setup/doctype/company/company.js index c8042710a67..ca3c93b49bf 100644 --- a/setup/doctype/company/company.js +++ b/setup/doctype/company/company.js @@ -90,18 +90,7 @@ cur_frm.fields_dict.cost_center.get_query = function(doc) { } } -if (sys_defaults.auto_inventory_accounting) { - cur_frm.fields_dict["stock_in_hand_account"].get_query = function(doc) { - return { - "filters": { - "is_pl_account": "No", - "debit_or_credit": "Debit", - "company": doc.name, - 'group_or_ledger': "Ledger" - } - } - } - +if (sys_defaults.auto_accounting_for_stock) { cur_frm.fields_dict["stock_adjustment_account"].get_query = function(doc) { return { "filters": { @@ -126,10 +115,4 @@ if (sys_defaults.auto_inventory_accounting) { } } } - - cur_frm.fields_dict["stock_adjustment_cost_center"].get_query = function(doc) { - return { - "filters": {"company": doc.name} - } - } } \ No newline at end of file diff --git a/setup/doctype/company/company.py b/setup/doctype/company/company.py index bb0ee1baebc..da220cc6bee 100644 --- a/setup/doctype/company/company.py +++ b/setup/doctype/company/company.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import webnotes from webnotes import _, msgprint -from webnotes.utils import cstr +from webnotes.utils import cstr, cint from webnotes.model.doc import Document from webnotes.model.code import get_obj import webnotes.defaults @@ -58,14 +58,17 @@ class DocType: def create_default_warehouses(self): for whname in ("Stores", "Work In Progress", "Finished Goods"): - webnotes.bean({ - "doctype":"Warehouse", - "warehouse_name": whname, - "company": self.doc.name - }).insert() + if not webnotes.conn.exists("Warehouse", whname + " - " + self.doc.abbr): + webnotes.bean({ + "doctype":"Warehouse", + "warehouse_name": whname, + "company": self.doc.name, + "create_account_under": "Stock Assets - " + self.doc.abbr + }).insert() def create_default_web_page(self): - if not webnotes.conn.get_value("Website Settings", None, "home_page"): + if not webnotes.conn.get_value("Website Settings", None, "home_page") and \ + not webnotes.conn.sql("select name from tabCompany where name!=%s", self.doc.name): import os with open(os.path.join(os.path.dirname(__file__), "sample_home_page.html"), "r") as webfile: webpage = webnotes.bean({ @@ -112,7 +115,6 @@ class DocType: ['Securities and Deposits','Current Assets','Group','No','','Debit',self.doc.name,''], ['Earnest Money','Securities and Deposits','Ledger','No','','Debit',self.doc.name,''], ['Stock Assets','Current Assets','Group','No','','Debit',self.doc.name,''], - ['Stock In Hand','Stock Assets','Ledger','No','','Debit',self.doc.name,''], ['Tax Assets','Current Assets','Group','No','','Debit',self.doc.name,''], ['Fixed Assets','Application of Funds (Assets)','Group','No','','Debit',self.doc.name,''], ['Capital Equipments','Fixed Assets','Ledger','No','Fixed Asset Account','Debit',self.doc.name,''], @@ -242,8 +244,8 @@ class DocType: "default_expense_account": "Cost of Goods Sold", "receivables_group": "Accounts Receivable", "payables_group": "Accounts Payable", + "default_cash_account": "Cash", "stock_received_but_not_billed": "Stock Received But Not Billed", - "stock_in_hand_account": "Stock In Hand", "stock_adjustment_account": "Stock Adjustment", "expenses_included_in_valuation": "Expenses Included In Valuation" } @@ -253,9 +255,6 @@ class DocType: if not self.doc.fields.get(a) and webnotes.conn.exists("Account", account_name): webnotes.conn.set(self.doc, a, account_name) - if not self.doc.stock_adjustment_cost_center: - webnotes.conn.set(self.doc, "stock_adjustment_cost_center", self.doc.cost_center) - def create_default_cost_center(self): cc_list = [ { @@ -275,10 +274,9 @@ class DocType: cc.update({"doctype": "Cost Center"}) cc_bean = webnotes.bean(cc) cc_bean.ignore_permissions = True - + if cc.get("cost_center_name") == self.doc.name: cc_bean.ignore_mandatory = True - cc_bean.insert() webnotes.conn.set(self.doc, "cost_center", "Main - " + self.doc.abbr) @@ -287,11 +285,8 @@ class DocType: """ Trash accounts and cost centers for this company if no gl entry exists """ - rec = webnotes.conn.sql("SELECT name from `tabGL Entry` where ifnull(is_cancelled, 'No') = 'No' and company = %s", self.doc.name) + rec = webnotes.conn.sql("SELECT name from `tabGL Entry` where company = %s", self.doc.name) if not rec: - # delete gl entry - webnotes.conn.sql("delete from `tabGL Entry` where company = %s", self.doc.name) - #delete tabAccount webnotes.conn.sql("delete from `tabAccount` where company = %s order by lft desc, rgt desc", self.doc.name) @@ -300,6 +295,9 @@ class DocType: #delete cost center webnotes.conn.sql("delete from `tabCost Center` WHERE company = %s order by lft desc, rgt desc", self.doc.name) + if not webnotes.conn.get_value("Stock Ledger Entry", {"company": self.doc.name}): + webnotes.conn.sql("""delete from `tabWarehouse` where company=%s""", self.doc.name) + webnotes.defaults.clear_default("company", value=self.doc.name) webnotes.conn.sql("""update `tabSingles` set value="" diff --git a/setup/doctype/company/company.txt b/setup/doctype/company/company.txt index 768c7b1caf6..1ba1dde4811 100644 --- a/setup/doctype/company/company.txt +++ b/setup/doctype/company/company.txt @@ -2,7 +2,7 @@ { "creation": "2013-04-10 08:35:39", "docstatus": 0, - "modified": "2013-08-05 15:39:36", + "modified": "2013-08-28 19:15:04", "modified_by": "Administrator", "owner": "Administrator" }, @@ -221,19 +221,9 @@ { "depends_on": "eval:!doc.__islocal", "doctype": "DocField", - "fieldname": "auto_inventory_accounting_settings", + "fieldname": "auto_accounting_for_stock_settings", "fieldtype": "Section Break", - "label": "Auto Inventory Accounting Settings", - "read_only": 0 - }, - { - "description": "This account will be used to maintain value of available stock", - "doctype": "DocField", - "fieldname": "stock_in_hand_account", - "fieldtype": "Link", - "label": "Stock In Hand Account", - "no_copy": 1, - "options": "Account", + "label": "Auto Accounting For Stock Settings", "read_only": 0 }, { @@ -245,13 +235,6 @@ "options": "Account", "read_only": 0 }, - { - "doctype": "DocField", - "fieldname": "col_break23", - "fieldtype": "Column Break", - "read_only": 0, - "width": "50%" - }, { "doctype": "DocField", "fieldname": "stock_adjustment_account", @@ -270,15 +253,6 @@ "options": "Account", "read_only": 0 }, - { - "doctype": "DocField", - "fieldname": "stock_adjustment_cost_center", - "fieldtype": "Link", - "label": "Stock Adjustment Cost Center", - "no_copy": 1, - "options": "Cost Center", - "read_only": 0 - }, { "description": "For reference only.", "doctype": "DocField", diff --git a/setup/doctype/email_digest/email_digest.py b/setup/doctype/email_digest/email_digest.py index a05bae98963..9a00723110f 100644 --- a/setup/doctype/email_digest/email_digest.py +++ b/setup/doctype/email_digest/email_digest.py @@ -362,8 +362,8 @@ class DocType(DocListController): gl_entries = webnotes.conn.sql("""select `account`, ifnull(credit, 0) as credit, ifnull(debit, 0) as debit, `against` from `tabGL Entry` - where company=%s and ifnull(is_cancelled, "No")="No" and - posting_date <= %s %s""" % ("%s", "%s", + where company=%s + and posting_date <= %s %s""" % ("%s", "%s", from_date and "and posting_date>='%s'" % from_date or ""), (self.doc.company, to_date or self.to_date), as_dict=1) diff --git a/setup/doctype/setup_control/setup_control.py b/setup/doctype/setup_control/setup_control.py index 0bc2f24ca76..b78bfcc7d6e 100644 --- a/setup/doctype/setup_control/setup_control.py +++ b/setup/doctype/setup_control/setup_control.py @@ -105,8 +105,9 @@ class DocType: }) global_defaults.save() - webnotes.conn.set_value("Accounts Settings", None, "auto_inventory_accounting", 1) - webnotes.conn.set_default("auto_inventory_accounting", 1) + accounts_settings = webnotes.bean("Accounts Settings") + accounts_settings.doc.auto_accounting_for_stock = 1 + accounts_settings.save() stock_settings = webnotes.bean("Stock Settings") stock_settings.doc.item_naming_by = "Item Code" diff --git a/stock/report/item_wise_price_list/__init__.py b/setup/report/__init__.py similarity index 100% rename from stock/report/item_wise_price_list/__init__.py rename to setup/report/__init__.py diff --git a/setup/report/item_wise_price_list/__init__.py b/setup/report/item_wise_price_list/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/setup/report/item_wise_price_list/item_wise_price_list.txt b/setup/report/item_wise_price_list/item_wise_price_list.txt new file mode 100644 index 00000000000..44118c8b13f --- /dev/null +++ b/setup/report/item_wise_price_list/item_wise_price_list.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-09-25 10:29:04", + "docstatus": 0, + "modified": "2013-09-25 10:29:04", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "json": "{\"filters\":[[\"Item Price\",\"item_code\",\"like\",\"%\"],[\"Price List\",\"price_list_name\",\"like\",\"%\"]],\"columns\":[[\"item_code\",\"Item Price\"],[\"price_list_name\",\"Price List\"],[\"currency\",\"Price List\"],[\"ref_rate\",\"Item Price\"],[\"buying_or_selling\",\"Price List\"],[\"name\",\"Price List\"]],\"sort_by\":\"Price List.modified\",\"sort_order\":\"desc\",\"sort_by_next\":\"\",\"sort_order_next\":\"desc\"}", + "name": "__common__", + "ref_doctype": "Price List", + "report_name": "Item-Wise Price List", + "report_type": "Report Builder" + }, + { + "doctype": "Report", + "name": "Item-Wise Price List" + } +] \ No newline at end of file diff --git a/setup/report/item_wise_price_list_rate/__init__.py b/setup/report/item_wise_price_list_rate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/setup/report/item_wise_price_list_rate/item_wise_price_list_rate.txt b/setup/report/item_wise_price_list_rate/item_wise_price_list_rate.txt new file mode 100644 index 00000000000..08b1bef01d4 --- /dev/null +++ b/setup/report/item_wise_price_list_rate/item_wise_price_list_rate.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-09-25 10:21:15", + "docstatus": 0, + "modified": "2013-09-25 10:24:57", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "json": "{\"filters\":[[\"Item Price\",\"item_code\",\"like\",\"%\"],[\"Price List\",\"price_list_name\",\"like\",\"%\"]],\"columns\":[[\"item_code\",\"Item Price\"],[\"price_list_name\",\"Price List\"],[\"currency\",\"Price List\"],[\"ref_rate\",\"Item Price\"],[\"buying_or_selling\",\"Price List\"],[\"name\",\"Price List\"]],\"sort_by\":\"Price List.modified\",\"sort_order\":\"desc\",\"sort_by_next\":\"\",\"sort_order_next\":\"desc\"}", + "name": "__common__", + "ref_doctype": "Price List", + "report_name": "Item-wise Price List Rate", + "report_type": "Report Builder" + }, + { + "doctype": "Report", + "name": "Item-wise Price List Rate" + } +] \ No newline at end of file diff --git a/startup/report_data_map.py b/startup/report_data_map.py index 0f6d4fe1940..54453f68bbd 100644 --- a/startup/report_data_map.py +++ b/startup/report_data_map.py @@ -36,7 +36,6 @@ data_map = { "GL Entry": { "columns": ["name", "account", "posting_date", "cost_center", "debit", "credit", "is_opening", "company", "voucher_type", "voucher_no", "remarks"], - "conditions": ["ifnull(is_cancelled, 'No')='No'"], "order_by": "posting_date, account", "links": { "account": ["Account", "name"], @@ -81,7 +80,6 @@ data_map = { "columns": ["name", "posting_date", "posting_time", "item_code", "warehouse", "actual_qty as qty", "voucher_type", "voucher_no", "project", "ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no"], - "conditions": ["ifnull(is_cancelled, 'No')='No'"], "order_by": "posting_date, posting_time, name", "links": { "item_code": ["Item", "name"], diff --git a/stock/doctype/bin/bin.py b/stock/doctype/bin/bin.py index 8c892c13244..32407e21a05 100644 --- a/stock/doctype/bin/bin.py +++ b/stock/doctype/bin/bin.py @@ -64,7 +64,6 @@ class DocType: select * from `tabStock Ledger Entry` where item_code = %s and warehouse = %s - and ifnull(is_cancelled, 'No') = 'No' order by timestamp(posting_date, posting_time) asc, name asc limit 1 """, (self.doc.item_code, self.doc.warehouse), as_dict=1) diff --git a/stock/doctype/delivery_note/delivery_note.js b/stock/doctype/delivery_note/delivery_note.js index 80c26463ff8..40d840011a9 100644 --- a/stock/doctype/delivery_note/delivery_note.js +++ b/stock/doctype/delivery_note/delivery_note.js @@ -43,8 +43,8 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( set_print_hide(doc, dt, dn); - // unhide expense_account and cost_center is auto_inventory_accounting enabled - var aii_enabled = cint(sys_defaults.auto_inventory_accounting) + // unhide expense_account and cost_center is auto_accounting_for_stock enabled + var aii_enabled = cint(sys_defaults.auto_accounting_for_stock) cur_frm.fields_dict[cur_frm.cscript.fname].grid.set_column_disp(["expense_account", "cost_center"], aii_enabled); if (this.frm.doc.docstatus===0) { @@ -201,7 +201,7 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { } } -if (sys_defaults.auto_inventory_accounting) { +if (sys_defaults.auto_accounting_for_stock) { cur_frm.cscript.expense_account = function(doc, cdt, cdn){ var d = locals[cdt][cdn]; diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py index 5242f60ea64..e5e412e44bd 100644 --- a/stock/doctype/delivery_note/delivery_note.py +++ b/stock/doctype/delivery_note/delivery_note.py @@ -10,10 +10,7 @@ from webnotes.model.code import get_obj from webnotes import msgprint, _ import webnotes.defaults from webnotes.model.mapper import get_mapped_doclist - - - - +from stock.utils import update_bin from controllers.selling_controller import SellingController class DocType(SellingController): @@ -129,7 +126,6 @@ class DocType(SellingController): } }) - def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.doc.project_name and self.doc.customer: @@ -163,7 +159,7 @@ class DocType(SellingController): if not d['warehouse']: msgprint("Please enter Warehouse for item %s as it is stock item" % d['item_code'], raise_exception=1) - + def update_current_stock(self): for d in getlist(self.doclist, 'delivery_note_details'): @@ -188,12 +184,11 @@ class DocType(SellingController): self.update_prevdoc_status() # create stock ledger entry - self.update_stock_ledger(update_stock = 1) + self.update_stock_ledger() self.update_serial_nos() self.credit_limit() - self.set_buying_amount() self.make_gl_entries() # set DN status @@ -207,7 +202,7 @@ class DocType(SellingController): self.update_prevdoc_status() - self.update_stock_ledger(update_stock = -1) + self.update_stock_ledger() self.update_serial_nos(cancel=True) webnotes.conn.set(self.doc, 'status', 'Cancelled') @@ -291,60 +286,39 @@ class DocType(SellingController): webnotes.msgprint(_("Packing Slip(s) Cancelled")) - def update_stock_ledger(self, update_stock): - self.values = [] + def update_stock_ledger(self): + sl_entries = [] for d in self.get_item_list(): - if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes": - # this happens when item is changed from non-stock to stock item - if not d["warehouse"]: - continue + if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes" \ + and d.warehouse: + self.update_reserved_qty(d) + + sl_entries.append(self.get_sl_entries(d, { + "actual_qty": -1*flt(d['qty']), + })) + + self.make_sl_entries(sl_entries) + + def update_reserved_qty(self, d): + if d['reserved_qty'] < 0 : + # Reduce reserved qty from reserved warehouse mentioned in so + if not d["reserved_warehouse"]: + webnotes.throw(_("Reserved Warehouse is missing in Sales Order")) - if d['reserved_qty'] < 0 : - # Reduce reserved qty from reserved warehouse mentioned in so - if not d["reserved_warehouse"]: - webnotes.throw(_("Reserved Warehouse is missing in Sales Order")) - - args = { - "item_code": d['item_code'], - "voucher_type": self.doc.doctype, - "voucher_no": self.doc.name, - "reserved_qty": flt(update_stock) * flt(d['reserved_qty']), - "posting_date": self.doc.posting_date, - "is_amended": self.doc.amended_from and 'Yes' or 'No' - } - get_obj("Warehouse", d["reserved_warehouse"]).update_bin(args) - - # Reduce actual qty from warehouse - self.make_sl_entry(d, d['warehouse'], - flt(d['qty']) , 0, update_stock) - - get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values) - + args = { + "item_code": d['item_code'], + "warehouse": d["reserved_warehouse"], + "voucher_type": self.doc.doctype, + "voucher_no": self.doc.name, + "reserved_qty": (self.doc.docstatus==1 and 1 or -1)*flt(d['reserved_qty']), + "posting_date": self.doc.posting_date, + "is_amended": self.doc.amended_from and 'Yes' or 'No' + } + update_bin(args) def get_item_list(self): return get_obj('Sales Common').get_item_list(self) - - def make_sl_entry(self, d, wh, qty, in_value, update_stock): - self.values.append({ - 'item_code' : d['item_code'], - 'warehouse' : wh, - 'posting_date' : self.doc.posting_date, - 'posting_time' : self.doc.posting_time, - 'voucher_type' : 'Delivery Note', - 'voucher_no' : self.doc.name, - 'voucher_detail_no' : d['name'], - 'actual_qty' : qty, - 'stock_uom' : d['uom'], - 'incoming_rate' : in_value, - 'company' : self.doc.company, - 'fiscal_year' : self.doc.fiscal_year, - 'is_cancelled' : (update_stock==1) and 'No' or 'Yes', - 'batch_no' : d['batch_no'], - 'serial_no' : d['serial_no'], - "project" : self.doc.project_name - }) - - def credit_limit(self): """check credit limit of items in DN Detail which are not fetched from sales order""" amount, total = 0, 0 @@ -354,22 +328,6 @@ class DocType(SellingController): if amount != 0: total = (amount/self.doc.net_total)*self.doc.grand_total get_obj('Sales Common').check_credit(self, total) - - def make_gl_entries(self): - if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")): - return - - gl_entries = [] - for item in self.doclist.get({"parentfield": "delivery_note_details"}): - self.check_expense_account(item) - - if item.buying_amount: - gl_entries += self.get_gl_entries_for_stock(item.expense_account, -1*item.buying_amount, - cost_center=item.cost_center) - - if gl_entries: - from accounts.general_ledger import make_gl_entries - make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2)) def get_invoiced_qty_map(delivery_note): """returns a map: {dn_detail: invoiced_qty}""" diff --git a/stock/doctype/delivery_note/test_delivery_note.py b/stock/doctype/delivery_note/test_delivery_note.py index 89690fe0ba5..7c525504ae4 100644 --- a/stock/doctype/delivery_note/test_delivery_note.py +++ b/stock/doctype/delivery_note/test_delivery_note.py @@ -7,20 +7,22 @@ import unittest import webnotes import webnotes.defaults from webnotes.utils import cint +from stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, set_perpetual_inventory, test_records as pr_test_records class TestDeliveryNote(unittest.TestCase): - def _insert_purchase_receipt(self): - from stock.doctype.purchase_receipt.test_purchase_receipt import test_records as pr_test_records + def _insert_purchase_receipt(self, item_code=None): pr = webnotes.bean(copy=pr_test_records[0]) - pr.run_method("calculate_taxes_and_totals") + if item_code: + pr.doclist[1].item_code = item_code pr.insert() pr.submit() def test_over_billing_against_dn(self): + self.clear_stock_account_balance() self._insert_purchase_receipt() from stock.doctype.delivery_note.delivery_note import make_sales_invoice - + self._insert_purchase_receipt() dn = webnotes.bean(copy=test_records[0]).insert() self.assertRaises(webnotes.ValidationError, make_sales_invoice, @@ -38,9 +40,9 @@ class TestDeliveryNote(unittest.TestCase): def test_delivery_note_no_gl_entry(self): - webnotes.conn.sql("""delete from `tabBin`""") - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) - self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 0) + self.clear_stock_account_balance() + set_perpetual_inventory(0) + self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 0) self._insert_purchase_receipt() @@ -48,18 +50,24 @@ class TestDeliveryNote(unittest.TestCase): dn.insert() dn.submit() + stock_value, stock_value_difference = webnotes.conn.get_value("Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": dn.doc.name, + "item_code": "_Test Item"}, ["stock_value", "stock_value_difference"]) + self.assertEqual(stock_value, 0) + self.assertEqual(stock_value_difference, -375) + + gl_entries = webnotes.conn.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Delivery Note' and voucher_no=%s order by account desc""", dn.doc.name, as_dict=1) - self.assertTrue(not gl_entries) + self.assertFalse(get_gl_entries("Delivery Note", dn.doc.name)) def test_delivery_note_gl_entry(self): - webnotes.conn.sql("""delete from `tabBin`""") - webnotes.conn.sql("delete from `tabStock Ledger Entry`") - - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) + self.clear_stock_account_balance() + set_perpetual_inventory() + self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 1) + webnotes.conn.set_value("Item", "_Test Item", "valuation_method", "FIFO") self._insert_purchase_receipt() @@ -67,8 +75,8 @@ class TestDeliveryNote(unittest.TestCase): dn.doclist[1].expense_account = "Cost of Goods Sold - _TC" dn.doclist[1].cost_center = "Main - _TC" - stock_in_hand_account = webnotes.conn.get_value("Company", dn.doc.company, - "stock_in_hand_account") + stock_in_hand_account = webnotes.conn.get_value("Account", + {"master_name": dn.doclist[1].warehouse}) from accounts.utils import get_balance_on prev_bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date) @@ -76,26 +84,79 @@ class TestDeliveryNote(unittest.TestCase): dn.insert() dn.submit() - - gl_entries = webnotes.conn.sql("""select account, debit, credit - from `tabGL Entry` where voucher_type='Delivery Note' and voucher_no=%s - order by account asc""", dn.doc.name, as_dict=1) + gl_entries = get_gl_entries("Delivery Note", dn.doc.name) self.assertTrue(gl_entries) - - expected_values = sorted([ - [stock_in_hand_account, 0.0, 375.0], - ["Cost of Goods Sold - _TC", 375.0, 0.0] - ]) + expected_values = { + stock_in_hand_account: [0.0, 375.0], + "Cost of Goods Sold - _TC": [375.0, 0.0] + } for i, gle in enumerate(gl_entries): - self.assertEquals(expected_values[i][0], gle.account) - self.assertEquals(expected_values[i][1], gle.debit) - self.assertEquals(expected_values[i][2], gle.credit) + self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account)) # check stock in hand balance bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date) self.assertEquals(bal, prev_bal - 375.0) + + # back dated purchase receipt + pr = webnotes.bean(copy=pr_test_records[0]) + pr.doc.posting_date = "2013-01-01" + pr.doclist[1].import_rate = 100 + pr.doclist[1].amount = 100 - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + pr.insert() + pr.submit() + + gl_entries = get_gl_entries("Delivery Note", dn.doc.name) + self.assertTrue(gl_entries) + expected_values = { + stock_in_hand_account: [0.0, 666.65], + "Cost of Goods Sold - _TC": [666.65, 0.0] + } + for i, gle in enumerate(gl_entries): + self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account)) + + dn.cancel() + self.assertFalse(get_gl_entries("Delivery Note", dn.doc.name)) + set_perpetual_inventory(0) + + def test_delivery_note_gl_entry_packing_item(self): + self.clear_stock_account_balance() + set_perpetual_inventory() + + self._insert_purchase_receipt() + self._insert_purchase_receipt("_Test Item Home Desktop 100") + + dn = webnotes.bean(copy=test_records[0]) + dn.doclist[1].item_code = "_Test Sales BOM Item" + dn.doclist[1].qty = 1 + + stock_in_hand_account = webnotes.conn.get_value("Account", + {"master_name": dn.doclist[1].warehouse}) + + from accounts.utils import get_balance_on + prev_bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date) + + dn.insert() + dn.submit() + + gl_entries = get_gl_entries("Delivery Note", dn.doc.name) + self.assertTrue(gl_entries) + + expected_values = { + stock_in_hand_account: [0.0, 525], + "Cost of Goods Sold - _TC": [525.0, 0.0] + } + for i, gle in enumerate(gl_entries): + self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account)) + + # check stock in hand balance + bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date) + self.assertEquals(bal, prev_bal - 525.0) + + dn.cancel() + self.assertFalse(get_gl_entries("Delivery Note", dn.doc.name)) + + set_perpetual_inventory(0) def test_serialized(self): from stock.doctype.stock_entry.test_stock_entry import make_serialized_item @@ -148,7 +209,13 @@ class TestDeliveryNote(unittest.TestCase): dn.insert() self.assertRaises(SerialNoStatusError, dn.submit) + + def clear_stock_account_balance(self): + webnotes.conn.sql("""delete from `tabBin`""") + webnotes.conn.sql("delete from `tabStock Ledger Entry`") + webnotes.conn.sql("delete from `tabGL Entry`") +test_dependencies = ["Sales BOM"] test_records = [ [ @@ -183,8 +250,10 @@ test_records = [ "export_rate": 100.0, "amount": 500.0, "warehouse": "_Test Warehouse - _TC", - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "Main - _TC" } ] -] \ No newline at end of file +] diff --git a/stock/doctype/delivery_note_item/delivery_note_item.txt b/stock/doctype/delivery_note_item/delivery_note_item.txt index 83b8f75c9cb..b74c33a621f 100644 --- a/stock/doctype/delivery_note_item/delivery_note_item.txt +++ b/stock/doctype/delivery_note_item/delivery_note_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-04-22 13:15:44", "docstatus": 0, - "modified": "2013-08-07 14:45:30", + "modified": "2013-08-29 16:58:16", "modified_by": "Administrator", "owner": "Administrator" }, @@ -420,17 +420,6 @@ "print_hide": 1, "read_only": 1 }, - { - "doctype": "DocField", - "fieldname": "buying_amount", - "fieldtype": "Currency", - "hidden": 1, - "label": "Buying Amount", - "no_copy": 1, - "options": "Company:company:default_currency", - "print_hide": 1, - "read_only": 1 - }, { "allow_on_submit": 1, "doctype": "DocField", diff --git a/stock/doctype/item/item.py b/stock/doctype/item/item.py index 1ece42035bd..aedb71c2c23 100644 --- a/stock/doctype/item/item.py +++ b/stock/doctype/item/item.py @@ -67,7 +67,7 @@ class DocType(DocListController): if not self.doc.fields.get("__islocal"): matched=True ref_uom = webnotes.conn.get_value("Stock Ledger Entry", - {"item_code": self.doc.name, "is_cancelled": "No"}, "stock_uom") + {"item_code": self.doc.name}, "stock_uom") if ref_uom: if cstr(ref_uom) != cstr(self.doc.stock_uom): matched = False @@ -75,8 +75,8 @@ class DocType(DocListController): bin_list = webnotes.conn.sql("select * from tabBin where item_code=%s", self.doc.item_code, as_dict=1) for bin in bin_list: - if bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \ - or bin.planned_qty > 0 and cstr(bin.stock_uom) != cstr(self.doc.stock_uom): + if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \ + or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(self.doc.stock_uom): matched = False break @@ -199,7 +199,7 @@ class DocType(DocListController): def check_if_sle_exists(self): sle = webnotes.conn.sql("""select name from `tabStock Ledger Entry` - where item_code = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name) + where item_code = %s""", self.doc.name) return sle and 'exists' or 'not exists' def validate_name_with_item_group(self): @@ -260,8 +260,6 @@ class DocType(DocListController): def on_trash(self): webnotes.conn.sql("""delete from tabBin where item_code=%s""", self.doc.item_code) - webnotes.conn.sql("""delete from `tabStock Ledger Entry` - where item_code=%s and is_cancelled='Yes' """, self.doc.item_code) if self.doc.page_name: from webnotes.webutils import clear_cache diff --git a/stock/doctype/landed_cost_item/landed_cost_item.txt b/stock/doctype/landed_cost_item/landed_cost_item.txt index 3e14ce834d1..bfd6216dfe9 100644 --- a/stock/doctype/landed_cost_item/landed_cost_item.txt +++ b/stock/doctype/landed_cost_item/landed_cost_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-22 01:28:02", "docstatus": 0, - "modified": "2013-07-10 14:54:10", + "modified": "2013-09-02 17:36:19", "modified_by": "Administrator", "owner": "wasim@webnotestech.com" }, @@ -14,7 +14,6 @@ }, { "doctype": "DocField", - "in_list_view": 1, "name": "__common__", "parent": "Landed Cost Item", "parentfield": "fields", @@ -30,16 +29,25 @@ "doctype": "DocField", "fieldname": "account_head", "fieldtype": "Link", + "in_list_view": 1, "label": "Account Head", "oldfieldname": "account_head", "oldfieldtype": "Link", "options": "Account", "search_index": 1 }, + { + "doctype": "DocField", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, { "doctype": "DocField", "fieldname": "description", "fieldtype": "Data", + "in_list_view": 1, "label": "Description", "oldfieldname": "description", "oldfieldtype": "Data", @@ -50,6 +58,7 @@ "doctype": "DocField", "fieldname": "amount", "fieldtype": "Currency", + "in_list_view": 1, "label": "Amount", "oldfieldname": "amount", "oldfieldtype": "Currency", diff --git a/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.txt b/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.txt index e47dd27a5c7..5f0bc9079f9 100644 --- a/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.txt +++ b/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-22 01:28:02", "docstatus": 0, - "modified": "2013-07-10 14:54:10", + "modified": "2013-09-02 13:44:28", "modified_by": "Administrator", "owner": "wasim@webnotestech.com" }, @@ -14,36 +14,26 @@ }, { "doctype": "DocField", + "fieldname": "purchase_receipt", + "fieldtype": "Link", "in_list_view": 1, + "label": "Purchase Receipt", "name": "__common__", + "oldfieldname": "purchase_receipt_no", + "oldfieldtype": "Link", + "options": "Purchase Receipt", "parent": "Landed Cost Purchase Receipt", "parentfield": "fields", "parenttype": "DocType", - "permlevel": 0 + "permlevel": 0, + "print_width": "220px", + "width": "220px" }, { "doctype": "DocType", "name": "Landed Cost Purchase Receipt" }, { - "doctype": "DocField", - "fieldname": "purchase_receipt", - "fieldtype": "Link", - "label": "Purchase Receipt", - "oldfieldname": "purchase_receipt_no", - "oldfieldtype": "Link", - "options": "Purchase Receipt", - "print_width": "220px", - "width": "220px" - }, - { - "doctype": "DocField", - "fieldname": "select_pr", - "fieldtype": "Check", - "label": "Select PR", - "oldfieldname": "include_in_landed_cost", - "oldfieldtype": "Check", - "print_width": "120px", - "width": "120px" + "doctype": "DocField" } ] \ No newline at end of file diff --git a/stock/doctype/landed_cost_wizard/landed_cost_wizard.js b/stock/doctype/landed_cost_wizard/landed_cost_wizard.js index 06f3fd6ae6b..542acbeebe1 100644 --- a/stock/doctype/landed_cost_wizard/landed_cost_wizard.js +++ b/stock/doctype/landed_cost_wizard/landed_cost_wizard.js @@ -1,18 +1,48 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. // License: GNU General Public License v3. See license.txt -cur_frm.cscript.onload = function(doc, cdt, cdn) { -if(!doc.currency){doc.currency = sys_defaults.currency;} -} +wn.provide("erpnext.stock"); +wn.require("public/app/js/controllers/stock_controller.js"); -cur_frm.fields_dict['landed_cost_details'].grid.get_field("account_head").get_query = function(doc,cdt,cdn) { -return{ - filters:[ - ['Account', 'group_or_ledger', '=', 'Ledger'], - ['Account', 'account_type', 'in', 'Tax, Chargeable'], - ['Account', 'is_pl_account', '=', 'Yes'], - ['Account', 'debit_or_credit', '=', 'Debit'] - ] +erpnext.stock.LandedCostWizard = erpnext.stock.StockController.extend({ + setup: function() { + var me = this; + this.frm.fields_dict.lc_pr_details.grid.get_field('purchase_receipt').get_query = + function() { + if(!me.frm.doc.company) msgprint(wn._("Please enter company first")); + return { + filters:[ + ['Purchase Receipt', 'docstatus', '=', '1'], + ['Purchase Receipt', 'company', '=', me.frm.doc.company], + ] + } + }; + + this.frm.fields_dict.landed_cost_details.grid.get_field('account_head').get_query = function() { + if(!me.frm.doc.company) msgprint(wn._("Please enter company first")); + return { + filters:[ + ['Account', 'group_or_ledger', '=', 'Ledger'], + ['Account', 'account_type', 'in', 'Tax, Chargeable'], + ['Account', 'is_pl_account', '=', 'Yes'], + ['Account', 'debit_or_credit', '=', 'Debit'], + ['Account', 'company', '=', me.frm.doc.company] + ] + } + }, + + this.frm.fields_dict.landed_cost_details.grid.get_field('cost_center').get_query = + function() { + if(!me.frm.doc.company) msgprint(wn._("Please enter company first")); + return { + filters:[ + ['Cost Center', 'group_or_ledger', '=', 'Ledger'], + ['Cost Center', 'company', '=', me.frm.doc.company] + ] + } + } } -} +}); + +cur_frm.script_manager.make(erpnext.stock.LandedCostWizard); \ No newline at end of file diff --git a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py index b9bbb068f19..f431537c6c5 100644 --- a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py +++ b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py @@ -7,225 +7,95 @@ from webnotes.utils import cint, cstr, flt from webnotes.model.doc import addchild from webnotes.model.bean import getlist from webnotes.model.code import get_obj -from webnotes import msgprint - - +from webnotes import msgprint, _ class DocType: def __init__(self, doc, doclist=[]): self.doc = doc self.doclist = doclist - self.prwise_cost = {} - - def check_mandatory(self): - """ Check mandatory fields """ - if not self.doc.from_pr_date or not self.doc.to_pr_date: - msgprint("Please enter From and To PR Date", raise_exception=1) - - if not self.doc.currency: - msgprint("Please enter Currency.", raise_exception=1) - - - def get_purchase_receipts(self): - """ Get purchase receipts for given period """ - - self.doclist = self.doc.clear_table(self.doclist,'lc_pr_details',1) - self.check_mandatory() - - pr = webnotes.conn.sql("select name from `tabPurchase Receipt` where docstatus = 1 and posting_date >= '%s' and posting_date <= '%s' and currency = '%s' order by name " % (self.doc.from_pr_date, self.doc.to_pr_date, self.doc.currency), as_dict = 1) - if len(pr)>200: - msgprint("Please enter date of shorter duration as there are too many purchase receipt, hence it cannot be loaded.", raise_exception=1) - for i in pr: - ch = addchild(self.doc, 'lc_pr_details', 'Landed Cost Purchase Receipt', - self.doclist) - ch.purchase_receipt = i and i['name'] or '' - ch.save() - - def get_selected_pr(self): - """ Get selected purchase receipt no """ - self.selected_pr = [d.purchase_receipt for d in getlist(self.doclist, 'lc_pr_details') if d.select_pr] - if not self.selected_pr: - msgprint("Please select atleast one PR to proceed.", raise_exception=1) - - def validate_selected_pr(self): - """Validate selected PR as submitted""" - invalid_pr = webnotes.conn.sql("SELECT name FROM `tabPurchase Receipt` WHERE docstatus != 1 and name in (%s)" % ("'" + "', '".join(self.selected_pr) + "'")) - if invalid_pr: - msgprint("Selected purchase receipts must be submitted. Following PR are not submitted: %s" % invalid_pr, raise_exception=1) + def update_landed_cost(self): + """ + Add extra cost and recalculate all values in pr, + Recalculate valuation rate in all sle after pr posting date + """ + purchase_receipts = [row.purchase_receipt for row in + self.doclist.get({"parentfield": "lc_pr_details"})] + self.validate_purchase_receipts(purchase_receipts) + self.cancel_pr(purchase_receipts) + self.add_charges_in_pr(purchase_receipts) + self.submit_pr(purchase_receipts) + msgprint("Landed Cost updated successfully") - def get_total_amt(self): - """ Get sum of net total of all selected PR""" - return webnotes.conn.sql("SELECT SUM(net_total) FROM `tabPurchase Receipt` WHERE name in (%s)" % ("'" + "', '".join(self.selected_pr) + "'"))[0][0] - + def validate_purchase_receipts(self, purchase_receipts): + for pr in purchase_receipts: + if webnotes.conn.get_value("Purchase Receipt", pr, "docstatus") != 1: + webnotes.throw(_("Purchase Receipt") + ": " + pr + _(" is not submitted document")) - def add_charges_in_pr(self): + def add_charges_in_pr(self, purchase_receipts): """ Add additional charges in selected pr proportionately""" - total_amt = self.get_total_amt() + total_amt = self.get_total_pr_amt(purchase_receipts) - for pr in self.selected_pr: - pr_obj = get_obj('Purchase Receipt', pr, with_children = 1) - cumulative_grand_total = flt(pr_obj.doc.grand_total) + for pr in purchase_receipts: + pr_bean = webnotes.bean('Purchase Receipt', pr) + idx = max([d.idx for d in pr_bean.doclist.get({"parentfield": "purchase_tax_details"})]) - for lc in getlist(self.doclist, 'landed_cost_details'): - amt = flt(lc.amount) * flt(pr_obj.doc.net_total)/ flt(total_amt) - self.prwise_cost[pr] = self.prwise_cost.get(pr, 0) + amt - cumulative_grand_total += amt + for lc in self.doclist.get({"parentfield": "landed_cost_details"}): + amt = flt(lc.amount) * flt(pr_bean.doc.net_total)/ flt(total_amt) - pr_oc_row = webnotes.conn.sql("select name from `tabPurchase Taxes and Charges` where parent = %s and category = 'Valuation' and add_deduct_tax = 'Add' and charge_type = 'Actual' and account_head = %s",(pr, lc.account_head)) - if not pr_oc_row: # add if not exists - ch = addchild(pr_obj.doc, 'purchase_tax_details', 'Purchase Taxes and Charges') + matched_row = pr_bean.doclist.get({ + "parentfield": "purchase_tax_details", + "category": "Valuation", + "add_deduct_tax": "Add", + "charge_type": "Actual", + "account_head": lc.account_head + }) + + if not matched_row: # add if not exists + ch = addchild(pr_bean.doc, 'purchase_tax_details', 'Purchase Taxes and Charges') ch.category = 'Valuation' ch.add_deduct_tax = 'Add' ch.charge_type = 'Actual' ch.description = lc.description ch.account_head = lc.account_head + ch.cost_center = lc.cost_center ch.rate = amt ch.tax_amount = amt - ch.total = cumulative_grand_total ch.docstatus = 1 - ch.idx = 500 # add at the end + ch.idx = idx ch.save(1) + idx += 1 else: # overwrite if exists - webnotes.conn.sql("update `tabPurchase Taxes and Charges` set rate = %s, tax_amount = %s where name = %s and parent = %s ", (amt, amt, pr_oc_row[0][0], pr)) - - - def reset_other_charges(self, pr_obj): - """ Reset all calculated values to zero""" - for t in getlist(pr_obj.doclist, 'purchase_tax_details'): - t.total_tax_amount = 0; - t.total_amount = 0; - t.tax_amount = 0; - t.total = 0; - t.save() - - - def cal_charges_and_item_tax_amt(self): - """ Re-calculates other charges values and itemwise tax amount for getting valuation rate""" - import json - for pr in self.selected_pr: - obj = get_obj('Purchase Receipt', pr, with_children = 1) - total = 0 - self.reset_other_charges(obj) - - for prd in getlist(obj.doclist, 'purchase_receipt_details'): - prev_total, item_tax = flt(prd.amount), 0 - total += flt(prd.qty) * flt(prd.purchase_rate) - - try: - item_tax_rate = prd.item_tax_rate and json.loads(prd.item_tax_rate) or {} - except ValueError: - item_tax_rate = prd.item_tax_rate and eval(prd.item_tax_rate) or {} - - - ocd = getlist(obj.doclist, 'purchase_tax_details') - # calculate tax for other charges - for oc in range(len(ocd)): - # Get rate : consider if diff for this item - if item_tax_rate.get(ocd[oc].account_head) and ocd[oc].charge_type != 'Actual': - rate = item_tax_rate[ocd[oc].account_head] - else: - rate = flt(ocd[oc].rate) - - tax_amount = self.cal_tax(ocd, prd, rate, obj.doc.net_total, oc) - total, prev_total, item_tax = self.add_deduct_taxes(ocd, oc, tax_amount, total, prev_total, item_tax) - - prd.item_tax_amount = flt(item_tax) - prd.save() - obj.doc.save() + matched_row[0].rate = amt + matched_row[0].tax_amount = amt + matched_row[0].cost_center = lc.cost_center + + pr_bean.run_method("validate") + for d in pr_bean.doclist: + d.save() - - def cal_tax(self, ocd, prd, rate, net_total, oc): - """ Calculates tax amount for one item""" - tax_amount = 0 - if ocd[oc].charge_type == 'Actual': - tax_amount = flt(rate) * flt(prd.amount) / flt(net_total) - elif ocd[oc].charge_type == 'On Net Total': - tax_amount = flt(rate) * flt(prd.amount) / 100 - elif ocd[oc].charge_type == 'On Previous Row Amount': - row_no = cstr(ocd[oc].row_id) - row = row_no.split("+") - for r in range(0, len(row)): - id = cint(row[r]) - tax_amount += flt((flt(rate) * flt(ocd[id-1].total_amount) / 100)) - row_id = row_no.find("/") - if row_id != -1: - rate = '' - row = (row_no).split("/") - id1 = cint(row[0]) - id2 = cint(row[1]) - tax_amount = flt(flt(ocd[id1-1].total_amount) / flt(ocd[id2-1].total_amount)) - elif ocd[oc].charge_type == 'On Previous Row Total': - row = cint(ocd[oc].row_id) - if ocd[row-1].add_deduct_tax == 'Add': - tax_amount = flt(rate) * (flt(ocd[row-1].total_tax_amount)+flt(ocd[row-1].total_amount)) / 100 - elif ocd[row-1].add_deduct_tax == 'Deduct': - tax_amount = flt(rate) * (flt(ocd[row-1].total_tax_amount)-flt(ocd[row-1].total_amount)) / 100 - - return tax_amount - - def add_deduct_taxes(self, ocd, oc, tax_amount, total, prev_total, item_tax): - """Calculates other charges values""" - add_ded = ocd[oc].add_deduct_tax == 'Add' and 1 or ocd[oc].add_or_deduct == 'Deduct' and -1 - ocd[oc].total_amount = flt(tax_amount) - ocd[oc].total_tax_amount = flt(prev_total) - ocd[oc].tax_amount += flt(tax_amount) - - if ocd[oc].category != "Valuation": - prev_total += add_ded * flt(ocd[oc].total_amount) - total += add_ded * flt(ocd[oc].tax_amount) - ocd[oc].total = total - else: - prev_total = prev_total - ocd[oc].total = flt(total) - ocd[oc].save() - - if ocd[oc].category != "Total": - item_tax += add_ded * ocd[oc].total_amount - - return total, prev_total, item_tax - - - def update_sle(self): - """ Recalculate valuation rate in all sle after pr posting date""" - from stock.stock_ledger import update_entries_after - - for pr in self.selected_pr: - pr_obj = get_obj('Purchase Receipt', pr, with_children = 1) + def get_total_pr_amt(self, purchase_receipts): + return webnotes.conn.sql("""SELECT SUM(net_total) FROM `tabPurchase Receipt` + WHERE name in (%s)""" % ', '.join(['%s']*len(purchase_receipts)), + tuple(purchase_receipts))[0][0] - for d in getlist(pr_obj.doclist, 'purchase_receipt_details'): - if flt(d.qty): - d.valuation_rate = (flt(d.purchase_rate) + (flt(d.rm_supp_cost)/flt(d.qty)) + (flt(d.item_tax_amount)/flt(d.qty))) / flt(d.conversion_factor) - d.save() - if d.serial_no: - self.update_serial_no(d.serial_no, d.valuation_rate) - webnotes.conn.sql("update `tabStock Ledger Entry` set incoming_rate = '%s' where voucher_detail_no = '%s'"%(flt(d.valuation_rate), d.name)) - - res = webnotes.conn.sql("""select item_code, warehouse, posting_date, posting_time - from `tabStock Ledger Entry` where voucher_detail_no = %s LIMIT 1""", - d.name, as_dict=1) - - # update valuation rate after pr posting date - if res: - update_entries_after(res[0]) - - - def update_serial_no(self, sr_no, rate): - """ update valuation rate in serial no""" - sr_no = map(lambda x: x.strip(), cstr(sr_no).split('\n')) - - webnotes.conn.sql("""update `tabSerial No` set purchase_rate = %s where name in (%s)""" % - ('%s', ', '.join(['%s']*len(sr_no))), tuple([rate] + sr_no)) - - def update_landed_cost(self): - """ - Add extra cost and recalculate all values in pr, - Recalculate valuation rate in all sle after pr posting date - """ - self.get_selected_pr() - self.validate_selected_pr() - self.add_charges_in_pr() - self.cal_charges_and_item_tax_amt() - self.update_sle() - msgprint("Landed Cost updated successfully") + def cancel_pr(self, purchase_receipts): + for pr in purchase_receipts: + pr_bean = webnotes.bean("Purchase Receipt", pr) + + pr_bean.run_method("update_ordered_qty", is_cancelled="Yes") + pr_bean.run_method("update_serial_nos", cancel=True) + + webnotes.conn.sql("""delete from `tabStock Ledger Entry` + where voucher_type='Purchase Receipt' and voucher_no=%s""", pr) + webnotes.conn.sql("""delete from `tabGL Entry` where voucher_type='Purchase Receipt' + and voucher_no=%s""", pr) + + def submit_pr(self, purchase_receipts): + for pr in purchase_receipts: + pr_bean = webnotes.bean("Purchase Receipt", pr) + pr_bean.run_method("update_ordered_qty") + pr_bean.run_method("update_stock") + pr_bean.run_method("update_serial_nos") + pr_bean.run_method("make_gl_entries") diff --git a/stock/doctype/landed_cost_wizard/landed_cost_wizard.txt b/stock/doctype/landed_cost_wizard/landed_cost_wizard.txt index 53f2407be7f..40f2e23c903 100644 --- a/stock/doctype/landed_cost_wizard/landed_cost_wizard.txt +++ b/stock/doctype/landed_cost_wizard/landed_cost_wizard.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-22 16:50:39", "docstatus": 0, - "modified": "2013-07-22 15:31:20", + "modified": "2013-09-02 19:13:09", "modified_by": "Administrator", "owner": "wasim@webnotestech.com" }, @@ -40,46 +40,19 @@ }, { "doctype": "DocField", - "fieldname": "process", - "fieldtype": "HTML", - "label": "Process", - "options": "
Process:
1. Fetch and select Purchase Receipt
2. Enter extra costs
3. Click on Update Landed Cost button
4. Cost will be added into other charges table of selected PR proportionately based on net total
5. Item Valuation Rate will be recalculated
" + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 }, { "doctype": "DocField", "fieldname": "section_break0", "fieldtype": "Section Break", + "label": "Select Purchase Receipts", "options": "Simple" }, - { - "doctype": "DocField", - "fieldname": "from_pr_date", - "fieldtype": "Date", - "label": "From PR Date", - "reqd": 1 - }, - { - "doctype": "DocField", - "fieldname": "to_pr_date", - "fieldtype": "Date", - "label": "To PR Date", - "reqd": 1 - }, - { - "doctype": "DocField", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Currency", - "options": "Currency", - "reqd": 1 - }, - { - "doctype": "DocField", - "fieldname": "get_purchase_receipt", - "fieldtype": "Button", - "label": "Get Purchase Receipt", - "options": "get_purchase_receipts" - }, { "doctype": "DocField", "fieldname": "lc_pr_details", @@ -91,7 +64,7 @@ "doctype": "DocField", "fieldname": "section_break1", "fieldtype": "Section Break", - "options": "Simple" + "label": "Add Taxes and Charges" }, { "doctype": "DocField", @@ -102,9 +75,9 @@ }, { "doctype": "DocField", - "fieldname": "update_pr", + "fieldname": "update_landed_cost", "fieldtype": "Button", - "label": "Update PR", + "label": "Update Landed Cost", "options": "update_landed_cost" }, { diff --git a/stock/doctype/material_request/material_request.py b/stock/doctype/material_request/material_request.py index 9cb0bc5dd8a..f0022655091 100644 --- a/stock/doctype/material_request/material_request.py +++ b/stock/doctype/material_request/material_request.py @@ -87,6 +87,8 @@ class DocType(BuyingController): def update_bin(self, is_submit, is_stopped): """ Update Quantity Requested for Purchase in Bin for Material Request of type 'Purchase'""" + + from stock.utils import update_bin for d in getlist(self.doclist, 'indent_details'): if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes": if not d.warehouse: @@ -99,10 +101,11 @@ class DocType(BuyingController): args = { "item_code": d.item_code, + "warehouse": d.warehouse, "indented_qty": (is_submit and 1 or -1) * flt(qty), "posting_date": self.doc.transaction_date } - get_obj('Warehouse', d.warehouse).update_bin(args) + update_bin(args) def on_submit(self): purchase_controller = webnotes.get_obj("Purchase Common") @@ -200,6 +203,7 @@ def update_completed_qty(controller, caller_method): def _update_requested_qty(controller, mr_obj, mr_items): """update requested qty (before ordered_qty is updated)""" + from stock.utils import update_bin for mr_item_name in mr_items: mr_item = mr_obj.doclist.getone({"parentfield": "indent_details", "name": mr_item_name}) se_detail = controller.doclist.getone({"parentfield": "mtn_details", @@ -218,8 +222,9 @@ def _update_requested_qty(controller, mr_obj, mr_items): else: add_indented_qty = se_detail.transfer_qty - webnotes.get_obj("Warehouse", se_detail.t_warehouse).update_bin({ + update_bin({ "item_code": se_detail.item_code, + "warehouse": se_detail.t_warehouse, "indented_qty": (se_detail.docstatus==2 and 1 or -1) * add_indented_qty, "posting_date": controller.doc.posting_date, }) diff --git a/stock/doctype/material_request/test_material_request.py b/stock/doctype/material_request/test_material_request.py index 80233d7a5f0..8ebad15810a 100644 --- a/stock/doctype/material_request/test_material_request.py +++ b/stock/doctype/material_request/test_material_request.py @@ -10,7 +10,7 @@ from webnotes.utils import flt class TestMaterialRequest(unittest.TestCase): def setUp(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 0) def test_make_purchase_order(self): from stock.doctype.material_request.material_request import make_purchase_order diff --git a/stock/doctype/purchase_receipt/purchase_receipt.py b/stock/doctype/purchase_receipt/purchase_receipt.py index 1d48975b372..278becd7e15 100644 --- a/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/stock/doctype/purchase_receipt/purchase_receipt.py @@ -9,7 +9,7 @@ from webnotes.model.bean import getlist from webnotes.model.code import get_obj from webnotes import msgprint import webnotes.defaults - +from stock.utils import update_bin from controllers.buying_controller import BuyingController class DocType(BuyingController): @@ -42,16 +42,46 @@ class DocType(BuyingController): def get_bin_details(self, arg = ''): return get_obj(dt='Purchase Common').get_bin_details(arg) + def validate(self): + super(DocType, self).validate() + + self.po_required() + + if not self.doc.status: + self.doc.status = "Draft" + + import utilities + utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Cancelled"]) + + self.validate_with_previous_doc() + self.validate_rejected_warehouse() + self.validate_accepted_rejected_qty() + self.validate_inspection() + self.validate_uom_is_integer("uom", ["qty", "received_qty"]) + self.validate_uom_is_integer("stock_uom", "stock_qty") + self.validate_challan_no() + + pc_obj = get_obj(dt='Purchase Common') + pc_obj.validate_for_items(self) + pc_obj.get_prevdoc_date(self) + self.check_for_stopped_status(pc_obj) + + # sub-contracting + self.validate_for_subcontracting() + self.update_raw_materials_supplied("pr_raw_material_details") + + self.update_valuation_rate("purchase_receipt_details") + + def validate_rejected_warehouse(self): + for d in self.doclist.get({"parentfield": "purchase_receipt_details"}): + if flt(d.rejected_qty) and not d.rejected_warehouse: + d.rejected_warehouse = self.doc.rejected_warehouse + if not d.rejected_warehouse: + webnotes.throw(_("Rejected Warehouse is mandatory against regected item")) # validate accepted and rejected qty def validate_accepted_rejected_qty(self): for d in getlist(self.doclist, "purchase_receipt_details"): - - # If Reject Qty than Rejected warehouse is mandatory - if flt(d.rejected_qty) and (not self.doc.rejected_warehouse): - msgprint("Rejected Warehouse is necessary if there are rejections.") - raise Exception - if not flt(d.received_qty) and flt(d.qty): d.received_qty = flt(d.qty) - flt(d.rejected_qty) @@ -110,109 +140,70 @@ class DocType(BuyingController): msgprint("Purchse Order No. required against item %s"%d.item_code) raise Exception - def validate(self): - super(DocType, self).validate() + def update_stock(self): + sl_entries = [] + stock_items = self.get_stock_items() - self.po_required() - - if not self.doc.status: - self.doc.status = "Draft" - - import utilities - utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Cancelled"]) - - self.validate_with_previous_doc() - self.validate_accepted_rejected_qty() - self.validate_inspection() - self.validate_uom_is_integer("uom", ["qty", "received_qty"]) - self.validate_uom_is_integer("stock_uom", "stock_qty") - self.validate_challan_no() - - pc_obj = get_obj(dt='Purchase Common') - pc_obj.validate_for_items(self) - pc_obj.get_prevdoc_date(self) - self.check_for_stopped_status(pc_obj) - - # sub-contracting - self.validate_for_subcontracting() - self.update_raw_materials_supplied("pr_raw_material_details") - - self.update_valuation_rate("purchase_receipt_details") - - def on_update(self): - if self.doc.rejected_warehouse: - for d in getlist(self.doclist,'purchase_receipt_details'): - d.rejected_warehouse = self.doc.rejected_warehouse - - def update_stock(self, is_submit): - pc_obj = get_obj('Purchase Common') - self.values = [] for d in getlist(self.doclist, 'purchase_receipt_details'): - if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes": - if not d.warehouse: - continue + if d.item_code in stock_items and d.warehouse: + pr_qty = flt(d.qty) * flt(d.conversion_factor) - ord_qty = 0 + if pr_qty: + sl_entries.append(self.get_sl_entries(d, { + "actual_qty": flt(pr_qty), + "serial_no": cstr(d.serial_no).strip(), + "incoming_rate": d.valuation_rate + })) + + if flt(d.rejected_qty) > 0: + sl_entries.append(self.get_sl_entries(d, { + "warehouse": d.rejected_warehouse, + "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), + "serial_no": cstr(d.rejected_serial_no).strip(), + "incoming_rate": d.valuation_rate + })) + + self.bk_flush_supp_wh(sl_entries) + self.make_sl_entries(sl_entries) + + def update_ordered_qty(self, is_cancelled="No"): + pc_obj = get_obj('Purchase Common') + stock_items = self.get_stock_items() + for d in getlist(self.doclist, 'purchase_receipt_details'): + if d.item_code in stock_items and d.warehouse \ + and cstr(d.prevdoc_doctype) == 'Purchase Order': pr_qty = flt(d.qty) * flt(d.conversion_factor) - if cstr(d.prevdoc_doctype) == 'Purchase Order': - # get qty and pending_qty of prevdoc - curr_ref_qty = pc_obj.get_qty( d.doctype, 'prevdoc_detail_docname', - d.prevdoc_detail_docname, 'Purchase Order Item', - 'Purchase Order - Purchase Receipt', self.doc.name) - max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \ - flt(curr_ref_qty.split('~~~')[0]), 0 - - if flt(qty) + flt(pr_qty) > flt(max_qty): - curr_qty = (flt(max_qty) - flt(qty)) * flt(d.conversion_factor) - else: - curr_qty = flt(pr_qty) - - ord_qty = -flt(curr_qty) - - # update ordered qty in bin - args = { - "item_code": d.item_code, - "posting_date": self.doc.posting_date, - "ordered_qty": (is_submit and 1 or -1) * flt(ord_qty) - } - get_obj("Warehouse", d.warehouse).update_bin(args) - - # UPDATE actual qty to warehouse by pr_qty - if pr_qty: - self.make_sl_entry(d, d.warehouse, flt(pr_qty), d.valuation_rate, is_submit) - - # UPDATE actual to rejected warehouse by rejected qty - if flt(d.rejected_qty) > 0: - self.make_sl_entry(d, self.doc.rejected_warehouse, flt(d.rejected_qty) * flt(d.conversion_factor), d.valuation_rate, is_submit, rejected = 1) - - self.bk_flush_supp_wh(is_submit) - - if self.values: - get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values) - - - # make Stock Entry - def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0): - self.values.append({ - 'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code, - 'warehouse' : wh, - 'posting_date' : self.doc.posting_date, - 'posting_time' : self.doc.posting_time, - 'voucher_type' : 'Purchase Receipt', - 'voucher_no' : self.doc.name, - 'voucher_detail_no' : d.name, - 'actual_qty' : qty, - 'stock_uom' : d.stock_uom, - 'incoming_rate' : in_value, - 'company' : self.doc.company, - 'fiscal_year' : self.doc.fiscal_year, - 'is_cancelled' : (is_submit==1) and 'No' or 'Yes', - 'batch_no' : cstr(d.batch_no).strip(), - 'serial_no' : d.serial_no, - "project" : d.project_name - }) + # get qty and pending_qty of prevdoc + curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname', + d.prevdoc_detail_docname, 'Purchase Order Item', + 'Purchase Order - Purchase Receipt', self.doc.name) + max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \ + flt(curr_ref_qty.split('~~~')[0]), 0 + if flt(qty) + flt(pr_qty) > flt(max_qty): + curr_qty = (flt(max_qty) - flt(qty)) * flt(d.conversion_factor) + else: + curr_qty = flt(pr_qty) + + args = { + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": self.doc.posting_date, + "ordered_qty": (is_cancelled=="Yes" and -1 or 1)*flt(curr_qty) + } + update_bin(args) + + def bk_flush_supp_wh(self, sl_entries): + for d in getlist(self.doclist, 'pr_raw_material_details'): + # negative quantity is passed as raw material qty has to be decreased + # when PR is submitted and it has to be increased when PR is cancelled + sl_entries.append(self.get_sl_entries(d, { + "item_code": d.rm_item_code, + "warehouse": self.doc.supplier_warehouse, + "actual_qty": -1*flt(consumed_qty), + "incoming_rate": 0 + })) def validate_inspection(self): for d in getlist(self.doclist, 'purchase_receipt_details'): #Enter inspection date for all items that require inspection @@ -243,12 +234,12 @@ class DocType(BuyingController): self.update_prevdoc_status() - # Update Stock - self.update_stock(is_submit = 1) + self.update_ordered_qty() + + self.update_stock() self.update_serial_nos() - # Update last purchase rate purchase_controller.update_last_purchase_rate(self, 1) self.make_gl_entries() @@ -279,7 +270,7 @@ class DocType(BuyingController): pc_obj = get_obj('Purchase Common') self.check_for_stopped_status(pc_obj) - # 1.Check if Purchase Invoice has been submitted against current Purchase Order + # Check if Purchase Invoice has been submitted against current Purchase Order # pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Invoice', docname = self.doc.name, detail_doctype = 'Purchase Invoice Item') submitted = webnotes.conn.sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % self.doc.name) @@ -287,29 +278,19 @@ class DocType(BuyingController): msgprint("Purchase Invoice : " + cstr(submitted[0][0]) + " has already been submitted !") raise Exception - # 2.Set Status as Cancelled + webnotes.conn.set(self.doc,'status','Cancelled') - # 3. Cancel Serial No - - # 4.Update Bin - self.update_stock(is_submit = 0) + self.update_ordered_qty(is_cancelled="Yes") + + self.update_stock() self.update_serial_nos(cancel=True) self.update_prevdoc_status() - - # 6. Update last purchase rate pc_obj.update_last_purchase_rate(self, 0) self.make_cancel_gl_entries() - - def bk_flush_supp_wh(self, is_submit): - for d in getlist(self.doclist, 'pr_raw_material_details'): - # negative quantity is passed as raw material qty has to be decreased - # when PR is submitted and it has to be increased when PR is cancelled - consumed_qty = - flt(d.consumed_qty) - self.make_sl_entry(d, self.doc.supplier_warehouse, flt(consumed_qty), 0, is_submit) - + def get_current_stock(self): for d in getlist(self.doclist, 'pr_raw_material_details'): if self.doc.supplier_warehouse: @@ -319,29 +300,13 @@ class DocType(BuyingController): def get_rate(self,arg): return get_obj('Purchase Common').get_rate(arg,self) - - def make_gl_entries(self): - if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")): - return - - from accounts.general_ledger import make_gl_entries - + + def get_gl_entries_for_stock(self, warehouse_account=None): against_stock_account = self.get_company_default("stock_received_but_not_billed") - total_valuation_amount = self.get_total_valuation_amount() - gl_entries = self.get_gl_entries_for_stock(against_stock_account, total_valuation_amount) - if gl_entries: - make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2)) - - def get_total_valuation_amount(self): - total_valuation_amount = 0.0 - - for item in self.doclist.get({"parentfield": "purchase_receipt_details"}): - if item.item_code in self.stock_items: - total_valuation_amount += flt(item.valuation_rate) * \ - flt(item.qty) * flt(item.conversion_factor) - - return total_valuation_amount + gl_entries = super(DocType, self).get_gl_entries_for_stock(warehouse_account, + against_stock_account) + return gl_entries @webnotes.whitelist() diff --git a/stock/doctype/purchase_receipt/test_purchase_receipt.py b/stock/doctype/purchase_receipt/test_purchase_receipt.py index af040231bb5..010c29be79f 100644 --- a/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -10,6 +10,8 @@ from webnotes.utils import cint class TestPurchaseReceipt(unittest.TestCase): def test_make_purchase_invoice(self): + self._clear_stock_account_balance() + set_perpetual_inventory(0) from stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice pr = webnotes.bean(copy=test_records[0]).insert() @@ -29,45 +31,63 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertRaises(webnotes.ValidationError, webnotes.bean(pi).submit) def test_purchase_receipt_no_gl_entry(self): + self._clear_stock_account_balance() + set_perpetual_inventory(0) pr = webnotes.bean(copy=test_records[0]) - pr.run_method("calculate_taxes_and_totals") pr.insert() pr.submit() - gl_entries = webnotes.conn.sql("""select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s - order by account desc""", pr.doc.name, as_dict=1) - - self.assertTrue(not gl_entries) + stock_value, stock_value_difference = webnotes.conn.get_value("Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.doc.name, + "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, + ["stock_value", "stock_value_difference"]) + self.assertEqual(stock_value, 375) + self.assertEqual(stock_value_difference, 375) + + bin_stock_value = webnotes.conn.get_value("Bin", {"item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC"}, "stock_value") + self.assertEqual(bin_stock_value, 375) + + self.assertFalse(get_gl_entries("Purchase Receipt", pr.doc.name)) def test_purchase_receipt_gl_entry(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) + self._clear_stock_account_balance() + + set_perpetual_inventory() + self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 1) pr = webnotes.bean(copy=test_records[0]) - pr.run_method("calculate_taxes_and_totals") pr.insert() pr.submit() - gl_entries = webnotes.conn.sql("""select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s - order by account desc""", pr.doc.name, as_dict=1) + gl_entries = get_gl_entries("Purchase Receipt", pr.doc.name) + self.assertTrue(gl_entries) - stock_in_hand_account = webnotes.conn.get_value("Company", pr.doc.company, - "stock_in_hand_account") + stock_in_hand_account = webnotes.conn.get_value("Account", + {"master_name": pr.doclist[1].warehouse}) + fixed_asset_account = webnotes.conn.get_value("Account", + {"master_name": pr.doclist[2].warehouse}) - expected_values = [ - [stock_in_hand_account, 750.0, 0.0], - ["Stock Received But Not Billed - _TC", 0.0, 750.0] - ] + expected_values = { + stock_in_hand_account: [375.0, 0.0], + fixed_asset_account: [375.0, 0.0], + "Stock Received But Not Billed - _TC": [0.0, 750.0] + } - for i, gle in enumerate(gl_entries): - self.assertEquals(expected_values[i][0], gle.account) - self.assertEquals(expected_values[i][1], gle.debit) - self.assertEquals(expected_values[i][2], gle.credit) + for gle in gl_entries: + self.assertEquals(expected_values[gle.account][0], gle.debit) + self.assertEquals(expected_values[gle.account][1], gle.credit) - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + pr.cancel() + self.assertFalse(get_gl_entries("Purchase Receipt", pr.doc.name)) + + set_perpetual_inventory(0) + + def _clear_stock_account_balance(self): + webnotes.conn.sql("delete from `tabStock Ledger Entry`") + webnotes.conn.sql("""delete from `tabBin`""") + webnotes.conn.sql("""delete from `tabGL Entry`""") def test_subcontracting(self): pr = webnotes.bean(copy=test_records[1]) @@ -95,7 +115,16 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no, "warehouse")) + +def get_gl_entries(voucher_type, voucher_no): + return webnotes.conn.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type=%s and voucher_no=%s + order by account desc""", (voucher_type, voucher_no), as_dict=1) +def set_perpetual_inventory(enable=1): + accounts_settings = webnotes.bean("Accounts Settings") + accounts_settings.doc.auto_accounting_for_stock = enable + accounts_settings.save() test_dependencies = ["BOM"] @@ -122,15 +151,31 @@ test_records = [ "item_code": "_Test Item", "item_name": "_Test Item", "parentfield": "purchase_receipt_details", - "received_qty": 10.0, - "qty": 10.0, + "received_qty": 5.0, + "qty": 5.0, "rejected_qty": 0.0, "import_rate": 50.0, - "amount": 500.0, + "amount": 250.0, "warehouse": "_Test Warehouse - _TC", "stock_uom": "Nos", "uom": "_Test UOM", }, + { + "conversion_factor": 1.0, + "description": "_Test Item", + "doctype": "Purchase Receipt Item", + "item_code": "_Test Item", + "item_name": "_Test Item", + "parentfield": "purchase_receipt_details", + "received_qty": 5.0, + "qty": 5.0, + "rejected_qty": 0.0, + "import_rate": 50.0, + "amount": 250.0, + "warehouse": "_Test Warehouse 1 - _TC", + "stock_uom": "Nos", + "uom": "_Test UOM", + }, { "account_head": "_Test Account Shipping Charges - _TC", "add_deduct_tax": "Add", diff --git a/stock/doctype/purchase_receipt_item/purchase_receipt_item.txt b/stock/doctype/purchase_receipt_item/purchase_receipt_item.txt index ec3c8e342d3..ce491689ec6 100755 --- a/stock/doctype/purchase_receipt_item/purchase_receipt_item.txt +++ b/stock/doctype/purchase_receipt_item/purchase_receipt_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 19:29:10", "docstatus": 0, - "modified": "2013-08-07 14:45:23", + "modified": "2013-09-20 11:36:55", "modified_by": "Administrator", "owner": "Administrator" }, @@ -310,7 +310,7 @@ "doctype": "DocField", "fieldname": "rejected_warehouse", "fieldtype": "Link", - "hidden": 1, + "hidden": 0, "label": "Rejected Warehouse", "no_copy": 1, "oldfieldname": "rejected_warehouse", @@ -318,7 +318,7 @@ "options": "Warehouse", "print_hide": 1, "print_width": "100px", - "read_only": 1, + "read_only": 0, "width": "100px" }, { diff --git a/stock/doctype/serial_no/serial_no.txt b/stock/doctype/serial_no/serial_no.txt index 2eba0f4fa67..8500303dc14 100644 --- a/stock/doctype/serial_no/serial_no.txt +++ b/stock/doctype/serial_no/serial_no.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-16 10:59:15", "docstatus": 0, - "modified": "2013-08-21 13:37:01", + "modified": "2013-08-28 19:13:09", "modified_by": "Administrator", "owner": "Administrator" }, @@ -35,7 +35,8 @@ "parenttype": "DocType", "permlevel": 0, "read": 1, - "write": 1 + "report": 1, + "submit": 0 }, { "doctype": "DocType", @@ -449,22 +450,23 @@ "cancel": 1, "create": 1, "doctype": "DocPerm", - "report": 1, - "role": "Material Manager", - "submit": 0 + "role": "Material Master Manager", + "write": 1 }, { "amend": 0, "cancel": 0, - "create": 1, - "doctype": "DocPerm", - "report": 1, - "role": "Material User", - "submit": 0 - }, - { "create": 0, "doctype": "DocPerm", - "role": "Accounts User" + "role": "Material Manager", + "write": 0 + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "doctype": "DocPerm", + "role": "Material User", + "write": 0 } ] \ No newline at end of file diff --git a/stock/doctype/serial_no/test_serial_no.py b/stock/doctype/serial_no/test_serial_no.py index 8e5f7756517..de824490e68 100644 --- a/stock/doctype/serial_no/test_serial_no.py +++ b/stock/doctype/serial_no/test_serial_no.py @@ -24,6 +24,6 @@ class TestSerialNo(unittest.TestCase): sr.doc.warehouse = None sr.insert() self.assertTrue(sr.doc.name) - + sr.doc.warehouse = "_Test Warehouse - _TC" self.assertTrue(SerialNoCannotCannotChangeError, sr.doc.save) \ No newline at end of file diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js index e3690817267..4695fdb22c2 100644 --- a/stock/doctype/stock_entry/stock_entry.js +++ b/stock/doctype/stock_entry/stock_entry.js @@ -38,10 +38,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ } }; - if(cint(wn.defaults.get_default("auto_inventory_accounting"))) { - this.frm.add_fetch("company", "stock_adjustment_account", "expense_adjustment_account"); - - this.frm.fields_dict["expense_adjustment_account"].get_query = function() { + if(cint(wn.defaults.get_default("auto_accounting_for_stock"))) { + this.frm.add_fetch("company", "stock_adjustment_account", "expense_account"); + this.frm.fields_dict.mtn_details.grid.get_field('expense_account').get_query = + function() { return { filters: { "company": me.frm.doc.company, @@ -88,7 +88,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ set_default_account: function() { var me = this; - if (cint(wn.defaults.get_default("auto_inventory_accounting")) && !this.frm.doc.expense_adjustment_account) { + if(cint(wn.defaults.get_default("auto_accounting_for_stock"))) { var account_for = "stock_adjustment_account"; if (this.frm.doc.purpose == "Sales Return") account_for = "stock_in_hand_account"; @@ -102,12 +102,22 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ "company": this.frm.doc.company }, callback: function(r) { - if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message); + if (!r.exc) { + for(d in getchildren('Stock Entry Detail',doc.name,'mtn_details')) { + if(!d.expense_account) d.expense_account = r.message; + } + } } }); } }, + entries_add: function(doc, cdt, cdn) { + var row = wn.model.get_doc(cdt, cdn); + this.frm.script_manager.copy_from_first_row("mtn_details", row, + ["expense_account", "cost_center"]); + }, + clean_up: function() { // Clear Production Order record from locals, because it is updated via Stock Entry if(this.frm.doc.production_order && diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index 1fdd609a62e..de5636f0ae6 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -38,7 +38,6 @@ class DocType(StockController): self.validate_item() self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("stock_uom", "transfer_qty") - self.validate_warehouse(pro_obj) self.validate_production_order(pro_obj) self.get_stock_and_rate() @@ -51,13 +50,13 @@ class DocType(StockController): self.set_total_amount() def on_submit(self): - self.update_stock_ledger(0) + self.update_stock_ledger() self.update_serial_no(1) self.update_production_order(1) self.make_gl_entries() def on_cancel(self): - self.update_stock_ledger(1) + self.update_stock_ledger() self.update_serial_no(0) self.update_production_order(0) self.make_cancel_gl_entries() @@ -75,8 +74,9 @@ class DocType(StockController): raise_exception=True) def validate_item(self): + stock_items = self.get_stock_items() for item in self.doclist.get({"parentfield": "mtn_details"}): - if item.item_code not in self.stock_items: + if item.item_code not in stock_items: msgprint(_("""Only Stock Items are allowed for Stock Entry"""), raise_exception=True) @@ -176,33 +176,6 @@ class DocType(StockController): def set_total_amount(self): self.doc.total_amount = sum([flt(item.amount) for item in self.doclist.get({"parentfield": "mtn_details"})]) - def make_gl_entries(self): - if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")): - return - - if not self.doc.expense_adjustment_account: - webnotes.msgprint(_("Please enter Expense/Adjustment Account"), raise_exception=1) - - from accounts.general_ledger import make_gl_entries - - total_valuation_amount = self.get_total_valuation_amount() - - gl_entries = self.get_gl_entries_for_stock(self.doc.expense_adjustment_account, - total_valuation_amount) - if gl_entries: - make_gl_entries(gl_entries, cancel=self.doc.docstatus == 2) - - def get_total_valuation_amount(self): - total_valuation_amount = 0 - for item in self.doclist.get({"parentfield": "mtn_details"}): - if item.t_warehouse and not item.s_warehouse: - total_valuation_amount += flt(item.incoming_rate, 2) * flt(item.transfer_qty) - - if item.s_warehouse and not item.t_warehouse: - total_valuation_amount -= flt(item.incoming_rate, 2) * flt(item.transfer_qty) - - return total_valuation_amount - def get_stock_and_rate(self): """get stock and incoming rate on posting date""" for d in getlist(self.doclist, 'mtn_details'): @@ -231,7 +204,7 @@ class DocType(StockController): sle = webnotes.conn.sql("""select name, posting_date, posting_time, actual_qty, stock_value, warehouse from `tabStock Ledger Entry` where voucher_type = %s and voucher_no = %s and - item_code = %s and ifnull(is_cancelled, 'No') = 'No' limit 1""", + item_code = %s limit 1""", ((self.doc.delivery_note_no and "Delivery Note" or "Sales Invoice"), self.doc.delivery_note_no or self.doc.sales_invoice_no, args.item_code), as_dict=1) if sle: @@ -339,20 +312,34 @@ class DocType(StockController): sr.doc.status = "Sales Returned" if is_submit else "Delivered" sr.save() - def update_stock_ledger(self, is_cancelled=0): - self.values = [] + def update_stock_ledger(self): + sl_entries = [] for d in getlist(self.doclist, 'mtn_details'): - if cstr(d.s_warehouse) and not is_cancelled: - self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled) + if cstr(d.s_warehouse) and self.doc.docstatus == 1: + sl_entries.append(self.get_sl_entries(d, { + "warehouse": cstr(d.s_warehouse), + "actual_qty": -flt(d.transfer_qty), + "incoming_rate": 0 + })) if cstr(d.t_warehouse): - self.add_to_values(d, cstr(d.t_warehouse), flt(d.transfer_qty), is_cancelled) + sl_entries.append(self.get_sl_entries(d, { + "warehouse": cstr(d.t_warehouse), + "actual_qty": flt(d.transfer_qty), + "incoming_rate": flt(d.incoming_rate) + })) + + # On cancellation, make stock ledger entry for + # target warehouse first, to update serial no values properly + + if cstr(d.s_warehouse) and self.doc.docstatus == 2: + sl_entries.append(self.get_sl_entries(d, { + "warehouse": cstr(d.s_warehouse), + "actual_qty": -flt(d.transfer_qty), + "incoming_rate": 0 + })) - if cstr(d.s_warehouse) and is_cancelled: - self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled) - - get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values, - self.doc.amended_from and 'Yes' or 'No') + self.make_sl_entries(sl_entries, self.doc.amended_from and 'Yes' or 'No') def update_production_order(self, is_submit): if self.doc.production_order: @@ -370,14 +357,16 @@ class DocType(StockController): # update bin if self.doc.purpose == "Manufacture/Repack": + from stock.utils import update_bin pro_obj.doc.produced_qty = flt(pro_obj.doc.produced_qty) + \ (is_submit and 1 or -1 ) * flt(self.doc.fg_completed_qty) args = { "item_code": pro_obj.doc.production_item, + "warehouse": pro_obj.doc.fg_warehouse, "posting_date": self.doc.posting_date, "planned_qty": (is_submit and -1 or 1 ) * flt(self.doc.fg_completed_qty) } - get_obj('Warehouse', pro_obj.doc.fg_warehouse).update_bin(args) + update_bin(args) # update production order status pro_obj.doc.status = (flt(pro_obj.doc.qty)==flt(pro_obj.doc.produced_qty)) \ @@ -413,7 +402,7 @@ class DocType(StockController): arg, ret = eval(arg), {} uom = webnotes.conn.sql("""select conversion_factor from `tabUOM Conversion Detail` where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1) - if not uom: + if not uom or not flt(uom[0].conversion_factor): msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'], arg['item_code'])) ret = {'uom' : ''} @@ -629,26 +618,6 @@ class DocType(StockController): # to be assigned for finished item se_child.bom_no = bom_no - def add_to_values(self, d, wh, qty, is_cancelled): - self.values.append({ - 'item_code': d.item_code, - 'warehouse': wh, - 'posting_date': self.doc.posting_date, - 'posting_time': self.doc.posting_time, - 'voucher_type': 'Stock Entry', - 'voucher_no': self.doc.name, - 'voucher_detail_no': d.name, - 'actual_qty': qty, - 'incoming_rate': flt(d.incoming_rate, 2) or 0, - 'stock_uom': d.stock_uom, - 'company': self.doc.company, - 'is_cancelled': (is_cancelled ==1) and 'Yes' or 'No', - 'batch_no': cstr(d.batch_no).strip(), - 'serial_no': cstr(d.serial_no).strip(), - "project": self.doc.project_name, - "fiscal_year": self.doc.fiscal_year, - }) - def get_cust_values(self): """fetches customer details""" if self.doc.delivery_note_no: @@ -787,7 +756,6 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): from `tabStock Ledger Entry` sle where item_code = '%(item_code)s' and warehouse = '%(s_warehouse)s' - and ifnull(is_cancelled, 'No') = 'No' and batch_no like '%(txt)s' and exists(select * from `tabBatch` where name = sle.batch_no @@ -885,7 +853,8 @@ def make_return_jv(stock_entry): "account": r.get("account"), "against_invoice": r.get("against_invoice"), "against_voucher": r.get("against_voucher"), - "balance": get_balance_on(r.get("account"), se.doc.posting_date) + "balance": get_balance_on(r.get("account"), se.doc.posting_date) \ + if r.get("account") else 0 }) return jv_list diff --git a/stock/doctype/stock_entry/stock_entry.txt b/stock/doctype/stock_entry/stock_entry.txt index 911c92fd263..204ebfa4d49 100644 --- a/stock/doctype/stock_entry/stock_entry.txt +++ b/stock/doctype/stock_entry/stock_entry.txt @@ -2,7 +2,7 @@ { "creation": "2013-04-09 11:43:55", "docstatus": 0, - "modified": "2013-08-08 14:22:31", + "modified": "2013-08-24 15:16:34", "modified_by": "Administrator", "owner": "Administrator" }, @@ -199,16 +199,6 @@ "reqd": 1, "search_index": 0 }, - { - "depends_on": "eval:sys_defaults.auto_inventory_accounting", - "doctype": "DocField", - "fieldname": "expense_adjustment_account", - "fieldtype": "Link", - "label": "Expense/Adjustment Account", - "options": "Account", - "print_hide": 1, - "read_only": 0 - }, { "doctype": "DocField", "fieldname": "items_section", diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py index 3beb61ddc83..e2358eba4f0 100644 --- a/stock/doctype/stock_entry/test_stock_entry.py +++ b/stock/doctype/stock_entry/test_stock_entry.py @@ -8,17 +8,19 @@ from __future__ import unicode_literals import webnotes, unittest from webnotes.utils import flt from stock.doctype.stock_ledger_entry.stock_ledger_entry import * +from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory + class TestStockEntry(unittest.TestCase): def tearDown(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + set_perpetual_inventory(0) if hasattr(self, "old_default_company"): webnotes.conn.set_default("company", self.old_default_company) def test_auto_material_request(self): webnotes.conn.sql("""delete from `tabMaterial Request Item`""") webnotes.conn.sql("""delete from `tabMaterial Request`""") - self._clear_stock() + self._clear_stock_account_balance() webnotes.conn.set_value("Stock Settings", None, "auto_indent", True) @@ -41,6 +43,7 @@ class TestStockEntry(unittest.TestCase): webnotes.conn.set_default("company", self.old_default_company) def test_warehouse_company_validation(self): + self._clear_stock_account_balance() webnotes.session.user = "test2@example.com" webnotes.bean("Profile", "test2@example.com").get_controller()\ .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") @@ -79,15 +82,15 @@ class TestStockEntry(unittest.TestCase): webnotes.session.user = "Administrator" def test_material_receipt_gl_entry(self): - webnotes.conn.sql("delete from `tabStock Ledger Entry`") - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + self._clear_stock_account_balance() + set_perpetual_inventory() mr = webnotes.bean(copy=test_records[0]) mr.insert() mr.submit() - stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company", - "stock_in_hand_account") + stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse", + "master_name": mr.doclist[1].t_warehouse}) self.check_stock_ledger_entries("Stock Entry", mr.doc.name, [["_Test Item", "_Test Warehouse - _TC", 50.0]]) @@ -100,37 +103,30 @@ class TestStockEntry(unittest.TestCase): ) mr.cancel() - self.check_stock_ledger_entries("Stock Entry", mr.doc.name, - sorted([["_Test Item", "_Test Warehouse - _TC", 50.0], - ["_Test Item", "_Test Warehouse - _TC", -50.0]])) - - self.check_gl_entries("Stock Entry", mr.doc.name, - sorted([ - [stock_in_hand_account, 5000.0, 0.0], - ["Stock Adjustment - _TC", 0.0, 5000.0], - [stock_in_hand_account, 0.0, 5000.0], - ["Stock Adjustment - _TC", 5000.0, 0.0] - ]) - ) + + self.assertFalse(webnotes.conn.sql("""select * from `tabStock Ledger Entry` + where voucher_type='Stock Entry' and voucher_no=%s""", mr.doc.name)) + + self.assertFalse(webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type='Stock Entry' and voucher_no=%s""", mr.doc.name)) + def test_material_issue_gl_entry(self): - self._clear_stock() - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + self._clear_stock_account_balance() + set_perpetual_inventory() - mr = webnotes.bean(copy=test_records[0]) - mr.insert() - mr.submit() + self._insert_material_receipt() mi = webnotes.bean(copy=test_records[1]) mi.insert() mi.submit() - stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company", - "stock_in_hand_account") - self.check_stock_ledger_entries("Stock Entry", mi.doc.name, [["_Test Item", "_Test Warehouse - _TC", -40.0]]) - + + stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse", + "master_name": mi.doclist[1].s_warehouse}) + self.check_gl_entries("Stock Entry", mi.doc.name, sorted([ [stock_in_hand_account, 0.0, 4000.0], @@ -139,28 +135,24 @@ class TestStockEntry(unittest.TestCase): ) mi.cancel() + self.assertFalse(webnotes.conn.sql("""select * from `tabStock Ledger Entry` + where voucher_type='Stock Entry' and voucher_no=%s""", mi.doc.name)) - self.check_stock_ledger_entries("Stock Entry", mi.doc.name, - sorted([["_Test Item", "_Test Warehouse - _TC", -40.0], - ["_Test Item", "_Test Warehouse - _TC", 40.0]])) + self.assertFalse(webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type='Stock Entry' and voucher_no=%s""", mi.doc.name)) - self.check_gl_entries("Stock Entry", mi.doc.name, - sorted([ - [stock_in_hand_account, 0.0, 4000.0], - ["Stock Adjustment - _TC", 4000.0, 0.0], - [stock_in_hand_account, 4000.0, 0.0], - ["Stock Adjustment - _TC", 0.0, 4000.0], - ]) - ) + self.assertEquals(webnotes.conn.get_value("Bin", {"warehouse": mi.doclist[1].s_warehouse, + "item_code": mi.doclist[1].item_code}, "actual_qty"), 50) + + self.assertEquals(webnotes.conn.get_value("Bin", {"warehouse": mi.doclist[1].s_warehouse, + "item_code": mi.doclist[1].item_code}, "stock_value"), 5000) def test_material_transfer_gl_entry(self): - self._clear_stock() - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - - mr = webnotes.bean(copy=test_records[0]) - mr.insert() - mr.submit() + self._clear_stock_account_balance() + set_perpetual_inventory() + self._insert_material_receipt() + mtn = webnotes.bean(copy=test_records[2]) mtn.insert() mtn.submit() @@ -168,57 +160,104 @@ class TestStockEntry(unittest.TestCase): self.check_stock_ledger_entries("Stock Entry", mtn.doc.name, [["_Test Item", "_Test Warehouse - _TC", -45.0], ["_Test Item", "_Test Warehouse 1 - _TC", 45.0]]) - # no gl entry - gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` - where voucher_type = 'Stock Entry' and voucher_no=%s""", mtn.doc.name) - self.assertFalse(gl_entries) + stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse", + "master_name": mtn.doclist[1].s_warehouse}) + + fixed_asset_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse", + "master_name": mtn.doclist[1].t_warehouse}) + + + self.check_gl_entries("Stock Entry", mtn.doc.name, + sorted([ + [stock_in_hand_account, 0.0, 4500.0], + [fixed_asset_account, 4500.0, 0.0], + ]) + ) + mtn.cancel() - self.check_stock_ledger_entries("Stock Entry", mtn.doc.name, - sorted([["_Test Item", "_Test Warehouse - _TC", 45.0], - ["_Test Item", "_Test Warehouse 1 - _TC", -45.0], - ["_Test Item", "_Test Warehouse - _TC", -45.0], - ["_Test Item", "_Test Warehouse 1 - _TC", 45.0]])) + self.assertFalse(webnotes.conn.sql("""select * from `tabStock Ledger Entry` + where voucher_type='Stock Entry' and voucher_no=%s""", mtn.doc.name)) + + self.assertFalse(webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type='Stock Entry' and voucher_no=%s""", mtn.doc.name)) + + + def test_repack_no_change_in_valuation(self): + self._clear_stock_account_balance() + set_perpetual_inventory() - # no gl entry - gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` - where voucher_type = 'Stock Entry' and voucher_no=%s""", mtn.doc.name) + self._insert_material_receipt() + + repack = webnotes.bean(copy=test_records[3]) + repack.insert() + repack.submit() + + self.check_stock_ledger_entries("Stock Entry", repack.doc.name, + [["_Test Item", "_Test Warehouse - _TC", -50.0], + ["_Test Item Home Desktop 100", "_Test Warehouse - _TC", 1]]) + + gl_entries = webnotes.conn.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type='Stock Entry' and voucher_no=%s + order by account desc""", repack.doc.name, as_dict=1) self.assertFalse(gl_entries) + + set_perpetual_inventory(0) + + def test_repack_with_change_in_valuation(self): + self._clear_stock_account_balance() + set_perpetual_inventory() + + self._insert_material_receipt() + + repack = webnotes.bean(copy=test_records[3]) + repack.doclist[2].incoming_rate = 6000 + repack.insert() + repack.submit() + + stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse", + "master_name": repack.doclist[2].t_warehouse}) + + self.check_gl_entries("Stock Entry", repack.doc.name, + sorted([ + [stock_in_hand_account, 1000.0, 0.0], + ["Stock Adjustment - _TC", 0.0, 1000.0], + ]) + ) + set_perpetual_inventory(0) def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle): - # check stock ledger entries - sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` where voucher_type = %s - and voucher_no = %s order by item_code, warehouse, actual_qty""", - (voucher_type, voucher_no), as_dict=1) - self.assertTrue(sle) + expected_sle.sort(key=lambda x: x[0]) + # check stock ledger entries + sle = webnotes.conn.sql("""select item_code, warehouse, actual_qty + from `tabStock Ledger Entry` where voucher_type = %s + and voucher_no = %s order by item_code, warehouse, actual_qty""", + (voucher_type, voucher_no), as_list=1) + self.assertTrue(sle) + sle.sort(key=lambda x: x[0]) + for i, sle in enumerate(sle): - self.assertEquals(expected_sle[i][0], sle.item_code) - self.assertEquals(expected_sle[i][1], sle.warehouse) - self.assertEquals(expected_sle[i][2], sle.actual_qty) + self.assertEquals(expected_sle[i][0], sle[0]) + self.assertEquals(expected_sle[i][1], sle[1]) + self.assertEquals(expected_sle[i][2], sle[2]) def check_gl_entries(self, voucher_type, voucher_no, expected_gl_entries): - # check gl entries + expected_gl_entries.sort(key=lambda x: x[0]) gl_entries = webnotes.conn.sql("""select account, debit, credit from `tabGL Entry` where voucher_type=%s and voucher_no=%s - order by account asc, debit asc""", (voucher_type, voucher_no), as_dict=1) + order by account asc, debit asc""", (voucher_type, voucher_no), as_list=1) self.assertTrue(gl_entries) - for i, gle in enumerate(gl_entries): - self.assertEquals(expected_gl_entries[i][0], gle.account) - self.assertEquals(expected_gl_entries[i][1], gle.debit) - self.assertEquals(expected_gl_entries[i][2], gle.credit) - - def _clear_stock(self): - webnotes.conn.sql("delete from `tabStock Ledger Entry`") - webnotes.conn.sql("""delete from `tabBin`""") - webnotes.conn.sql("""delete from `tabSerial No`""") + gl_entries.sort(key=lambda x: x[0]) + + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_gl_entries[i][0], gle[0]) + self.assertEquals(expected_gl_entries[i][1], gle[1]) + self.assertEquals(expected_gl_entries[i][2], gle[2]) - self.old_default_company = webnotes.conn.get_default("company") - webnotes.conn.set_default("company", "_Test Company") - def _insert_material_receipt(self): - self._clear_stock() + self._clear_stock_account_balance() se1 = webnotes.bean(copy=test_records[0]) se1.insert() se1.submit() @@ -305,9 +344,11 @@ class TestStockEntry(unittest.TestCase): return se def test_sales_invoice_return_of_non_packing_item(self): + self._clear_stock_account_balance() self._test_sales_invoice_return("_Test Item", 5, 2) def test_sales_invoice_return_of_packing_item(self): + self._clear_stock_account_balance() self._test_sales_invoice_return("_Test Sales BOM Item", 25, 20) def _test_delivery_note_return(self, item_code, delivered_qty, returned_qty): @@ -319,7 +360,6 @@ class TestStockEntry(unittest.TestCase): from stock.doctype.delivery_note.delivery_note import make_sales_invoice actual_qty_0 = self._get_actual_qty() - # make a delivery note based on this invoice dn = webnotes.bean(copy=delivery_note_test_records[0]) dn.doclist[1].item_code = item_code @@ -358,9 +398,11 @@ class TestStockEntry(unittest.TestCase): return se def test_delivery_note_return_of_non_packing_item(self): + self._clear_stock_account_balance() self._test_delivery_note_return("_Test Item", 5, 2) def test_delivery_note_return_of_packing_item(self): + self._clear_stock_account_balance() self._test_delivery_note_return("_Test Sales BOM Item", 25, 20) def _test_sales_return_jv(self, se): @@ -375,14 +417,17 @@ class TestStockEntry(unittest.TestCase): self.assertTrue(jv_list[1].get("against_invoice")) def test_make_return_jv_for_sales_invoice_non_packing_item(self): + self._clear_stock_account_balance() se = self._test_sales_invoice_return("_Test Item", 5, 2) self._test_sales_return_jv(se) def test_make_return_jv_for_sales_invoice_packing_item(self): + self._clear_stock_account_balance() se = self._test_sales_invoice_return("_Test Sales BOM Item", 25, 20) self._test_sales_return_jv(se) def test_make_return_jv_for_delivery_note_non_packing_item(self): + self._clear_stock_account_balance() se = self._test_delivery_note_return("_Test Item", 5, 2) self._test_sales_return_jv(se) @@ -390,6 +435,7 @@ class TestStockEntry(unittest.TestCase): self._test_sales_return_jv(se) def test_make_return_jv_for_delivery_note_packing_item(self): + self._clear_stock_account_balance() se = self._test_delivery_note_return("_Test Sales BOM Item", 25, 20) self._test_sales_return_jv(se) @@ -450,7 +496,7 @@ class TestStockEntry(unittest.TestCase): return se def test_purchase_receipt_return(self): - self._clear_stock() + self._clear_stock_account_balance() actual_qty_0 = self._get_actual_qty() @@ -466,7 +512,7 @@ class TestStockEntry(unittest.TestCase): actual_qty_1 = self._get_actual_qty() - self.assertEquals(actual_qty_0 + 10, actual_qty_1) + self.assertEquals(actual_qty_0 + 5, actual_qty_1) pi_doclist = make_purchase_invoice(pr.doc.name) @@ -506,6 +552,7 @@ class TestStockEntry(unittest.TestCase): def test_over_stock_return(self): from stock.doctype.stock_entry.stock_entry import StockOverReturnError + self._clear_stock_account_balance() # out of 10, 5 gets returned prev_se, pr_docname = self.test_purchase_receipt_return() @@ -533,6 +580,7 @@ class TestStockEntry(unittest.TestCase): self.assertTrue(jv_list[1].get("against_voucher")) def test_make_return_jv_for_purchase_receipt(self): + self._clear_stock_account_balance() se, pr_name = self.test_purchase_receipt_return() self._test_purchase_return_jv(se) @@ -540,7 +588,7 @@ class TestStockEntry(unittest.TestCase): self._test_purchase_return_jv(se) def _test_purchase_return_return_against_purchase_order(self): - self._clear_stock() + self._clear_stock_account_balance() actual_qty_0 = self._get_actual_qty() @@ -604,6 +652,14 @@ class TestStockEntry(unittest.TestCase): return se, pr.doc.name + def _clear_stock_account_balance(self): + webnotes.conn.sql("delete from `tabStock Ledger Entry`") + webnotes.conn.sql("""delete from `tabBin`""") + webnotes.conn.sql("""delete from `tabGL Entry`""") + + self.old_default_company = webnotes.conn.get_default("company") + webnotes.conn.set_default("company", "_Test Company") + def test_serial_no_not_reqd(self): se = webnotes.bean(copy=test_records[0]) se.doclist[1].serial_no = "ABCD" @@ -637,6 +693,7 @@ class TestStockEntry(unittest.TestCase): self.assertRaises(SerialNoQtyError, se.submit) def test_serial_no_transfer_in(self): + self._clear_stock_account_balance() se = webnotes.bean(copy=test_records[0]) se.doclist[1].item_code = "_Test Serialized Item" se.doclist[1].qty = 2 @@ -652,6 +709,7 @@ class TestStockEntry(unittest.TestCase): self.assertFalse(webnotes.conn.get_value("Serial No", "ABCD", "warehouse")) def test_serial_no_not_exists(self): + self._clear_stock_account_balance() se = webnotes.bean(copy=test_records[0]) se.doc.purpose = "Material Issue" se.doclist[1].item_code = "_Test Serialized Item" @@ -664,6 +722,7 @@ class TestStockEntry(unittest.TestCase): self.assertRaises(SerialNoNotExistsError, se.submit) def test_serial_by_series(self): + self._clear_stock_account_balance() se = make_serialized_item() serial_nos = get_serial_nos(se.doclist[1].serial_no) @@ -674,6 +733,7 @@ class TestStockEntry(unittest.TestCase): return se def test_serial_item_error(self): + self._clear_stock_account_balance() self.test_serial_by_series() se = webnotes.bean(copy=test_records[0]) @@ -688,6 +748,7 @@ class TestStockEntry(unittest.TestCase): self.assertRaises(SerialNoItemError, se.submit) def test_serial_move(self): + self._clear_stock_account_balance() se = make_serialized_item() serial_no = get_serial_nos(se.doclist[1].serial_no)[0] @@ -707,6 +768,7 @@ class TestStockEntry(unittest.TestCase): self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC") def test_serial_warehouse_error(self): + self._clear_stock_account_balance() make_serialized_item() se = webnotes.bean(copy=test_records[0]) @@ -721,6 +783,7 @@ class TestStockEntry(unittest.TestCase): self.assertRaises(SerialNoWarehouseError, se.submit) def test_serial_cancel(self): + self._clear_stock_account_balance() se = self.test_serial_by_series() se.cancel() @@ -745,7 +808,6 @@ test_records = [ "posting_time": "17:14:24", "purpose": "Material Receipt", "fiscal_year": "_Test Fiscal Year 2013", - "expense_adjustment_account": "Stock Adjustment - _TC" }, { "conversion_factor": 1.0, @@ -758,6 +820,8 @@ test_records = [ "transfer_qty": 50.0, "uom": "_Test UOM", "t_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC" }, ], [ @@ -768,7 +832,6 @@ test_records = [ "posting_time": "17:15", "purpose": "Material Issue", "fiscal_year": "_Test Fiscal Year 2013", - "expense_adjustment_account": "Stock Adjustment - _TC" }, { "conversion_factor": 1.0, @@ -781,6 +844,8 @@ test_records = [ "transfer_qty": 40.0, "uom": "_Test UOM", "s_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC" }, ], [ @@ -791,7 +856,6 @@ test_records = [ "posting_time": "17:14:24", "purpose": "Material Transfer", "fiscal_year": "_Test Fiscal Year 2013", - "expense_adjustment_account": "Stock Adjustment - _TC" }, { "conversion_factor": 1.0, @@ -805,6 +869,46 @@ test_records = [ "uom": "_Test UOM", "s_warehouse": "_Test Warehouse - _TC", "t_warehouse": "_Test Warehouse 1 - _TC", + "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC" } - ] + ], + [ + { + "company": "_Test Company", + "doctype": "Stock Entry", + "posting_date": "2013-01-25", + "posting_time": "17:14:24", + "purpose": "Manufacture/Repack", + "fiscal_year": "_Test Fiscal Year 2013", + }, + { + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item", + "parentfield": "mtn_details", + "incoming_rate": 100, + "qty": 50.0, + "stock_uom": "_Test UOM", + "transfer_qty": 50.0, + "uom": "_Test UOM", + "s_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC" + }, + { + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item Home Desktop 100", + "parentfield": "mtn_details", + "incoming_rate": 5000, + "qty": 1, + "stock_uom": "_Test UOM", + "transfer_qty": 1, + "uom": "_Test UOM", + "t_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC" + }, + ], ] \ No newline at end of file diff --git a/stock/doctype/stock_entry_detail/stock_entry_detail.txt b/stock/doctype/stock_entry_detail/stock_entry_detail.txt index f47e192c696..b400cdd2485 100644 --- a/stock/doctype/stock_entry_detail/stock_entry_detail.txt +++ b/stock/doctype/stock_entry_detail/stock_entry_detail.txt @@ -2,7 +2,7 @@ { "creation": "2013-03-29 18:22:12", "docstatus": 0, - "modified": "2013-07-10 14:54:23", + "modified": "2013-08-28 19:25:38", "modified_by": "Administrator", "owner": "Administrator" }, @@ -144,6 +144,27 @@ "print_hide": 1, "read_only": 0 }, + { + "depends_on": "eval:sys_defaults.auto_accounting_for_stock", + "doctype": "DocField", + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Difference Account", + "options": "Account", + "print_hide": 1 + }, + { + "depends_on": "eval:sys_defaults.auto_accounting_for_stock", + "doctype": "DocField", + "fieldname": "cost_center", + "fieldtype": "Link", + "hidden": 0, + "label": "Cost Center", + "options": "Cost Center", + "print_hide": 1, + "read_only": 0, + "reqd": 0 + }, { "doctype": "DocField", "fieldname": "actual_qty", diff --git a/stock/doctype/stock_ledger/README.md b/stock/doctype/stock_ledger/README.md deleted file mode 100644 index 8ccaf594fa7..00000000000 --- a/stock/doctype/stock_ledger/README.md +++ /dev/null @@ -1 +0,0 @@ -Control (to be deprecated) for updating stock entries. \ No newline at end of file diff --git a/stock/doctype/stock_ledger/__init__.py b/stock/doctype/stock_ledger/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/stock/doctype/stock_ledger/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/stock/doctype/stock_ledger/stock_ledger.txt b/stock/doctype/stock_ledger/stock_ledger.txt deleted file mode 100644 index afdaa4d9b48..00000000000 --- a/stock/doctype/stock_ledger/stock_ledger.txt +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "creation": "2013-01-10 16:34:30", - "docstatus": 0, - "modified": "2013-07-10 14:54:23", - "modified_by": "Administrator", - "owner": "Administrator" - }, - { - "doctype": "DocType", - "hide_toolbar": 1, - "in_create": 1, - "issingle": 1, - "module": "Stock", - "name": "__common__", - "read_only": 1 - }, - { - "doctype": "DocType", - "name": "Stock Ledger" - } -] \ No newline at end of file diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index f8616c39a63..1c3d3e132dd 100644 --- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -34,25 +34,35 @@ class DocType(DocListController): self.validate_item() validate_warehouse_user(self.doc.warehouse) self.validate_warehouse_company() - self.actual_amt_check() - self.check_stock_frozen_date() self.scrub_posting_time() from accounts.utils import validate_fiscal_year validate_fiscal_year(self.doc.posting_date, self.doc.fiscal_year, self.meta.get_label("posting_date")) + def on_submit(self): + self.check_stock_frozen_date() + self.actual_amt_check() + self.validate_serial_no() + #check for item quantity available in stock def actual_amt_check(self): if self.doc.batch_no: - batch_bal = flt(webnotes.conn.sql("select sum(actual_qty) from `tabStock Ledger Entry` where warehouse = '%s' and item_code = '%s' and batch_no = '%s'"%(self.doc.warehouse,self.doc.item_code,self.doc.batch_no))[0][0]) - self.doc.fields.update({'batch_bal': batch_bal}) + batch_bal_after_transaction = flt(webnotes.conn.sql("""select sum(actual_qty) + from `tabStock Ledger Entry` + where warehouse=%s and item_code=%s and batch_no=%s""", + (self.doc.warehouse, self.doc.item_code, self.doc.batch_no))[0][0]) + + if batch_bal_after_transaction < 0: + self.doc.fields.update({ + 'batch_bal': batch_bal_after_transaction - self.doc.actual_qty + }) + + webnotes.throw("""Not enough quantity (requested: %(actual_qty)s, \ + current: %(batch_bal)s in Batch %(batch_no)s for Item \ + %(item_code)s at Warehouse %(warehouse)s \ + as on %(posting_date)s %(posting_time)s""" % self.doc.fields) - if (batch_bal + self.doc.actual_qty) < 0: - msgprint("""Not enough quantity (requested: %(actual_qty)s, current: %(batch_bal)s in Batch - %(batch_no)s for Item %(item_code)s at Warehouse %(warehouse)s - as on %(posting_date)s %(posting_time)s""" % self.doc.fields, raise_exception = 1) - - self.doc.fields.pop('batch_bal') + sself.doc.fields.pop('batch_bal') def validate_warehouse_company(self): warehouse_company = webnotes.conn.get_value("Warehouse", self.doc.warehouse, "company") @@ -71,10 +81,7 @@ class DocType(DocListController): msgprint("Warehouse: '%s' does not exist in the system. Please check." % self.doc.fields.get(k), raise_exception = 1) def validate_item(self): - item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus, - is_stock_item, has_serial_no, serial_no_series, stock_uom - from tabItem where name=%s""", - self.doc.item_code, as_dict=True)[0] + item_det = self.get_item_details() if item_det.is_stock_item != 'Yes': webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code) @@ -91,10 +98,16 @@ class DocType(DocListController): if not self.doc.stock_uom: self.doc.stock_uom = item_det.stock_uom - - self.validate_serial_no(item_det) + + def get_item_details(self): + return webnotes.conn.sql("""select name, has_batch_no, docstatus, + is_stock_item, has_serial_no, serial_no_series + from tabItem where name=%s""", + self.doc.item_code, as_dict=True)[0] - def validate_serial_no(self, item_det): + def validate_serial_no(self): + item_det = self.get_item_details() + if item_det.has_serial_no=="No": if self.doc.serial_no: webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item), diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt b/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt index 5b65e973d4e..a5ea161a736 100644 --- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt +++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.txt @@ -2,7 +2,7 @@ { "creation": "2013-01-29 19:25:42", "docstatus": 0, - "modified": "2013-07-25 16:39:10", + "modified": "2013-08-23 12:23:18", "modified_by": "Administrator", "owner": "Administrator" }, @@ -224,6 +224,14 @@ "options": "Company:company:default_currency", "read_only": 1 }, + { + "doctype": "DocField", + "fieldname": "stock_value_difference", + "fieldtype": "Currency", + "label": "Stock Value Difference", + "options": "Company:company:default_currency", + "read_only": 1 + }, { "doctype": "DocField", "fieldname": "stock_queue", @@ -276,15 +284,10 @@ "doctype": "DocField", "fieldname": "is_cancelled", "fieldtype": "Select", - "in_filter": 1, + "hidden": 1, "label": "Is Cancelled", - "oldfieldname": "is_cancelled", - "oldfieldtype": "Select", - "options": "\nYes\nNo", - "print_width": "100px", - "read_only": 1, - "search_index": 0, - "width": "100px" + "options": "\nNo\nYes", + "report_hide": 1 }, { "amend": 0, diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.js b/stock/doctype/stock_reconciliation/stock_reconciliation.js index fa2600e5a6a..7373648972b 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -12,7 +12,7 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ set_default_expense_account: function() { var me = this; - if (sys_defaults.auto_inventory_accounting && !this.frm.doc.expense_account) { + if (sys_defaults.auto_accounting_for_stock && !this.frm.doc.expense_account) { return this.frm.call({ method: "accounts.utils.get_company_default", args: { @@ -28,8 +28,9 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ setup: function() { var me = this; - if (sys_defaults.auto_inventory_accounting) { + if (sys_defaults.auto_accounting_for_stock) { this.frm.add_fetch("company", "stock_adjustment_account", "expense_account"); + this.frm.add_fetch("company", "cost_center", "cost_center"); this.frm.fields_dict["expense_account"].get_query = function() { return { diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.py b/stock/doctype/stock_reconciliation/stock_reconciliation.py index da286d3c9dd..465edc490ad 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -9,6 +9,7 @@ from webnotes import msgprint, _ from webnotes.utils import cstr, flt, cint from stock.stock_ledger import update_entries_after from controllers.stock_controller import StockController +from stock.utils import update_bin class DocType(StockController): def setup(self): @@ -17,14 +18,14 @@ class DocType(StockController): def validate(self): self.validate_data() + self.validate_expense_account() def on_submit(self): self.insert_stock_ledger_entries() - self.set_stock_value_difference() self.make_gl_entries() def on_cancel(self): - self.delete_stock_ledger_entries() + self.delete_and_repost_sle() self.make_cancel_gl_entries() def validate_data(self): @@ -56,7 +57,6 @@ class DocType(StockController): if len(rows) > 100: msgprint(_("""Sorry! We can only allow upto 100 rows for Stock Reconciliation."""), raise_exception=True) - for row_num, row in enumerate(rows): # find duplicates if [row[0], row[1]] in item_warehouse_combinations: @@ -88,7 +88,7 @@ class DocType(StockController): msgprint(msg) raise webnotes.ValidationError - + def validate_item(self, item_code, row_num): from stock.utils import validate_end_of_life, validate_is_stock_item, \ validate_cancelled_item @@ -244,33 +244,26 @@ class DocType(StockController): "voucher_no": self.doc.name, "company": self.doc.company, "stock_uom": webnotes.conn.get_value("Item", row.item_code, "stock_uom"), - "is_cancelled": "No", "voucher_detail_no": row.voucher_detail_no, "fiscal_year": self.doc.fiscal_year, }) args.update(opts) - # create stock ledger entry - sle_wrapper = webnotes.bean([args]) - sle_wrapper.ignore_permissions = 1 - sle_wrapper.insert() - - # update bin - webnotes.get_obj('Warehouse', row.warehouse).update_bin(args) - + self.make_sl_entries([args]) + # append to entries self.entries.append(args) - def delete_stock_ledger_entries(self): - """ Delete Stock Ledger Entries related to this Stock Reconciliation + def delete_and_repost_sle(self): + """ Delete Stock Ledger Entries related to this voucher and repost future Stock Ledger Entries""" - - existing_entries = webnotes.conn.sql("""select item_code, warehouse - from `tabStock Ledger Entry` where voucher_type='Stock Reconciliation' - and voucher_no=%s""", self.doc.name, as_dict=1) + + existing_entries = webnotes.conn.sql("""select distinct item_code, warehouse + from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""", + (self.doc.doctype, self.doc.name), as_dict=1) # delete entries webnotes.conn.sql("""delete from `tabStock Ledger Entry` - where voucher_type='Stock Reconciliation' and voucher_no=%s""", self.doc.name) + where voucher_type=%s and voucher_no=%s""", (self.doc.doctype, self.doc.name)) # repost future entries for selected item_code, warehouse for entries in existing_entries: @@ -281,36 +274,27 @@ class DocType(StockController): "posting_time": self.doc.posting_time }) - def set_stock_value_difference(self): - """stock_value_difference is the increment in the stock value""" - from stock.utils import get_buying_amount + def get_gl_entries_for_stock(self, warehouse_account=None): + if not self.doc.cost_center: + msgprint(_("Please enter Cost Center"), raise_exception=1) + + return super(DocType, self).get_gl_entries_for_stock(warehouse_account, + self.doc.expense_account, self.doc.cost_center) - item_list = [d.item_code for d in self.entries] - warehouse_list = [d.warehouse for d in self.entries] - if not (item_list and warehouse_list): - webnotes.throw(_("Invalid Item or Warehouse Data")) - - stock_ledger_entries = self.get_stock_ledger_entries(item_list, warehouse_list) - - self.doc.stock_value_difference = 0.0 - for d in self.entries: - self.doc.stock_value_difference -= get_buying_amount(self.doc.doctype, self.doc.name, - d.voucher_detail_no, stock_ledger_entries.get((d.item_code, d.warehouse), [])) - webnotes.conn.set(self.doc, "stock_value_difference", self.doc.stock_value_difference) - - def make_gl_entries(self): - if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")): + + def validate_expense_account(self): + if not cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")): return - + if not self.doc.expense_account: msgprint(_("Please enter Expense Account"), raise_exception=1) - - from accounts.general_ledger import make_gl_entries - - gl_entries = self.get_gl_entries_for_stock(self.doc.expense_account, - self.doc.stock_value_difference) - if gl_entries: - make_gl_entries(gl_entries, cancel=self.doc.docstatus == 2) + elif not webnotes.conn.sql("""select * from `tabStock Ledger Entry`"""): + if webnotes.conn.get_value("Account", self.doc.expense_account, + "is_pl_account") == "Yes": + msgprint(_("""Expense Account can not be a PL Account, as this stock \ + reconciliation is an opening entry. \ + Please select 'Temporary Account (Liabilities)' or relevant account"""), + raise_exception=1) @webnotes.whitelist() def upload(): diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.txt b/stock/doctype/stock_reconciliation/stock_reconciliation.txt index 7ddcbf7c1c9..c9959d36e35 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.txt +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.txt @@ -2,7 +2,7 @@ { "creation": "2013-03-28 10:35:31", "docstatus": 0, - "modified": "2013-07-22 15:22:44", + "modified": "2013-09-24 15:35:12", "modified_by": "Administrator", "owner": "Administrator" }, @@ -102,13 +102,20 @@ "reqd": 1 }, { - "depends_on": "eval:sys_defaults.auto_inventory_accounting", + "depends_on": "eval:sys_defaults.auto_accounting_for_stock", "doctype": "DocField", "fieldname": "expense_account", "fieldtype": "Link", - "label": "Expense Account", + "label": "Difference Account", "options": "Account" }, + { + "doctype": "DocField", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, { "doctype": "DocField", "fieldname": "col1", @@ -148,15 +155,6 @@ "print_hide": 1, "read_only": 1 }, - { - "doctype": "DocField", - "fieldname": "stock_value_difference", - "fieldtype": "Currency", - "hidden": 1, - "in_list_view": 1, - "label": "Stock Value Difference", - "print_hide": 1 - }, { "doctype": "DocPerm" } diff --git a/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 909dfc759a7..0434f82d096 100644 --- a/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -8,11 +8,12 @@ from __future__ import unicode_literals import webnotes, unittest from webnotes.utils import flt import json -from accounts.utils import get_fiscal_year +from accounts.utils import get_fiscal_year, get_stock_and_account_difference, get_balance_on + class TestStockReconciliation(unittest.TestCase): def test_reco_for_fifo(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 0) # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] input_data = [ @@ -56,7 +57,7 @@ class TestStockReconciliation(unittest.TestCase): def test_reco_for_moving_average(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 0) # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] input_data = [ @@ -102,42 +103,40 @@ class TestStockReconciliation(unittest.TestCase): self.assertFalse(gl_entries) def test_reco_fifo_gl_entries(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 1) - # [[qty, valuation_rate, posting_date, - # posting_time, stock_in_hand_debit]] + # [[qty, valuation_rate, posting_date, posting_time, stock_in_hand_debit]] input_data = [ - [50, 1000, "2012-12-26", "12:00", 38000], - [5, 1000, "2012-12-26", "12:00", -7000], - [15, 1000, "2012-12-26", "12:00", 3000], - [25, 900, "2012-12-26", "12:00", 10500], - [20, 500, "2012-12-26", "12:00", -2000], - ["", 1000, "2012-12-26", "12:05", 3000], - [20, "", "2012-12-26", "12:05", 4000], - [10, 2000, "2012-12-26", "12:10", 8000], - [0, "", "2012-12-26", "12:10", -12000], - [50, 1000, "2013-01-01", "12:00", 50000], - [5, 1000, "2013-01-01", "12:00", 5000], - [1, 1000, "2012-12-01", "00:00", 1000], - + [50, 1000, "2012-12-26", "12:00"], + [5, 1000, "2012-12-26", "12:00"], + [15, 1000, "2012-12-26", "12:00"], + [25, 900, "2012-12-26", "12:00"], + [20, 500, "2012-12-26", "12:00"], + ["", 1000, "2012-12-26", "12:05"], + [20, "", "2012-12-26", "12:05"], + [10, 2000, "2012-12-26", "12:10"], + [0, "", "2012-12-26", "12:10"], + [50, 1000, "2013-01-01", "12:00"], + [5, 1000, "2013-01-01", "12:00"], + [1, 1000, "2012-12-01", "00:00"], ] for d in input_data: self.cleanup_data() self.insert_existing_sle("FIFO") + self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3]) - # check gl_entries - self.check_gl_entries(stock_reco.doc.name, d[4]) - # cancel + self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) + stock_reco.cancel() - self.check_gl_entries(stock_reco.doc.name, -d[4], True) + self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 0) def test_reco_moving_average_gl_entries(self): - webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 1) # [[qty, valuation_rate, posting_date, # posting_time, stock_in_hand_debit]] @@ -161,20 +160,19 @@ class TestStockReconciliation(unittest.TestCase): self.cleanup_data() self.insert_existing_sle("Moving Average") stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3]) - - # check gl_entries - self.check_gl_entries(stock_reco.doc.name, d[4]) + self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"])) # cancel stock_reco.cancel() - self.check_gl_entries(stock_reco.doc.name, -d[4], True) + self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"])) - webnotes.defaults.set_global_default("auto_inventory_accounting", 0) + webnotes.defaults.set_global_default("auto_accounting_for_stock", 0) def cleanup_data(self): webnotes.conn.sql("delete from `tabStock Ledger Entry`") webnotes.conn.sql("delete from tabBin") + webnotes.conn.sql("delete from `tabGL Entry`") def submit_stock_reconciliation(self, qty, rate, posting_date, posting_time): stock_reco = webnotes.bean([{ @@ -184,6 +182,7 @@ class TestStockReconciliation(unittest.TestCase): "fiscal_year": get_fiscal_year(posting_date)[0], "company": "_Test Company", "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC", "reconciliation_json": json.dumps([ ["Item Code", "Warehouse", "Quantity", "Valuation Rate"], ["_Test Item", "_Test Warehouse - _TC", qty, rate] @@ -193,82 +192,82 @@ class TestStockReconciliation(unittest.TestCase): stock_reco.submit() return stock_reco - def check_gl_entries(self, voucher_no, stock_value_diff, cancel=None): - stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company", - "stock_in_hand_account") - debit_amount = stock_value_diff > 0 and stock_value_diff or 0.0 - credit_amount = stock_value_diff < 0 and abs(stock_value_diff) or 0.0 - - expected_gl_entries = sorted([ - [stock_in_hand_account, debit_amount, credit_amount], - ["Stock Adjustment - _TC", credit_amount, debit_amount] - ]) - if cancel: - expected_gl_entries = sorted([ - [stock_in_hand_account, debit_amount, credit_amount], - ["Stock Adjustment - _TC", credit_amount, debit_amount], - [stock_in_hand_account, credit_amount, debit_amount], - ["Stock Adjustment - _TC", debit_amount, credit_amount] - ]) - - gl_entries = webnotes.conn.sql("""select account, debit, credit - from `tabGL Entry` where voucher_type='Stock Reconciliation' and voucher_no=%s - order by account asc, debit asc""", voucher_no, as_dict=1) - self.assertTrue(gl_entries) - - for i, gle in enumerate(gl_entries): - self.assertEquals(expected_gl_entries[i][0], gle.account) - self.assertEquals(expected_gl_entries[i][1], gle.debit) - self.assertEquals(expected_gl_entries[i][2], gle.credit) - def insert_existing_sle(self, valuation_method): webnotes.conn.set_value("Item", "_Test Item", "valuation_method", valuation_method) webnotes.conn.set_default("allow_negative_stock", 1) - existing_ledgers = [ + stock_entry = [ { - "doctype": "Stock Ledger Entry", "__islocal": 1, - "voucher_type": "Stock Entry", "voucher_no": "TEST", - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC", - "posting_date": "2012-12-12", "posting_time": "01:00", - "actual_qty": 20, "incoming_rate": 1000, "company": "_Test Company", - "fiscal_year": "_Test Fiscal Year 2012", - }, + "company": "_Test Company", + "doctype": "Stock Entry", + "posting_date": "2012-12-12", + "posting_time": "01:00", + "purpose": "Material Receipt", + "fiscal_year": "_Test Fiscal Year 2012", + }, { - "doctype": "Stock Ledger Entry", "__islocal": 1, - "voucher_type": "Stock Entry", "voucher_no": "TEST", - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC", - "posting_date": "2012-12-15", "posting_time": "02:00", - "actual_qty": 10, "incoming_rate": 700, "company": "_Test Company", - "fiscal_year": "_Test Fiscal Year 2012", - }, - { - "doctype": "Stock Ledger Entry", "__islocal": 1, - "voucher_type": "Stock Entry", "voucher_no": "TEST", - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC", - "posting_date": "2012-12-25", "posting_time": "03:00", - "actual_qty": -15, "company": "_Test Company", - "fiscal_year": "_Test Fiscal Year 2012", - }, - { - "doctype": "Stock Ledger Entry", "__islocal": 1, - "voucher_type": "Stock Entry", "voucher_no": "TEST", - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC", - "posting_date": "2012-12-31", "posting_time": "08:00", - "actual_qty": -20, "company": "_Test Company", - "fiscal_year": "_Test Fiscal Year 2012", - }, - { - "doctype": "Stock Ledger Entry", "__islocal": 1, - "voucher_type": "Stock Entry", "voucher_no": "TEST", - "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC", - "posting_date": "2013-01-05", "posting_time": "07:00", - "actual_qty": 15, "incoming_rate": 1200, "company": "_Test Company", - "fiscal_year": "_Test Fiscal Year 2013", - }, + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item", + "parentfield": "mtn_details", + "incoming_rate": 1000, + "qty": 20.0, + "stock_uom": "_Test UOM", + "transfer_qty": 20.0, + "uom": "_Test UOM", + "t_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock Adjustment - _TC", + "cost_center": "_Test Cost Center - _TC" + }, ] + + pr = webnotes.bean(copy=stock_entry) + pr.insert() + pr.submit() + + pr1 = webnotes.bean(copy=stock_entry) + pr1.doc.posting_date = "2012-12-15" + pr1.doc.posting_time = "02:00" + pr1.doclist[1].qty = 10 + pr1.doclist[1].transfer_qty = 10 + pr1.doclist[1].incoming_rate = 700 + pr1.insert() + pr1.submit() + + pr2 = webnotes.bean(copy=stock_entry) + pr2.doc.posting_date = "2012-12-25" + pr2.doc.posting_time = "03:00" + pr2.doc.purpose = "Material Issue" + pr2.doclist[1].s_warehouse = "_Test Warehouse - _TC" + pr2.doclist[1].t_warehouse = None + pr2.doclist[1].qty = 15 + pr2.doclist[1].transfer_qty = 15 + pr2.doclist[1].incoming_rate = 0 + pr2.insert() + pr2.submit() + + pr3 = webnotes.bean(copy=stock_entry) + pr3.doc.posting_date = "2012-12-31" + pr3.doc.posting_time = "08:00" + pr3.doc.purpose = "Material Issue" + pr3.doclist[1].s_warehouse = "_Test Warehouse - _TC" + pr3.doclist[1].t_warehouse = None + pr3.doclist[1].qty = 20 + pr3.doclist[1].transfer_qty = 20 + pr3.doclist[1].incoming_rate = 0 + pr3.insert() + pr3.submit() + + + pr4 = webnotes.bean(copy=stock_entry) + pr4.doc.posting_date = "2013-01-05" + pr4.doc.fiscal_year = "_Test Fiscal Year 2013" + pr4.doc.posting_time = "07:00" + pr4.doclist[1].qty = 15 + pr4.doclist[1].transfer_qty = 15 + pr4.doclist[1].incoming_rate = 1200 + pr4.insert() + pr4.submit() - webnotes.get_obj("Stock Ledger").update_stock(existing_ledgers) - test_dependencies = ["Item", "Warehouse"] \ No newline at end of file diff --git a/stock/doctype/warehouse/test_warehouse.py b/stock/doctype/warehouse/test_warehouse.py index ed72562078b..76b18180e21 100644 --- a/stock/doctype/warehouse/test_warehouse.py +++ b/stock/doctype/warehouse/test_warehouse.py @@ -5,16 +5,19 @@ test_records = [ [{ "doctype": "Warehouse", "warehouse_name": "_Test Warehouse", - "company": "_Test Company" + "company": "_Test Company", + "create_account_under": "Stock Assets - _TC" }], [{ "doctype": "Warehouse", "warehouse_name": "_Test Warehouse 1", - "company": "_Test Company" + "company": "_Test Company", + "create_account_under": "Fixed Assets - _TC" }], [{ "doctype": "Warehouse", "warehouse_name": "_Test Warehouse 2", + "create_account_under": "Stock Assets - _TC", "company": "_Test Company 1" }, { "doctype": "Warehouse User", diff --git a/stock/doctype/warehouse/warehouse.js b/stock/doctype/warehouse/warehouse.js index 9373c2ac3ee..2a58a387f88 100644 --- a/stock/doctype/warehouse/warehouse.js +++ b/stock/doctype/warehouse/warehouse.js @@ -15,4 +15,14 @@ cur_frm.cscript.merge = function(doc, cdt, cdn) { if (check) { return $c_obj(make_doclist(cdt, cdn), 'merge_warehouses', '', ''); } -} \ No newline at end of file +} + +cur_frm.set_query("create_account_under", function() { + return { + filters: { + "company": cur_frm.doc.company, + "debit_or_credit": "Debit", + 'group_or_ledger': "Group" + } + } +}) diff --git a/stock/doctype/warehouse/warehouse.py b/stock/doctype/warehouse/warehouse.py index 54d0b15836b..1494428549b 100644 --- a/stock/doctype/warehouse/warehouse.py +++ b/stock/doctype/warehouse/warehouse.py @@ -4,9 +4,9 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import flt, validate_email_add +from webnotes.utils import cint, flt, validate_email_add from webnotes.model.code import get_obj -from webnotes import msgprint +from webnotes import msgprint, _ class DocType: @@ -18,39 +18,56 @@ class DocType: suffix = " - " + webnotes.conn.get_value("Company", self.doc.company, "abbr") if not self.doc.warehouse_name.endswith(suffix): self.doc.name = self.doc.warehouse_name + suffix - - def get_bin(self, item_code, warehouse=None): - warehouse = warehouse or self.doc.name - bin = webnotes.conn.sql("select name from tabBin where item_code = %s and \ - warehouse = %s", (item_code, warehouse)) - bin = bin and bin[0][0] or '' - if not bin: - bin_wrapper = webnotes.bean([{ - "doctype": "Bin", - "item_code": item_code, - "warehouse": warehouse, - }]) - bin_wrapper.ignore_permissions = 1 - bin_wrapper.insert() - - bin_obj = bin_wrapper.make_controller() - else: - bin_obj = get_obj('Bin', bin) - return bin_obj - - def update_bin(self, args): - is_stock_item = webnotes.conn.get_value('Item', args.get("item_code"), 'is_stock_item') - if is_stock_item == 'Yes': - bin = self.get_bin(args.get("item_code")) - bin.update_stock(args) - return bin - else: - msgprint("[Stock Update] Ignored %s since it is not a stock item" - % args.get("item_code")) def validate(self): if self.doc.email_id and not validate_email_add(self.doc.email_id): msgprint("Please enter valid Email Id", raise_exception=1) + + def on_update(self): + self.create_account_head() + + def create_account_head(self): + if cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")): + if not webnotes.conn.get_value("Account", {"account_type": "Warehouse", + "master_name": self.doc.name}) and not webnotes.conn.get_value("Account", + {"account_name": self.doc.warehouse_name}): + if self.doc.__islocal or not webnotes.conn.get_value("Stock Ledger Entry", + {"warehouse": self.doc.name}): + self.validate_parent_account() + ac_bean = webnotes.bean({ + "doctype": "Account", + 'account_name': self.doc.warehouse_name, + 'parent_account': self.doc.create_account_under, + 'group_or_ledger':'Ledger', + 'company':self.doc.company, + "account_type": "Warehouse", + "master_name": self.doc.name, + "freeze_account": "No" + }) + ac_bean.ignore_permissions = True + ac_bean.insert() + + msgprint(_("Account Head") + ": " + ac_bean.doc.name + _(" created")) + + def validate_parent_account(self): + if not self.doc.create_account_under: + parent_account = webnotes.conn.get_value("Account", + {"account_name": "Stock Assets", "company": self.doc.company}) + if parent_account: + self.doc.create_account_under = parent_account + else: + webnotes.throw(_("Please enter account group under which account \ + for warehouse ") + self.doc.name +_(" will be created")) + + def on_rename(self, new, old, merge=False): + webnotes.conn.set_value("Account", {"account_type": "Warehouse", "master_name": old}, + "master_name", new) + + if merge: + from stock.stock_ledger import update_entries_after + for item_code in webnotes.conn.sql("""select item_code from `tabBin` + where warehouse=%s""", new): + update_entries_after({"item_code": item_code, "warehouse": new}) def merge_warehouses(self): webnotes.conn.auto_commit_on_many_writes = 1 @@ -65,6 +82,15 @@ class DocType: link_fields = rename_doc.get_link_fields('Warehouse') rename_doc.update_link_field_values(link_fields, self.doc.name, self.doc.merge_with) + account_link_fields = rename_doc.get_link_fields('Account') + old_warehouse_account = webnotes.conn.get_value("Account", {"master_name": self.doc.name}) + new_warehouse_account = webnotes.conn.get_value("Account", + {"master_name": self.doc.merge_with}) + rename_doc.update_link_field_values(account_link_fields, old_warehouse_account, + new_warehouse_account) + + webnotes.conn.delete_doc("Account", old_warehouse_account) + for item_code in items: self.repost(item_code[0], self.doc.merge_with) @@ -75,9 +101,10 @@ class DocType: def repost(self, item_code, warehouse=None): + from stock.utils import get_bin self.repost_actual_qty(item_code, warehouse) - bin = self.get_bin(item_code, warehouse) + bin = get_bin(item_code, warehouse) self.repost_reserved_qty(bin) self.repost_indented_qty(bin) self.repost_ordered_qty(bin) @@ -172,17 +199,15 @@ class DocType: else: webnotes.conn.sql("delete from `tabBin` where name = %s", d['name']) + warehouse_account = webnotes.conn.get_value("Account", + {"account_type": "Warehosue", "master_name": self.doc.name}) + if warehouse_account: + webnotes.delete_doc("Account", warehouse_account) + # delete cancelled sle - if webnotes.conn.sql("""select name from `tabStock Ledger Entry` - where warehouse = %s and ifnull('is_cancelled', '') = 'No'""", self.doc.name): + if webnotes.conn.sql("""select name from `tabStock Ledger Entry` where warehouse = %s""", self.doc.name): msgprint("""Warehosue can not be deleted as stock ledger entry exists for this warehouse.""", raise_exception=1) else: webnotes.conn.sql("delete from `tabStock Ledger Entry` where warehouse = %s", self.doc.name) - def on_rename(self, newdn, olddn, merge=False): - if merge: - from stock.stock_ledger import update_entries_after - for item_code in webnotes.conn.sql("""select item_code from `tabBin` - where warehouse=%s""", newdn): - update_entries_after({"item_code": item_code, "warehouse": newdn}) diff --git a/stock/doctype/warehouse/warehouse.txt b/stock/doctype/warehouse/warehouse.txt index 631b968d90b..76ddac7930c 100644 --- a/stock/doctype/warehouse/warehouse.txt +++ b/stock/doctype/warehouse/warehouse.txt @@ -2,7 +2,7 @@ { "creation": "2013-03-07 18:50:32", "docstatus": 0, - "modified": "2013-07-23 12:01:16", + "modified": "2013-09-16 10:45:49", "modified_by": "Administrator", "owner": "Administrator" }, @@ -20,8 +20,7 @@ "name": "__common__", "parent": "Warehouse", "parentfield": "fields", - "parenttype": "DocType", - "read_only": 0 + "parenttype": "DocType" }, { "doctype": "DocPerm", @@ -43,7 +42,8 @@ "fieldtype": "Section Break", "label": "Warehouse Detail", "oldfieldtype": "Section Break", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -53,6 +53,7 @@ "oldfieldname": "warehouse_name", "oldfieldtype": "Data", "permlevel": 0, + "read_only": 0, "reqd": 1 }, { @@ -65,14 +66,26 @@ "oldfieldtype": "Link", "options": "Company", "permlevel": 0, + "read_only": 0, "reqd": 1, "search_index": 1 }, + { + "depends_on": "eval:sys_defaults.auto_accounting_for_stock", + "description": "Account for the warehouse (Perpetual Inventory) will be created under this Account.", + "doctype": "DocField", + "fieldname": "create_account_under", + "fieldtype": "Link", + "label": "Create Account Under", + "options": "Account", + "permlevel": 0 + }, { "doctype": "DocField", "fieldname": "column_break_4", "fieldtype": "Section Break", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "description": "If set, data entry is only allowed for specified users. Else, entry is allowed for all users with requisite permissions.", @@ -81,7 +94,8 @@ "fieldtype": "Table", "label": "Warehouse Users", "options": "Warehouse User", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "description": "For Reference Only.", @@ -89,7 +103,8 @@ "fieldname": "warehouse_contact_info", "fieldtype": "Section Break", "label": "Warehouse Contact Info", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -100,7 +115,8 @@ "oldfieldname": "email_id", "oldfieldtype": "Data", "permlevel": 0, - "print_hide": 0 + "print_hide": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -110,7 +126,8 @@ "oldfieldname": "phone_no", "oldfieldtype": "Int", "options": "Phone", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -120,14 +137,16 @@ "oldfieldname": "mobile_no", "oldfieldtype": "Int", "options": "Phone", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", "fieldname": "column_break0", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -136,7 +155,8 @@ "label": "Address Line 1", "oldfieldname": "address_line_1", "oldfieldtype": "Data", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -145,7 +165,8 @@ "label": "Address Line 2", "oldfieldname": "address_line_2", "oldfieldtype": "Data", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -156,6 +177,7 @@ "oldfieldname": "city", "oldfieldtype": "Data", "permlevel": 0, + "read_only": 0, "reqd": 0 }, { @@ -166,7 +188,8 @@ "oldfieldname": "state", "oldfieldtype": "Select", "options": "Suggest", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "doctype": "DocField", @@ -175,7 +198,8 @@ "label": "PIN", "oldfieldname": "pin", "oldfieldtype": "Int", - "permlevel": 0 + "permlevel": 0, + "read_only": 0 }, { "description": "This feature is for merging duplicate warehouses. It will replace all the links of this warehouse by \"Merge Into\" warehouse. After merging you can delete this warehouse, as stock level for this warehouse will be zero.", @@ -183,7 +207,8 @@ "fieldname": "merge_warehouses_section", "fieldtype": "Section Break", "label": "Merge Warehouses", - "permlevel": 2 + "permlevel": 2, + "read_only": 0 }, { "doctype": "DocField", @@ -191,14 +216,16 @@ "fieldtype": "Link", "label": "Merge Into", "options": "Warehouse", - "permlevel": 2 + "permlevel": 2, + "read_only": 0 }, { "doctype": "DocField", "fieldname": "merge", "fieldtype": "Button", "label": "Merge", - "permlevel": 2 + "permlevel": 2, + "read_only": 0 }, { "amend": 0, diff --git a/stock/page/stock_home/stock_home.js b/stock/page/stock_home/stock_home.js index 1cab7547aad..63db6086abd 100644 --- a/stock/page/stock_home/stock_home.js +++ b/stock/page/stock_home/stock_home.js @@ -201,9 +201,9 @@ wn.module_page["Stock"] = [ doctype: "Serial No" }, { - "label":wn._("Item-Wise Price List"), - route: "query-report/Item-Wise Price List", - doctype: "Item" + "label":wn._("Item-wise Price List Rate"), + route: "Report/Price List/Item-Wise Price List", + doctype: "Price List" }, { "label":wn._("Purchase In Transit"), diff --git a/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index e198b8ee478..db0c24057fd 100644 --- a/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -52,7 +52,7 @@ def get_stock_ledger_entries(filters): return webnotes.conn.sql("""select item_code, batch_no, warehouse, posting_date, actual_qty from `tabStock Ledger Entry` - where ifnull(is_cancelled, 'No') = 'No' %s order by item_code, warehouse""" % + where docstatus < 2 %s order by item_code, warehouse""" % conditions, as_dict=1) def get_item_warehouse_batch_map(filters): diff --git a/stock/report/item_wise_price_list/item_wise_price_list.txt b/stock/report/item_wise_price_list/item_wise_price_list.txt deleted file mode 100644 index b3d5717a41f..00000000000 --- a/stock/report/item_wise_price_list/item_wise_price_list.txt +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "creation": "2013-02-22 18:01:55", - "docstatus": 0, - "modified": "2013-09-10 15:50:26", - "modified_by": "Administrator", - "owner": "Administrator" - }, - { - "doctype": "Report", - "is_standard": "Yes", - "name": "__common__", - "query": "select\n item.name as \"ID:Link/Item:120\", \n item.item_name as \"Item Name::120\", \n item_price.parent as \"Price List::80\",\n price_list.currency as \"Currency::40\", \n item_price.ref_rate as \"Rate:Float:80\",\n item.description as \"Description::160\",\n item.item_group as \"Item Group:Link/Item Group:100\",\n item.brand as \"Brand::100\"\nfrom `tabItem` item, `tabItem Price` item_price, `tabPrice List` price_list\nwhere\n item_price.item_code = item.name and\n item_price.parent = price_list.name", - "ref_doctype": "Item", - "report_name": "Item-Wise Price List", - "report_type": "Query Report" - }, - { - "doctype": "Report", - "name": "Item-Wise Price List" - } -] \ No newline at end of file diff --git a/stock/report/stock_ledger/stock_ledger.txt b/stock/report/stock_ledger/stock_ledger.txt index 34df640f918..a40be1de236 100644 --- a/stock/report/stock_ledger/stock_ledger.txt +++ b/stock/report/stock_ledger/stock_ledger.txt @@ -2,14 +2,14 @@ { "creation": "2013-01-14 15:26:21", "docstatus": 0, - "modified": "2013-02-22 15:53:01", + "modified": "2013-08-20 11:53:43", "modified_by": "Administrator", "owner": "Administrator" }, { "doctype": "Report", "is_standard": "Yes", - "json": "{\"filters\":[[\"Stock Ledger Entry\",\"is_cancelled\",\"=\",\"No\"]],\"columns\":[[\"item_code\",\"Stock Ledger Entry\"],[\"warehouse\",\"Stock Ledger Entry\"],[\"posting_date\",\"Stock Ledger Entry\"],[\"posting_time\",\"Stock Ledger Entry\"],[\"actual_qty\",\"Stock Ledger Entry\"],[\"qty_after_transaction\",\"Stock Ledger Entry\"],[\"voucher_type\",\"Stock Ledger Entry\"],[\"voucher_no\",\"Stock Ledger Entry\"]],\"sort_by\":\"Stock Ledger Entry.posting_date\",\"sort_order\":\"desc\",\"sort_by_next\":\"Stock Ledger Entry.posting_time\",\"sort_order_next\":\"desc\"}", + "json": "{\"filters\":[],\"columns\":[[\"item_code\",\"Stock Ledger Entry\"],[\"warehouse\",\"Stock Ledger Entry\"],[\"posting_date\",\"Stock Ledger Entry\"],[\"posting_time\",\"Stock Ledger Entry\"],[\"actual_qty\",\"Stock Ledger Entry\"],[\"qty_after_transaction\",\"Stock Ledger Entry\"],[\"voucher_type\",\"Stock Ledger Entry\"],[\"voucher_no\",\"Stock Ledger Entry\"]],\"sort_by\":\"Stock Ledger Entry.posting_date\",\"sort_order\":\"desc\",\"sort_by_next\":\"Stock Ledger Entry.posting_time\",\"sort_order_next\":\"desc\"}", "name": "__common__", "ref_doctype": "Stock Ledger Entry", "report_name": "Stock Ledger", diff --git a/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py b/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py index 4d42c229f21..a1f7d446f34 100644 --- a/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py +++ b/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py @@ -52,7 +52,7 @@ def get_stock_ledger_entries(filters): return webnotes.conn.sql("""select item_code, warehouse, posting_date, actual_qty, company from `tabStock Ledger Entry` - where ifnull(is_cancelled, 'No') = 'No' %s order by item_code, warehouse""" % + where docstatus < 2 %s order by item_code, warehouse""" % conditions, as_dict=1) def get_item_warehouse_map(filters): diff --git a/stock/stock_ledger.py b/stock/stock_ledger.py index 0e8a341a4cd..fe5b685f444 100644 --- a/stock/stock_ledger.py +++ b/stock/stock_ledger.py @@ -3,7 +3,7 @@ import webnotes from webnotes import msgprint -from webnotes.utils import cint, flt, cstr +from webnotes.utils import cint, flt, cstr, now from stock.utils import get_valuation_method import json @@ -11,8 +11,51 @@ import json class NegativeStockError(webnotes.ValidationError): pass _exceptions = webnotes.local('stockledger_exceptions') - # _exceptions = [] + +def make_sl_entries(sl_entries, is_amended=None): + from stock.utils import update_bin + + cancel = True if sl_entries[0].get("is_cancelled") == "Yes" else False + if cancel: + set_as_cancel(sl_entries[0].get('voucher_no'), sl_entries[0].get('voucher_type')) + + for sle in sl_entries: + sle_id = None + if sle.get('is_cancelled') == 'Yes': + sle['actual_qty'] = -flt(sle['actual_qty']) + + if sle.get("actual_qty"): + sle_id = make_entry(sle) + + args = sle.copy() + args.update({ + "sle_id": sle_id, + "is_amended": is_amended + }) + update_bin(args) + + if cancel: + delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) + +def set_as_cancel(voucher_type, voucher_no): + webnotes.conn.sql("""update `tabStock Ledger Entry` set is_cancelled='Yes', + modified=%s, modified_by=%s + where voucher_no=%s and voucher_type=%s""", + (now(), webnotes.session.user, voucher_type, voucher_no)) + +def make_entry(args): + args.update({"doctype": "Stock Ledger Entry"}) + sle = webnotes.bean([args]) + sle.ignore_permissions = 1 + sle.insert() + sle.submit() + return sle.doc.name + +def delete_cancelled_entry(voucher_type, voucher_no): + webnotes.conn.sql("""delete from `tabStock Ledger Entry` + where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) + def update_entries_after(args, verbose=1): """ update valution rate and qty after transaction @@ -33,13 +76,15 @@ def update_entries_after(args, verbose=1): qty_after_transaction = flt(previous_sle.get("qty_after_transaction")) valuation_rate = flt(previous_sle.get("valuation_rate")) stock_queue = json.loads(previous_sle.get("stock_queue") or "[]") - stock_value = 0.0 - + stock_value = flt(previous_sle.get("stock_value")) + prev_stock_value = flt(previous_sle.get("stock_value")) + entries_to_fix = get_sle_after_datetime(previous_sle or \ {"item_code": args["item_code"], "warehouse": args["warehouse"]}, for_update=True) - + valuation_method = get_valuation_method(args["item_code"]) - + stock_value_difference = 0.0 + for sle in entries_to_fix: if sle.serial_no or not cint(webnotes.conn.get_default("allow_negative_stock")): # validate negative stock for serialized items, fifo valuation @@ -47,7 +92,7 @@ def update_entries_after(args, verbose=1): if not validate_negative_stock(qty_after_transaction, sle): qty_after_transaction += flt(sle.actual_qty) continue - + if sle.serial_no: valuation_rate = get_serialized_values(qty_after_transaction, sle, valuation_rate) elif valuation_method == "Moving Average": @@ -65,13 +110,16 @@ def update_entries_after(args, verbose=1): (qty_after_transaction * valuation_rate) or 0 else: stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue)) - # print sle.posting_date, sle.actual_qty, sle.incoming_rate, stock_queue, stock_value + + stock_value_difference = stock_value - prev_stock_value + prev_stock_value = stock_value + # update current sle webnotes.conn.sql("""update `tabStock Ledger Entry` set qty_after_transaction=%s, valuation_rate=%s, stock_queue=%s, - stock_value=%s where name=%s""", + stock_value=%s, stock_value_difference=%s where name=%s""", (qty_after_transaction, valuation_rate, - json.dumps(stock_queue), stock_value, sle.name)) + json.dumps(stock_queue), stock_value, stock_value_difference, sle.name)) if _exceptions: _raise_exceptions(args, verbose) @@ -126,7 +174,7 @@ def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, fo return webnotes.conn.sql("""select * from `tabStock Ledger Entry` where item_code = %%(item_code)s and warehouse = %%(warehouse)s - and ifnull(is_cancelled, 'No') = 'No' + and ifnull(is_cancelled, 'No')='No' %(conditions)s order by timestamp(posting_date, posting_time) %(order)s, name %(order)s %(limit)s %(for_update)s""" % { diff --git a/stock/utils.py b/stock/utils.py index 52471e4c6fd..8836c6c991f 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -9,6 +9,59 @@ from webnotes.defaults import get_global_default from webnotes.utils.email_lib import sendmail class UserNotAllowedForWarehouse(webnotes.ValidationError): pass + +def get_stock_balance_on(warehouse, posting_date=None): + if not posting_date: posting_date = nowdate() + + stock_ledger_entries = webnotes.conn.sql(""" + SELECT + item_code, stock_value + FROM + `tabStock Ledger Entry` + WHERE + warehouse=%s AND posting_date <= %s + ORDER BY timestamp(posting_date, posting_time) DESC, name DESC + """, (warehouse, posting_date), as_dict=1) + + sle_map = {} + for sle in stock_ledger_entries: + sle_map.setdefault(sle.item_code, flt(sle.stock_value)) + + return sum(sle_map.values()) + +def get_latest_stock_balance(): + bin_map = {} + for d in webnotes.conn.sql("""SELECT item_code, warehouse, stock_value as stock_value + FROM tabBin""", as_dict=1): + bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value)) + + return bin_map + +def get_bin(item_code, warehouse): + bin = webnotes.conn.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}) + if not bin: + bin_wrapper = webnotes.bean([{ + "doctype": "Bin", + "item_code": item_code, + "warehouse": warehouse, + }]) + bin_wrapper.ignore_permissions = 1 + bin_wrapper.insert() + bin_obj = bin_wrapper.make_controller() + else: + from webnotes.model.code import get_obj + bin_obj = get_obj('Bin', bin) + return bin_obj + +def update_bin(args): + is_stock_item = webnotes.conn.get_value('Item', args.get("item_code"), 'is_stock_item') + if is_stock_item == 'Yes': + bin = get_bin(args.get("item_code"), args.get("warehouse")) + bin.update_stock(args) + return bin + else: + msgprint("[Stock Update] Ignored %s since it is not a stock item" + % args.get("item_code")) def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: @@ -184,7 +237,6 @@ def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries): sle.voucher_detail_no == item_row: previous_stock_value = len(stock_ledger_entries) > i+1 and \ flt(stock_ledger_entries[i+1].stock_value) or 0.0 - buying_amount = previous_stock_value - flt(sle.stock_value) return buying_amount @@ -335,3 +387,12 @@ def notify_errors(exceptions_list): from webnotes.profile import get_system_managers sendmail(get_system_managers(), subject=subject, msg=msg) + + +def repost(): + """ + Repost everything! + """ + from webnotes.model.code import get_obj + for wh in webnotes.conn.sql("select name from tabWarehouse"): + get_obj('Warehouse', wh[0]).repost_stock() diff --git a/support/doctype/support_ticket/get_support_mails.py b/support/doctype/support_ticket/get_support_mails.py index 02f7ea8bfe9..4dcb59e4fe6 100644 --- a/support/doctype/support_ticket/get_support_mails.py +++ b/support/doctype/support_ticket/get_support_mails.py @@ -28,7 +28,7 @@ class SupportMailbox(POP3Mailbox): new_ticket = True ticket = add_support_communication(mail.subject, mail.content, mail.from_email, - docname=thread_id if new_ticket else None, mail=mail) + docname=None if new_ticket else thread_id, mail=mail) if new_ticket and cint(self.email_settings.send_autoreply) and \ "mailer-daemon" not in mail.from_email.lower(): diff --git a/utilities/demo/make_demo.py b/utilities/demo/make_demo.py index 99bc018394c..2d2d07e0eaa 100644 --- a/utilities/demo/make_demo.py +++ b/utilities/demo/make_demo.py @@ -275,7 +275,9 @@ def make_stock_entry_from_pro(pro_id, purpose, current_date): st = webnotes.bean(make_stock_entry(pro_id, purpose)) st.doc.posting_date = current_date st.doc.fiscal_year = "2013" - st.doc.expense_adjustment_account = "Stock in Hand - WP" + for d in st.doclist.get({"parentfield": "mtn_details"}): + d.expense_account = "Stock Adjustment - " + company_abbr + d.cost_center = "Main - " + company_abbr st.insert() webnotes.conn.commit() st.submit() diff --git a/utilities/transaction_base.py b/utilities/transaction_base.py index ea807c248e4..2535db7ab57 100644 --- a/utilities/transaction_base.py +++ b/utilities/transaction_base.py @@ -243,7 +243,8 @@ class TransactionBase(StatusUpdater): def delete_events(self): webnotes.delete_doc("Event", webnotes.conn.sql_list("""select name from `tabEvent` - where ref_type=%s and ref_name=%s""", (self.doc.doctype, self.doc.name))) + where ref_type=%s and ref_name=%s""", (self.doc.doctype, self.doc.name)), + ignore_permissions=True) def _add_calendar_event(self, opts): opts = webnotes._dict(opts)