[minor] fixed conflict while merging perpetual branch into master
This commit is contained in:
commit
11f41955c9
@ -42,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
|
||||
@ -54,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));
|
||||
}
|
||||
|
||||
@ -68,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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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, _
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
@ -16,13 +16,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_warehouse_account()
|
||||
|
||||
if not self.doc.parent_account:
|
||||
self.doc.parent_account = ''
|
||||
|
||||
def validate(self):
|
||||
self.validate_master_name()
|
||||
@ -118,9 +130,7 @@ class DocType:
|
||||
|
||||
# Check if any previous balance exists
|
||||
def check_gle_exists(self):
|
||||
exists = 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 sql("""select name from `tabAccount` where parent_account = %s
|
||||
@ -131,6 +141,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_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"""
|
||||
@ -183,10 +212,6 @@ class DocType:
|
||||
self.validate_trash()
|
||||
self.update_nsm_model()
|
||||
|
||||
# delete all cancelled gl entry of this account
|
||||
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(" - ")
|
||||
@ -214,9 +239,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):
|
||||
|
@ -153,7 +153,8 @@
|
||||
"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
|
||||
},
|
||||
{
|
||||
|
@ -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
|
@ -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()
|
@ -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.",
|
||||
|
@ -1 +0,0 @@
|
||||
Backend scripts for Budget Management.
|
@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
@ -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 <b>Account - "+cstr(acct)+" </b> under <b>Cost Center - "+ cstr(cost_center) + "</b>"+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)
|
@ -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"
|
||||
}
|
||||
]
|
@ -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
|
||||
}
|
||||
]
|
@ -1,4 +1,70 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
test_records = []
|
||||
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"
|
||||
}]
|
||||
]
|
@ -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 \
|
||||
|
@ -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"
|
||||
}],
|
||||
]
|
@ -7,52 +7,49 @@ import webnotes
|
||||
from webnotes.utils import flt, fmt_money, getdate
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint, _
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
|
||||
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.validate_frozen_account(adv_adj)
|
||||
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
|
||||
@ -65,8 +62,8 @@ class DocType:
|
||||
tot_outstanding = 0 #needed when there is no GL Entry in the system for that acc head
|
||||
if (self.doc.voucher_type=='Journal Voucher' or self.doc.voucher_type=='Sales Invoice') \
|
||||
and (master_type =='Customer' and master_name):
|
||||
dbcr = sql("""select sum(debit), sum(credit) from `tabGL Entry`
|
||||
where account = '%s' and is_cancelled='No'""" % self.doc.account)
|
||||
dbcr = webnotes.conn.sql("""select sum(debit), sum(credit) from `tabGL Entry`
|
||||
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)
|
||||
@ -76,22 +73,21 @@ 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 = sql("""select group_or_ledger, docstatus, 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':
|
||||
webnotes.throw(_("Account is not a ledger") + "(%s)" % self.doc.account)
|
||||
if ret.group_or_ledger=='Group':
|
||||
webnotes.throw(_("Account") + ": " + self.doc.account + _(" is not a ledger"))
|
||||
|
||||
if ret and ret[0]["docstatus"]==2:
|
||||
webnotes.throw(_("Account is not active ") + "(%s)" % self.doc.account)
|
||||
|
||||
if self.doc.is_cancelled in ("No", None) and ret and ret[0]["company"] != self.doc.company:
|
||||
if ret.docstatus==2:
|
||||
webnotes.throw(_("Account") + ": " + self.doc.account + _(" is not active"))
|
||||
|
||||
if ret.company != self.doc.company:
|
||||
webnotes.throw(_("Account") + ": " + self.doc.account +
|
||||
_(" does not belong to the company") + ": " + self.doc.company)
|
||||
|
||||
@ -101,84 +97,90 @@ 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 validate_frozen_account(self, adv_adj):
|
||||
frozen_account = webnotes.conn.get_value("Account", self.doc.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(self.doc.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(self.doc.account + _(" is a frozen account. ") +
|
||||
_("To create / edit transactions against this account, you need role") + ": " +
|
||||
frozen_accounts_modifier)
|
||||
|
||||
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 ") + self.doc.account)
|
||||
|
||||
def update_outstanding_amt(self):
|
||||
# get final outstanding amt
|
||||
bal = flt(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"]:
|
||||
sql("update `tab%s` set outstanding_amount=%s where name='%s'"%
|
||||
(self.doc.against_voucher_type, bal, self.doc.against_voucher))
|
||||
def validate_freezed_account(account, adv_adj=False):
|
||||
"""Account has been freezed for other users except account manager"""
|
||||
|
||||
freezed_account = webnotes.conn.get_value("Account", account, "freeze_account")
|
||||
|
||||
if freezed_account == 'Yes' and not adv_adj \
|
||||
and 'Accounts Manager' not in webnotes.user.get_roles():
|
||||
webnotes.throw(_("Account") + ": " + account + _(" has been freezed. \
|
||||
Only Accounts Manager can do transaction against this account"))
|
||||
|
||||
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)
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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 = [
|
||||
[{
|
||||
|
@ -19,6 +19,7 @@ class DocType:
|
||||
webnotes.conn.get_value("Account", self.doc.account, "debit_or_credit").lower() or ""
|
||||
|
||||
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'""" %
|
||||
@ -29,7 +30,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 +136,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 +143,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
|
||||
|
@ -3,179 +3,102 @@
|
||||
|
||||
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
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
|
||||
|
||||
|
||||
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 = 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 = 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 = 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 = 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 = 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):
|
||||
"""Get balance for pl accounts"""
|
||||
|
||||
def get_pl_balances(self, d_or_c):
|
||||
"""Get account (pl) specific balance"""
|
||||
acc_bal = 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
|
||||
|
||||
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 = 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
|
||||
sql("update `tabGL Entry` set is_cancelled = 'Yes' where voucher_type = '%s' and voucher_no = '%s'" % (self.doc.doctype, self.doc.name))
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries)
|
@ -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"
|
||||
}]
|
@ -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()
|
||||
self.on_update()
|
||||
|
@ -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",
|
||||
|
@ -212,28 +212,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)
|
||||
@ -314,9 +315,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 = []
|
||||
|
||||
@ -353,17 +353,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)) +
|
||||
@ -371,7 +369,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"
|
||||
@ -390,13 +388,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"
|
||||
@ -417,6 +415,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):
|
||||
@ -456,4 +455,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)})
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield)})
|
||||
|
@ -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])
|
||||
|
@ -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: {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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))
|
||||
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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -53,7 +53,7 @@ def get_stock_ledger_entries(filters):
|
||||
voucher_detail_no, posting_date, posting_time, stock_value,
|
||||
warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where ifnull(`is_cancelled`, "No") = "No" """
|
||||
where ifnull(`is_cancelled`, 'No') = No'"""
|
||||
|
||||
if filters.get("company"):
|
||||
query += """ and company=%(company)s"""
|
||||
|
@ -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([("<a href=\"#Form/Journal Voucher/%s\">%s</a>" % (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: \
|
||||
<a href="http://erpnext.com/auto-inventory-accounting" target="_blank">\
|
||||
Auto Inventory Accounting</a>"""))
|
||||
|
||||
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,93 @@ 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))
|
||||
(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()
|
||||
|
||||
account_warehouse_map = {}
|
||||
difference = {}
|
||||
warehouse_account = webnotes.conn.sql("""select name, account from tabWarehouse
|
||||
where account in (%s)""" % ', '.join(['%s']*len(account_list)), account_list, as_dict=1)
|
||||
|
||||
for wh in warehouse_account:
|
||||
account_warehouse_map.setdefault(wh.account, []).append(wh.name)
|
||||
|
||||
for account, warehouse_list in account_warehouse_map.items():
|
||||
account_balance = get_balance_on(account, posting_date)
|
||||
stock_value = get_stock_balance_on(warehouse_list, 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]
|
@ -89,6 +89,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'
|
||||
@ -123,12 +124,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 = sql("select modified from `tabPurchase Order` where name = '%s'" % self.doc.name)
|
||||
|
@ -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):
|
||||
|
@ -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"]]
|
||||
|
@ -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"
|
||||
|
@ -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()
|
42
docs/docs.user.stock.accounting_for_stock.md
Normal file
42
docs/docs.user.stock.accounting_for_stock.md
Normal file
@ -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)
|
313
docs/docs.user.stock.perpetual_inventory.md
Normal file
313
docs/docs.user.stock.perpetual_inventory.md
Normal file
@ -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:
|
||||
|
||||
<b>Supplier:</b> East Wind Inc.
|
||||
|
||||
<b>Items:</b>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th><th>Warehouse</th><th>Qty</th>
|
||||
<th>Rate</th><th>Amount</th><th>Valuation Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>RM0001</td><td>Stores</td><td>10</td><td>200</td><td>2000</td><td>2200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Desktop</td><td>Fixed Asset Warehouse</td>
|
||||
<td>5</td><td>100</td><td>500</td><td>550</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**Taxes:**
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Account</th><th>Amount</th><th>Category</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Shipping Charges</td><td>100</td><td>Total and Valuation</td></tr>
|
||||
<tr><td>VAT</td><td>120</td><td>Total</td></tr>
|
||||
<tr><td>Customs Duty</td><td>150</td><td>Valuation</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**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:**
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Item</th><th>Warehouse</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RM0001</td><td>Stores</td><td>5</td><td>300</td><td>1500</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**Taxes:**
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Account</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Service Tax</td><td>150</td></tr>
|
||||
<tr><td>VAT</td><td>100</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
**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.
|
||||
<pre>
|
||||
<code>
|
||||
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
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
--
|
||||
|
||||
### **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:**
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Item</th><th>Target Warehouse</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RM0001</td><td>Stores</td><td>50</td><td>220</td><td>11000</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**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:**
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Item</th><th>Source Warehouse</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RM0001</td><td>Stores</td><td>10</td><td>220</td><td>2200</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**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:**
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Item</th><th>Source Warehouse</th><th>Target Warehouse</th>
|
||||
<th>Qty</th><th>Rate</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RM0001</td><td>Stores</td><td>Work In Progress</td>
|
||||
<td>10</td><td>220</td><td>2200</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**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:**
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Item</th><th>Target Warehouse</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RM0001</td><td>Stores</td><td>2</td><td>200</td><td>400</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**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:**
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr><th>Item</th><th>Source Warehouse</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RM0001</td><td>Stores</td><td>4</td><td>220</td><td>880</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**Stock Ledger**
|
||||
|
||||
![pret_stock_ledger](img/accounting-for-stock-17.png)
|
||||
|
||||
**General Ledger**
|
||||
|
||||
![pret_general_ledger](img/accounting-for-stock-18.png)
|
@ -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.
|
@ -117,10 +117,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):
|
||||
|
@ -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()
|
11
patches/august_2013/p06_deprecate_is_cancelled.py
Normal file
11
patches/august_2013/p06_deprecate_is_cancelled.py
Normal file
@ -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'""")
|
@ -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",
|
||||
|
@ -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
|
@ -8,7 +8,6 @@ def execute():
|
||||
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 +44,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,26 +77,7 @@ 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`"):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'
|
||||
|
@ -146,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",
|
||||
@ -194,12 +193,15 @@ 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",
|
||||
"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",
|
||||
"patches.september_2013.p01_add_user_defaults_from_pos_setting",
|
||||
"execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-09-02",
|
||||
@ -208,6 +210,8 @@ 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",
|
||||
|
@ -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"],
|
||||
});
|
@ -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,
|
||||
|
@ -68,9 +68,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
|
||||
|
@ -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
|
||||
|
@ -267,17 +267,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):
|
||||
|
@ -42,7 +42,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'):
|
||||
|
@ -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}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -62,7 +62,8 @@ class DocType:
|
||||
webnotes.bean({
|
||||
"doctype":"Warehouse",
|
||||
"warehouse_name": whname,
|
||||
"company": self.doc.name
|
||||
"company": self.doc.name,
|
||||
"create_account_under": "Stock Assets - " + self.doc.abbr
|
||||
}).insert()
|
||||
|
||||
def create_default_web_page(self):
|
||||
@ -113,7 +114,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,''],
|
||||
@ -243,8 +243,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"
|
||||
}
|
||||
@ -254,9 +254,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 = [
|
||||
{
|
||||
@ -276,10 +273,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)
|
||||
@ -288,11 +284,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)
|
||||
|
||||
@ -301,6 +294,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=""
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"],
|
||||
|
@ -65,7 +65,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)
|
||||
|
@ -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];
|
||||
|
@ -10,11 +10,7 @@ from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint, _
|
||||
import webnotes.defaults
|
||||
from webnotes.model.mapper import get_mapped_doclist
|
||||
|
||||
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
|
||||
from stock.utils import update_bin
|
||||
from controllers.selling_controller import SellingController
|
||||
|
||||
class DocType(SellingController):
|
||||
@ -58,7 +54,7 @@ class DocType(SellingController):
|
||||
def set_actual_qty(self):
|
||||
for d in getlist(self.doclist, 'delivery_note_details'):
|
||||
if d.item_code and d.warehouse:
|
||||
actual_qty = sql("select actual_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (d.item_code, d.warehouse))
|
||||
actual_qty = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (d.item_code, d.warehouse))
|
||||
d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0
|
||||
|
||||
|
||||
@ -130,11 +126,10 @@ 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:
|
||||
res = sql("select name from `tabProject` where name = '%s' and (customer = '%s' or ifnull(customer,'')='')"%(self.doc.project_name, self.doc.customer))
|
||||
res = webnotes.conn.sql("select name from `tabProject` where name = '%s' and (customer = '%s' or ifnull(customer,'')='')"%(self.doc.project_name, self.doc.customer))
|
||||
if not res:
|
||||
msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in project - %s."%(self.doc.customer,self.doc.project_name,self.doc.project_name))
|
||||
raise Exception
|
||||
@ -164,15 +159,15 @@ 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'):
|
||||
bin = sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
|
||||
bin = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
|
||||
d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
|
||||
|
||||
for d in getlist(self.doclist, 'packing_details'):
|
||||
bin = sql("select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
|
||||
bin = webnotes.conn.sql("select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
|
||||
d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
|
||||
d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0
|
||||
|
||||
@ -189,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
|
||||
@ -208,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')
|
||||
@ -267,12 +261,12 @@ class DocType(SellingController):
|
||||
webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
|
||||
|
||||
def check_next_docstatus(self):
|
||||
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||
submit_rv = webnotes.conn.sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||
if submit_rv:
|
||||
msgprint("Sales Invoice : " + cstr(submit_rv[0][0]) + " has already been submitted !")
|
||||
raise Exception , "Validation Error."
|
||||
|
||||
submit_in = sql("select t1.name from `tabInstallation Note` t1, `tabInstallation Note Item` t2 where t1.name = t2.parent and t2.prevdoc_docname = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||
submit_in = webnotes.conn.sql("select t1.name from `tabInstallation Note` t1, `tabInstallation Note Item` t2 where t1.name = t2.parent and t2.prevdoc_docname = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||
if submit_in:
|
||||
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
|
||||
raise Exception , "Validation Error."
|
||||
@ -292,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
|
||||
@ -355,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}"""
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
]
|
||||
]
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
@ -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);
|
@ -7,226 +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
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
|
||||
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 = 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 = 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 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 = 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
|
||||
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)
|
||||
sql("update `tabStock Ledger Entry` set incoming_rate = '%s' where voucher_detail_no = '%s'"%(flt(d.valuation_rate), d.name))
|
||||
|
||||
res = 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")
|
@ -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": "<div class=\"field_description\"><b>Process:</b><br>1. Fetch and select Purchase Receipt<br>2. Enter extra costs<br>3. Click on Update Landed Cost button<br> 4. Cost will be added into other charges table of selected PR proportionately based on net total<br>5. Item Valuation Rate will be recalculated</div>"
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -9,8 +9,7 @@ from webnotes.model.bean import getlist
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint
|
||||
import webnotes.defaults
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
from stock.utils import update_bin
|
||||
|
||||
from controllers.buying_controller import BuyingController
|
||||
class DocType(BuyingController):
|
||||
@ -43,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)
|
||||
|
||||
@ -111,113 +140,74 @@ 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
|
||||
ins_reqd = sql("select inspection_required from `tabItem` where name = %s",
|
||||
ins_reqd = webnotes.conn.sql("select inspection_required from `tabItem` where name = %s",
|
||||
(d.item_code,), as_dict = 1)
|
||||
ins_reqd = ins_reqd and ins_reqd[0]['inspection_required'] or 'No'
|
||||
if ins_reqd == 'Yes' and not d.qa_no:
|
||||
@ -244,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()
|
||||
@ -270,7 +260,7 @@ class DocType(BuyingController):
|
||||
sr.save()
|
||||
|
||||
def check_next_docstatus(self):
|
||||
submit_rv = 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))
|
||||
submit_rv = 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))
|
||||
if submit_rv:
|
||||
msgprint("Purchase Invoice : " + cstr(self.submit_rv[0][0]) + " has already been submitted !")
|
||||
raise Exception , "Validation Error."
|
||||
@ -280,69 +270,43 @@ 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 = 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)
|
||||
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)
|
||||
if submitted:
|
||||
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:
|
||||
bin = sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.rm_item_code, self.doc.supplier_warehouse), as_dict = 1)
|
||||
bin = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.rm_item_code, self.doc.supplier_warehouse), as_dict = 1)
|
||||
d.current_stock = bin and flt(bin[0]['actual_qty']) or 0
|
||||
|
||||
|
||||
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()
|
||||
|
@ -7,9 +7,13 @@ import unittest
|
||||
import webnotes
|
||||
import webnotes.defaults
|
||||
from webnotes.utils import cint
|
||||
from accounts.utils import get_stock_and_account_difference
|
||||
|
||||
|
||||
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 +33,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 +117,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 +153,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",
|
||||
|
@ -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"
|
||||
},
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
]
|
@ -6,6 +6,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes, unittest
|
||||
from accounts.utils import get_stock_and_account_difference
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
test_records = []
|
||||
@ -24,6 +25,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)
|
@ -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 &&
|
||||
|
@ -39,7 +39,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()
|
||||
@ -52,13 +51,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()
|
||||
@ -76,8 +75,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)
|
||||
|
||||
@ -177,33 +177,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'):
|
||||
@ -232,7 +205,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:
|
||||
@ -340,20 +313,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:
|
||||
@ -371,14 +358,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)) \
|
||||
@ -630,26 +619,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:
|
||||
@ -788,7 +757,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
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
},
|
||||
],
|
||||
]
|
@ -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",
|
||||
|
@ -1 +0,0 @@
|
||||
Control (to be deprecated) for updating stock entries.
|
@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
@ -1,58 +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 add_days, cstr, flt, nowdate, cint, now
|
||||
from webnotes.model.doc import Document
|
||||
from webnotes.model.bean import getlist
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import session, msgprint
|
||||
from stock.utils import get_valid_serial_nos
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
|
||||
class DocType:
|
||||
def __init__(self, doc, doclist=[]):
|
||||
self.doc = doc
|
||||
self.doclist = doclist
|
||||
|
||||
def update_stock(self, values, is_amended = 'No'):
|
||||
for v in values:
|
||||
sle_id = ''
|
||||
|
||||
# reverse quantities for cancel
|
||||
if v.get('is_cancelled') == 'Yes':
|
||||
v['actual_qty'] = -flt(v['actual_qty'])
|
||||
# cancel matching entry
|
||||
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, v['voucher_no'], v['voucher_type']))
|
||||
|
||||
if v.get("actual_qty"):
|
||||
sle_id = self.make_entry(v)
|
||||
|
||||
args = v.copy()
|
||||
args.update({
|
||||
"sle_id": sle_id,
|
||||
"is_amended": is_amended
|
||||
})
|
||||
|
||||
get_obj('Warehouse', v["warehouse"]).update_bin(args)
|
||||
|
||||
|
||||
def make_entry(self, args):
|
||||
args.update({"doctype": "Stock Ledger Entry"})
|
||||
sle = webnotes.bean([args])
|
||||
sle.ignore_permissions = 1
|
||||
sle.insert()
|
||||
return sle.doc.name
|
||||
|
||||
def repost(self):
|
||||
"""
|
||||
Repost everything!
|
||||
"""
|
||||
for wh in webnotes.conn.sql("select name from tabWarehouse"):
|
||||
get_obj('Warehouse', wh[0]).repost_stock()
|
@ -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"
|
||||
}
|
||||
]
|
@ -41,6 +41,9 @@ class DocType(DocListController):
|
||||
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.validate_serial_no()
|
||||
|
||||
#check for item quantity available in stock
|
||||
def actual_amt_check(self):
|
||||
if self.doc.batch_no:
|
||||
@ -71,10 +74,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 +91,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),
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,6 +18,7 @@ class DocType(StockController):
|
||||
|
||||
def validate(self):
|
||||
self.validate_data()
|
||||
self.validate_expense_account()
|
||||
|
||||
def on_submit(self):
|
||||
self.insert_stock_ledger_entries()
|
||||
@ -24,7 +26,7 @@ class DocType(StockController):
|
||||
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 +58,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 +89,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 +245,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:
|
||||
@ -292,25 +286,36 @@ class DocType(StockController):
|
||||
|
||||
stock_ledger_entries = self.get_stock_ledger_entries(item_list, warehouse_list)
|
||||
|
||||
self.doc.stock_value_difference = 0.0
|
||||
stock_value_difference = {}
|
||||
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)
|
||||
diff = get_buying_amount(self.doc.doctype, self.doc.name, d.voucher_detail_no,
|
||||
stock_ledger_entries.get((d.item_code, d.warehouse), []))
|
||||
stock_value_difference.setdefault(d.warehouse, 0.0)
|
||||
stock_value_difference[d.warehouse] -= diff
|
||||
|
||||
webnotes.conn.set(self.doc, "stock_value_difference", json.dumps(stock_value_difference))
|
||||
|
||||
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)
|
||||
|
||||
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():
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-03-28 10:35:31",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-22 15:22:44",
|
||||
"modified": "2013-08-29 16:46:33",
|
||||
"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",
|
||||
@ -151,7 +158,7 @@
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "stock_value_difference",
|
||||
"fieldtype": "Currency",
|
||||
"fieldtype": "Long Text",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Stock Value Difference",
|
||||
|
@ -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"]
|
@ -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",
|
||||
|
@ -15,4 +15,14 @@ cur_frm.cscript.merge = function(doc, cdt, cdn) {
|
||||
if (check) {
|
||||
return $c_obj(make_doclist(cdt, cdn), 'merge_warehouses', '', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.set_query("create_account_under", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": cur_frm.doc.company,
|
||||
"debit_or_credit": "Debit",
|
||||
'group_or_ledger': "Group"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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, _
|
||||
|
||||
sql = webnotes.conn.sql
|
||||
|
||||
@ -19,39 +19,51 @@ 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 = 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)
|
||||
|
||||
self.validate_parent_account()
|
||||
|
||||
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}):
|
||||
if self.doc.__islocal or not webnotes.conn.get_value("Stock Ledger Entry",
|
||||
{"warehouse": self.doc.name}):
|
||||
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 cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")) and \
|
||||
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):
|
||||
webnotes.conn.set_value("Account", {"account_type": "Warehouse", "master_name": old},
|
||||
"master_name", new)
|
||||
|
||||
def merge_warehouses(self):
|
||||
webnotes.conn.auto_commit_on_many_writes = 1
|
||||
@ -66,6 +78,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)
|
||||
|
||||
@ -76,9 +97,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)
|
||||
@ -173,9 +195,13 @@ class DocType:
|
||||
else:
|
||||
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 sql("""select name from `tabStock Ledger Entry`
|
||||
where warehouse = %s and ifnull('is_cancelled', '') = 'No'""", self.doc.name):
|
||||
if 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:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user