[minor] fixed conflict

This commit is contained in:
Nabin Hait 2013-10-15 11:53:03 +05:30
commit 028a5202f6
380 changed files with 7668 additions and 5785 deletions

View File

@ -18,12 +18,22 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.toggle_display('account_name', doc.__islocal);
// hide fields if group
cur_frm.toggle_display(['account_type', 'master_type', 'master_name', 'freeze_account',
cur_frm.toggle_display(['account_type', 'master_type', 'master_name',
'credit_days', 'credit_limit', 'tax_rate'], doc.group_or_ledger=='Ledger')
// disable fields
cur_frm.toggle_enable(['account_name', 'debit_or_credit', 'group_or_ledger',
'is_pl_account', 'company'], false);
if(doc.group_or_ledger=='Ledger') {
wn.model.with_doc("Accounts Settings", "Accounts Settings", function (name) {
var accounts_settings = wn.model.get_doc("Accounts Settings", name);
var display = accounts_settings["frozen_accounts_modifier"]
&& in_list(user_roles, accounts_settings["frozen_accounts_modifier"]);
cur_frm.toggle_display('freeze_account', display);
});
}
// read-only for root accounts
if(!doc.parent_account) {
@ -32,10 +42,10 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
} else {
// credit days and type if customer or supplier
cur_frm.set_intro(null);
cur_frm.toggle_display(['credit_days', 'credit_limit', 'master_name'],
in_list(['Customer', 'Supplier'], doc.master_type));
// hide tax_rate
cur_frm.toggle_display(['credit_days', 'credit_limit'], in_list(['Customer', 'Supplier'],
doc.master_type));
cur_frm.cscript.master_type(doc, cdt, cdn);
cur_frm.cscript.account_type(doc, cdt, cdn);
// show / hide convert buttons
@ -44,7 +54,10 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
}
cur_frm.cscript.master_type = function(doc, cdt, cdn) {
cur_frm.toggle_display(['credit_days', 'credit_limit', 'master_name'],
cur_frm.toggle_display(['credit_days', 'credit_limit'], in_list(['Customer', 'Supplier'],
doc.master_type));
cur_frm.toggle_display('master_name', doc.account_type=='Warehouse' ||
in_list(['Customer', 'Supplier'], doc.master_type));
}
@ -58,10 +71,10 @@ cur_frm.add_fetch('parent_account', 'is_pl_account', 'is_pl_account');
// -----------------------------------------
cur_frm.cscript.account_type = function(doc, cdt, cdn) {
if(doc.group_or_ledger=='Ledger') {
cur_frm.toggle_display(['tax_rate'],
doc.account_type == 'Tax');
cur_frm.toggle_display(['master_type', 'master_name'],
cstr(doc.account_type)=='');
cur_frm.toggle_display(['tax_rate'], doc.account_type == 'Tax');
cur_frm.toggle_display('master_type', cstr(doc.account_type)=='');
cur_frm.toggle_display('master_name', doc.account_type=='Warehouse' ||
in_list(['Customer', 'Supplier'], doc.master_type));
}
}
@ -109,11 +122,15 @@ cur_frm.cscript.convert_to_group = function(doc, cdt, cdn) {
}
cur_frm.fields_dict['master_name'].get_query = function(doc) {
if (doc.master_type) {
if (doc.master_type || doc.account_type=="Warehouse") {
var dt = doc.master_type || "Warehouse";
return {
doctype: doc.master_type,
doctype: dt,
query: "accounts.doctype.account.account.get_master_name",
filters: { "master_type": doc.master_type }
filters: {
"master_type": dt,
"company": doc.company
}
}
}
}

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import flt, fmt_money
from webnotes.utils import flt, fmt_money, cstr, cint
from webnotes import msgprint, _
get_value = webnotes.conn.get_value
@ -15,13 +15,25 @@ class DocType:
self.nsm_parent_field = 'parent_account'
def autoname(self):
"""Append abbreviation to company on naming"""
self.doc.name = self.doc.account_name.strip() + ' - ' + \
webnotes.conn.get_value("Company", self.doc.company, "abbr")
def get_address(self):
address = webnotes.conn.get_value(self.doc.master_type, self.doc.master_name, "address")
return {'address': address}
return {
'address': webnotes.conn.get_value(self.doc.master_type,
self.doc.master_name, "address")
}
def validate(self):
self.validate_master_name()
self.validate_parent()
self.validate_duplicate_account()
self.validate_root_details()
self.validate_mandatory()
self.validate_frozen_accounts_modifier()
if not self.doc.parent_account:
self.doc.parent_account = ''
def validate_master_name(self):
"""Remind to add master name"""
@ -70,6 +82,15 @@ class DocType:
if webnotes.conn.exists("Account", self.doc.name):
if not webnotes.conn.get_value("Account", self.doc.name, "parent_account"):
webnotes.msgprint("Root cannot be edited.", raise_exception=1)
def validate_frozen_accounts_modifier(self):
old_value = webnotes.conn.get_value("Account", self.doc.name, "freeze_account")
if old_value and old_value != self.doc.freeze_account:
frozen_accounts_modifier = webnotes.conn.get_value( 'Accounts Settings', None,
'frozen_accounts_modifier')
if not frozen_accounts_modifier or \
frozen_accounts_modifier not in webnotes.user.get_roles():
webnotes.throw(_("You are not authorized to set Frozen value"))
def convert_group_to_ledger(self):
if self.check_if_child_exists():
@ -97,9 +118,7 @@ class DocType:
# Check if any previous balance exists
def check_gle_exists(self):
exists = webnotes.conn.sql("""select name from `tabGL Entry` where account = %s
and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name)
return exists and exists[0][0] or ''
return webnotes.conn.get_value("GL Entry", {"account": self.doc.name})
def check_if_child_exists(self):
return webnotes.conn.sql("""select name from `tabAccount` where parent_account = %s
@ -110,16 +129,25 @@ class DocType:
msgprint("Debit or Credit field is mandatory", raise_exception=1)
if not self.doc.is_pl_account:
msgprint("Is PL Account field is mandatory", raise_exception=1)
def validate(self):
self.validate_master_name()
self.validate_parent()
self.validate_duplicate_account()
self.validate_root_details()
self.validate_mandatory()
if not self.doc.parent_account:
self.doc.parent_account = ''
def validate_warehouse_account(self):
if not cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")):
return
if self.doc.account_type == "Warehouse":
old_warehouse = cstr(webnotes.conn.get_value("Account", self.doc.name, "master_name"))
if old_warehouse != cstr(self.doc.master_name):
if old_warehouse:
self.validate_warehouse(old_warehouse)
if self.doc.master_name:
self.validate_warehouse(self.doc.master_name)
else:
webnotes.throw(_("Master Name is mandatory if account type is Warehouse"))
def validate_warehouse(self, warehouse):
if webnotes.conn.get_value("Stock Ledger Entry", {"warehouse": warehouse}):
webnotes.throw(_("Stock transactions exist against warehouse ") + warehouse +
_(" .You can not assign / modify / remove Master Name"))
def update_nsm_model(self):
"""update lft, rgt indices for nested set model"""
@ -172,10 +200,6 @@ class DocType:
self.validate_trash()
self.update_nsm_model()
# delete all cancelled gl entry of this account
webnotes.conn.sql("""delete from `tabGL Entry` where account = %s and
ifnull(is_cancelled, 'No') = 'Yes'""", self.doc.name)
def on_rename(self, new, old, merge=False):
company_abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr")
parts = new.split(" - ")
@ -203,9 +227,11 @@ class DocType:
return " - ".join(parts)
def get_master_name(doctype, txt, searchfield, start, page_len, filters):
return webnotes.conn.sql("""select name from `tab%s` where %s like %s
conditions = (" and company='%s'"% filters["company"]) if doctype == "Warehouse" else ""
return webnotes.conn.sql("""select name from `tab%s` where %s like %s %s
order by name limit %s, %s""" %
(filters["master_type"], searchfield, "%s", "%s", "%s"),
(filters["master_type"], searchfield, "%s", conditions, "%s", "%s"),
("%%%s%%" % txt, start, page_len), as_list=1)
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):

View File

@ -2,7 +2,7 @@
{
"creation": "2013-01-30 12:49:46",
"docstatus": 0,
"modified": "2013-07-05 14:23:30",
"modified": "2013-09-24 11:22:18",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -23,7 +23,8 @@
"name": "__common__",
"parent": "Account",
"parentfield": "fields",
"parenttype": "DocType"
"parenttype": "DocType",
"permlevel": 0
},
{
"amend": 0,
@ -45,14 +46,12 @@
"fieldname": "properties",
"fieldtype": "Section Break",
"label": "Account Details",
"oldfieldtype": "Section Break",
"permlevel": 0
"oldfieldtype": "Section Break"
},
{
"doctype": "DocField",
"fieldname": "column_break0",
"fieldtype": "Column Break",
"permlevel": 0,
"width": "50%"
},
{
@ -64,7 +63,6 @@
"no_copy": 1,
"oldfieldname": "account_name",
"oldfieldtype": "Data",
"permlevel": 0,
"read_only": 1,
"reqd": 1,
"search_index": 1
@ -77,7 +75,6 @@
"label": "Level",
"oldfieldname": "level",
"oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
@ -91,7 +88,6 @@
"oldfieldname": "group_or_ledger",
"oldfieldtype": "Select",
"options": "\nLedger\nGroup",
"permlevel": 0,
"read_only": 1,
"reqd": 1,
"search_index": 1
@ -104,7 +100,6 @@
"label": "Debit or Credit",
"oldfieldname": "debit_or_credit",
"oldfieldtype": "Data",
"permlevel": 0,
"read_only": 1,
"search_index": 1
},
@ -117,7 +112,6 @@
"oldfieldname": "is_pl_account",
"oldfieldtype": "Select",
"options": "Yes\nNo",
"permlevel": 0,
"read_only": 1,
"search_index": 1
},
@ -130,7 +124,6 @@
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"permlevel": 0,
"read_only": 1,
"reqd": 1,
"search_index": 1
@ -139,7 +132,6 @@
"doctype": "DocField",
"fieldname": "column_break1",
"fieldtype": "Column Break",
"permlevel": 0,
"width": "50%"
},
{
@ -150,7 +142,6 @@
"oldfieldname": "parent_account",
"oldfieldtype": "Link",
"options": "Account",
"permlevel": 0,
"search_index": 1
},
{
@ -162,7 +153,7 @@
"label": "Account Type",
"oldfieldname": "account_type",
"oldfieldtype": "Select",
"options": "\nFixed Asset Account\nBank or Cash\nExpense Account\nTax\nIncome Account\nChargeable",
"options": "\nFixed Asset Account\nBank or Cash\nExpense Account\nTax\nIncome Account\nChargeable\nWarehouse",
"permlevel": 0,
"search_index": 0
},
@ -175,19 +166,17 @@
"label": "Rate",
"oldfieldname": "tax_rate",
"oldfieldtype": "Currency",
"permlevel": 0,
"reqd": 0
},
{
"description": "If the account is frozen, entries are allowed for the \"Account Manager\" only.",
"description": "If the account is frozen, entries are allowed to restricted users.",
"doctype": "DocField",
"fieldname": "freeze_account",
"fieldtype": "Select",
"label": "Frozen",
"oldfieldname": "freeze_account",
"oldfieldtype": "Select",
"options": "No\nYes",
"permlevel": 2
"options": "No\nYes"
},
{
"doctype": "DocField",
@ -197,7 +186,6 @@
"label": "Credit Days",
"oldfieldname": "credit_days",
"oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 1
},
{
@ -209,7 +197,6 @@
"oldfieldname": "credit_limit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 1
},
{
@ -220,8 +207,7 @@
"label": "Master Type",
"oldfieldname": "master_type",
"oldfieldtype": "Select",
"options": "\nSupplier\nCustomer\nEmployee",
"permlevel": 0
"options": "\nSupplier\nCustomer\nEmployee"
},
{
"doctype": "DocField",
@ -230,8 +216,7 @@
"label": "Master Name",
"oldfieldname": "master_name",
"oldfieldtype": "Link",
"options": "[Select]",
"permlevel": 0
"options": "[Select]"
},
{
"default": "1",
@ -239,8 +224,7 @@
"doctype": "DocField",
"fieldname": "allow_negative_balance",
"fieldtype": "Check",
"label": "Allow Negative Balance",
"permlevel": 0
"label": "Allow Negative Balance"
},
{
"doctype": "DocField",
@ -248,7 +232,6 @@
"fieldtype": "Int",
"hidden": 1,
"label": "Lft",
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
@ -258,7 +241,6 @@
"fieldtype": "Int",
"hidden": 1,
"label": "Rgt",
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
@ -268,7 +250,6 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Old Parent",
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},

View File

@ -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

View File

@ -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()

View File

@ -2,7 +2,7 @@
{
"creation": "2013-06-24 15:49:57",
"docstatus": 0,
"modified": "2013-07-05 14:23:40",
"modified": "2013-09-24 11:52:58",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -39,11 +39,12 @@
"name": "Accounts Settings"
},
{
"default": "1",
"description": "If enabled, the system will post accounting entries for inventory automatically.",
"doctype": "DocField",
"fieldname": "auto_inventory_accounting",
"fieldname": "auto_accounting_for_stock",
"fieldtype": "Check",
"label": "Enable Auto Inventory Accounting"
"label": "Make Accounting Entry For Every Stock Movement"
},
{
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
@ -53,11 +54,19 @@
"label": "Accounts Frozen Upto"
},
{
"description": "Users with this role are allowed to do / modify accounting entry before frozen date",
"description": "Users with this role are allowed to create / modify accounting entry before frozen date",
"doctype": "DocField",
"fieldname": "bde_auth_role",
"fieldtype": "Link",
"label": "Allow Editing of Frozen Accounts For",
"label": "Allowed Role to Edit Entries Before Frozen Date",
"options": "Role"
},
{
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
"doctype": "DocField",
"fieldname": "frozen_accounts_modifier",
"fieldtype": "Link",
"label": "Frozen Accounts Modifier",
"options": "Role"
},
{

View File

@ -1 +0,0 @@
Backend scripts for Budget Management.

View File

@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@ -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)

View File

@ -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"
}
]

View File

@ -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
}
]

View File

@ -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"
}]
]

View File

@ -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 \

View File

@ -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"
}],
]

View File

@ -7,50 +7,49 @@ import webnotes
from webnotes.utils import flt, fmt_money, getdate
from webnotes.model.code import get_obj
from webnotes import msgprint, _
class DocType:
def __init__(self,d,dl):
self.doc, self.doclist = d, dl
def validate(self): # not called on cancel
def validate(self):
self.check_mandatory()
self.pl_must_have_cost_center()
self.validate_posting_date()
self.doc.is_cancelled = 'No' # will be reset by GL Control if cancelled
self.check_credit_limit()
self.check_pl_account()
def on_update(self, adv_adj, cancel, update_outstanding = 'Yes'):
self.validate_account_details(adv_adj)
self.validate_cost_center()
self.check_freezing_date(adv_adj)
self.check_negative_balance(adv_adj)
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
self.validate_account_details(adv_adj)
validate_frozen_account(self.doc.account, adv_adj)
check_freezing_date(self.doc.posting_date, adv_adj)
check_negative_balance(self.doc.account, adv_adj)
# Update outstanding amt on against voucher
if self.doc.against_voucher and self.doc.against_voucher_type != "POS" \
and update_outstanding == 'Yes':
self.update_outstanding_amt()
update_outstanding_amt(self.doc.account, self.doc.against_voucher_type,
self.doc.against_voucher)
def check_mandatory(self):
mandatory = ['account','remarks','voucher_type','voucher_no','fiscal_year','company']
for k in mandatory:
if not self.doc.fields.get(k):
msgprint(k + _(" is mandatory for GL Entry"), raise_exception=1)
webnotes.throw(k + _(" is mandatory for GL Entry"))
# Zero value transaction is not allowed
if not (flt(self.doc.debit) or flt(self.doc.credit)):
msgprint(_("GL Entry: Debit or Credit amount is mandatory for ") + self.doc.account,
raise_exception=1)
webnotes.throw(_("GL Entry: Debit or Credit amount is mandatory for ") +
self.doc.account)
def pl_must_have_cost_center(self):
if webnotes.conn.get_value("Account", self.doc.account, "is_pl_account") == "Yes":
if not self.doc.cost_center and self.doc.voucher_type != 'Period Closing Voucher':
msgprint(_("Cost Center must be specified for PL Account: ") + self.doc.account,
raise_exception=1)
else:
if self.doc.cost_center:
self.doc.cost_center = ""
webnotes.throw(_("Cost Center must be specified for PL Account: ") +
self.doc.account)
elif self.doc.cost_center:
self.doc.cost_center = None
def validate_posting_date(self):
from accounts.utils import validate_fiscal_year
@ -64,7 +63,7 @@ class DocType:
if (self.doc.voucher_type=='Journal Voucher' or self.doc.voucher_type=='Sales Invoice') \
and (master_type =='Customer' and master_name):
dbcr = webnotes.conn.sql("""select sum(debit), sum(credit) from `tabGL Entry`
where account = '%s' and is_cancelled='No'""" % self.doc.account)
where account = %s""", self.doc.account)
if dbcr:
tot_outstanding = flt(dbcr[0][0]) - flt(dbcr[0][1]) + \
flt(self.doc.debit) - flt(self.doc.credit)
@ -74,30 +73,23 @@ class DocType:
def check_pl_account(self):
if self.doc.is_opening=='Yes' and \
webnotes.conn.get_value("Account", self.doc.account, "is_pl_account") == "Yes":
msgprint(_("For opening balance entry account can not be a PL account"),
raise_exception=1)
webnotes.throw(_("For opening balance entry account can not be a PL account"))
def validate_account_details(self, adv_adj):
"""Account must be ledger, active and not freezed"""
ret = webnotes.conn.sql("""select group_or_ledger, docstatus, freeze_account, company
from tabAccount where name=%s""", self.doc.account, as_dict=1)
ret = webnotes.conn.sql("""select group_or_ledger, docstatus, company
from tabAccount where name=%s""", self.doc.account, as_dict=1)[0]
if ret and ret[0]["group_or_ledger"]=='Group':
msgprint(_("Account") + ": " + self.doc.account + _(" is not a ledger"), raise_exception=1)
if ret.group_or_ledger=='Group':
webnotes.throw(_("Account") + ": " + self.doc.account + _(" is not a ledger"))
if ret and ret[0]["docstatus"]==2:
msgprint(_("Account") + ": " + self.doc.account + _(" is not active"), raise_exception=1)
if ret.docstatus==2:
webnotes.throw(_("Account") + ": " + self.doc.account + _(" is not active"))
# Account has been freezed for other users except account manager
if ret and ret[0]["freeze_account"]== 'Yes' and not adv_adj \
and not 'Accounts Manager' in webnotes.user.get_roles():
msgprint(_("Account") + ": " + self.doc.account + _(" has been freezed. \
Only Accounts Manager can do transaction against this account"), raise_exception=1)
if self.doc.is_cancelled in ("No", None) and ret and ret[0]["company"] != self.doc.company:
msgprint(_("Account") + ": " + self.doc.account + _(" does not belong to the company") \
+ ": " + self.doc.company, raise_exception=1)
if ret.company != self.doc.company:
webnotes.throw(_("Account") + ": " + self.doc.account +
_(" does not belong to the company") + ": " + self.doc.company)
def validate_cost_center(self):
if not hasattr(self, "cost_center_company"):
@ -105,70 +97,80 @@ class DocType:
def _get_cost_center_company():
if not self.cost_center_company.get(self.doc.cost_center):
self.cost_center_company[self.doc.cost_center] = webnotes.conn.get_value("Cost Center",
self.doc.cost_center, "company")
self.cost_center_company[self.doc.cost_center] = webnotes.conn.get_value(
"Cost Center", self.doc.cost_center, "company")
return self.cost_center_company[self.doc.cost_center]
if self.doc.is_cancelled in ("No", None) and \
self.doc.cost_center and _get_cost_center_company() != self.doc.company:
msgprint(_("Cost Center") + ": " + self.doc.cost_center \
+ _(" does not belong to the company") + ": " + self.doc.company, raise_exception=True)
def check_freezing_date(self, adv_adj):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = webnotes.conn.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
bde_auth_role = webnotes.conn.get_value( 'Accounts Settings', None,'bde_auth_role')
if getdate(self.doc.posting_date) <= getdate(acc_frozen_upto) \
and not bde_auth_role in webnotes.user.get_roles():
msgprint(_("You are not authorized to do/modify back dated entries before ") +
getdate(acc_frozen_upto).strftime('%d-%m-%Y'), raise_exception=1)
if self.doc.cost_center and _get_cost_center_company() != self.doc.company:
webnotes.throw(_("Cost Center") + ": " + self.doc.cost_center +
_(" does not belong to the company") + ": " + self.doc.company)
def check_negative_balance(self, adv_adj):
if not adv_adj:
account = webnotes.conn.get_value("Account", self.doc.account,
["allow_negative_balance", "debit_or_credit"], as_dict=True)
if not account["allow_negative_balance"]:
balance = webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry`
where account = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.account)
balance = account["debit_or_credit"] == "Debit" and \
flt(balance[0][0]) or -1*flt(balance[0][0])
if flt(balance) < 0:
msgprint(_("Negative balance is not allowed for account ") + self.doc.account,
raise_exception=1)
def check_negative_balance(account, adv_adj=False):
if not adv_adj and account:
account_details = webnotes.conn.get_value("Account", account,
["allow_negative_balance", "debit_or_credit"], as_dict=True)
if not account_details["allow_negative_balance"]:
balance = webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry`
where account = %s""", account)
balance = account_details["debit_or_credit"] == "Debit" and \
flt(balance[0][0]) or -1*flt(balance[0][0])
if flt(balance) < 0:
webnotes.throw(_("Negative balance is not allowed for account ") + account)
def update_outstanding_amt(self):
# get final outstanding amt
bal = flt(webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry`
where against_voucher=%s and against_voucher_type=%s and account = %s
and ifnull(is_cancelled,'No') = 'No'""", (self.doc.against_voucher,
self.doc.against_voucher_type, self.doc.account))[0][0] or 0.0)
def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
"""
if not adv_adj:
acc_frozen_upto = webnotes.conn.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
bde_auth_role = webnotes.conn.get_value( 'Accounts Settings', None,'bde_auth_role')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not bde_auth_role in webnotes.user.get_roles():
webnotes.throw(_("You are not authorized to do/modify back dated entries before ")
+ getdate(acc_frozen_upto).strftime('%d-%m-%Y'))
if self.doc.against_voucher_type == 'Purchase Invoice':
def update_outstanding_amt(account, against_voucher_type, against_voucher, on_cancel=False):
# get final outstanding amt
bal = flt(webnotes.conn.sql("""select sum(debit) - sum(credit) from `tabGL Entry`
where against_voucher_type=%s and against_voucher=%s and account = %s""",
(against_voucher_type, against_voucher, account))[0][0] or 0.0)
if against_voucher_type == 'Purchase Invoice':
bal = -bal
elif against_voucher_type == "Journal Voucher":
against_voucher_amount = flt(webnotes.conn.sql("""select sum(debit) - sum(credit)
from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s
and account = %s""", (against_voucher, account))[0][0])
bal = against_voucher_amount + bal
if against_voucher_amount < 0:
bal = -bal
elif self.doc.against_voucher_type == "Journal Voucher":
against_voucher_amount = flt(webnotes.conn.sql("""select sum(debit) - sum(credit)
from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s
and account = %s""", (self.doc.against_voucher, self.doc.account))[0][0])
# Validation : Outstanding can not be negative
if bal < 0 and not on_cancel:
webnotes.throw(_("Outstanding for Voucher ") + against_voucher + _(" will become ") +
fmt_money(bal) + _(". Outstanding cannot be less than zero. \
Please match exact outstanding."))
# Update outstanding amt on against voucher
if against_voucher_type in ["Sales Invoice", "Purchase Invoice"]:
webnotes.conn.sql("update `tab%s` set outstanding_amount=%s where name='%s'" %
(against_voucher_type, bal, against_voucher))
bal = against_voucher_amount + bal
if against_voucher_amount < 0:
bal = -bal
# Validation : Outstanding can not be negative
if bal < 0 and self.doc.is_cancelled == 'No':
msgprint(_("Outstanding for Voucher ") + self.doc.against_voucher +
_(" will become ") + fmt_money(bal) + _(". Outstanding cannot be less than zero. \
Please match exact outstanding."), raise_exception=1)
# Update outstanding amt on against voucher
if self.doc.against_voucher_type in ["Sales Invoice", "Purchase Invoice"]:
webnotes.conn.sql("update `tab%s` set outstanding_amount=%s where name='%s'"%
(self.doc.against_voucher_type, bal, self.doc.against_voucher))
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)

View File

@ -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",

View File

@ -7,7 +7,7 @@ import webnotes
from webnotes.utils import cint, cstr, flt, fmt_money, formatdate, getdate
from webnotes.model.doc import addchild
from webnotes.model.bean import getlist
from webnotes import msgprint
from webnotes import msgprint, _
from setup.utils import get_company_currency
from controllers.accounts_controller import AccountsController
@ -49,7 +49,7 @@ class DocType(AccountsController):
from accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doc.doctype, self.doc.name, "against_jv")
self.make_gl_entries(cancel=1)
self.make_gl_entries(1)
def on_trash(self):
pass
@ -255,7 +255,7 @@ class DocType(AccountsController):
"against_voucher": d.against_voucher or d.against_invoice or d.against_jv,
"remarks": self.doc.remark,
"cost_center": d.cost_center
}, cancel)
})
)
if gl_map:
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj)
@ -452,4 +452,4 @@ def get_outstanding(args):
return {
"debit": flt(webnotes.conn.get_value("Purchase Invoice", args["docname"],
"outstanding_amount"))
}
}

View File

@ -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 = [
[{

View File

@ -21,7 +21,7 @@ class DocType:
def get_voucher_details(self):
total_amount = webnotes.conn.sql("""select sum(%s) from `tabGL Entry`
where voucher_type = %s and voucher_no = %s
and account = %s and ifnull(is_cancelled, 'No') = 'No'""" %
and account = %s""" %
(self.doc.account_type, '%s', '%s', '%s'),
(self.doc.voucher_type, self.doc.voucher_no, self.doc.account))
@ -29,7 +29,7 @@ class DocType:
reconciled_payment = webnotes.conn.sql("""
select sum(ifnull(%s, 0)) - sum(ifnull(%s, 0)) from `tabGL Entry` where
against_voucher = %s and voucher_no != %s
and account = %s and ifnull(is_cancelled, 'No') = 'No'""" %
and account = %s""" %
((self.doc.account_type == 'debit' and 'credit' or 'debit'), self.doc.account_type,
'%s', '%s', '%s'), (self.doc.voucher_no, self.doc.voucher_no, self.doc.account))
@ -135,7 +135,6 @@ def gl_entry_details(doctype, txt, searchfield, start, page_len, filters):
where gle.account = '%(acc)s'
and gle.voucher_type = '%(dt)s'
and gle.voucher_no like '%(txt)s'
and ifnull(gle.is_cancelled, 'No') = 'No'
and (ifnull(gle.against_voucher, '') = ''
or ifnull(gle.against_voucher, '') = gle.voucher_no )
and ifnull(gle.%(account_type)s, 0) > 0
@ -143,8 +142,7 @@ def gl_entry_details(doctype, txt, searchfield, start, page_len, filters):
from `tabGL Entry`
where against_voucher_type = '%(dt)s'
and against_voucher = gle.voucher_no
and voucher_no != gle.voucher_no
and ifnull(is_cancelled, 'No') = 'No')
and voucher_no != gle.voucher_no)
!= abs(ifnull(gle.debit, 0) - ifnull(gle.credit, 0)
)
%(mcond)s

View File

@ -3,178 +3,101 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import cstr, flt, getdate
from webnotes.model import db_exists
from webnotes.model.doc import Document
from webnotes.model.bean import copy_doclist
from webnotes.model.code import get_obj
from webnotes import msgprint
from webnotes import msgprint, _
from controllers.accounts_controller import AccountsController
class DocType:
class DocType(AccountsController):
def __init__(self,d,dl):
self.doc, self.doclist = d, dl
self.td, self.tc = 0, 0
self.year_start_date = ''
self.year_end_date = ''
def validate(self):
self.validate_account_head()
self.validate_posting_date()
self.validate_pl_balances()
def on_submit(self):
self.make_gl_entries()
def on_cancel(self):
webnotes.conn.sql("""delete from `tabGL Entry`
where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.doc.name)
def validate_account_head(self):
acc_det = webnotes.conn.sql("select debit_or_credit, is_pl_account, group_or_ledger, company \
from `tabAccount` where name = '%s'" % (self.doc.closing_account_head))
# Account should be under liability
if cstr(acc_det[0][0]) != 'Credit' or cstr(acc_det[0][1]) != 'No':
msgprint("Account: %s must be created under 'Source of Funds'" % self.doc.closing_account_head)
raise Exception
# Account must be a ledger
if cstr(acc_det[0][2]) != 'Ledger':
msgprint("Account %s must be a ledger" % self.doc.closing_account_head)
raise Exception
# Account should belong to company selected
if cstr(acc_det[0][3]) != self.doc.company:
msgprint("Account %s does not belong to Company %s ." % (self.doc.closing_account_head, self.doc.company))
raise Exception
debit_or_credit, is_pl_account = webnotes.conn.get_value("Account",
self.doc.closing_account_head, ["debit_or_credit", "is_pl_account"])
if debit_or_credit != 'Credit' or is_pl_account != 'No':
webnotes.throw(_("Account") + ": " + self.doc.closing_account_head +
_("must be a Liability account"))
def validate_posting_date(self):
yr = webnotes.conn.sql("""select year_start_date, adddate(year_start_date, interval 1 year)
from `tabFiscal Year` where name=%s""", (self.doc.fiscal_year, ))
self.year_start_date = yr and yr[0][0] or ''
self.year_end_date = yr and yr[0][1] or ''
# Posting Date should be within closing year
if getdate(self.doc.posting_date) < getdate(self.year_start_date) or getdate(self.doc.posting_date) > getdate(self.year_end_date):
msgprint("Posting Date should be within Closing Fiscal Year")
raise Exception
from accounts.utils import get_fiscal_year
self.year_start_date = get_fiscal_year(self.doc.posting_date)[1]
# Period Closing Entry
pce = webnotes.conn.sql("select name from `tabPeriod Closing Voucher` \
where posting_date > '%s' and fiscal_year = '%s' and docstatus = 1" \
% (self.doc.posting_date, self.doc.fiscal_year))
pce = webnotes.conn.sql("""select name from `tabPeriod Closing Voucher`
where posting_date > %s and fiscal_year = %s and docstatus = 1""",
(self.doc.posting_date, self.doc.fiscal_year))
if pce and pce[0][0]:
msgprint("Another Period Closing Entry: %s has been made after posting date: %s"\
% (cstr(pce[0][0]), self.doc.posting_date))
raise Exception
webnotes.throw(_("Another Period Closing Entry") + ": " + cstr(pce[0][0]) +
_("has been made after posting date") + ": " + self.doc.posting_date)
def validate_pl_balances(self):
income_bal = webnotes.conn.sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \
from `tabGL Entry` t1, tabAccount t2 where t1.account = t2.name \
and t1.posting_date between '%s' and '%s' and t2.debit_or_credit = 'Credit' \
and t2.group_or_ledger = 'Ledger' and t2.is_pl_account = 'Yes' and t2.docstatus < 2 \
and t2.company = '%s'" % (self.year_start_date, self.doc.posting_date, self.doc.company))
income_bal = webnotes.conn.sql("""
select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0))
from `tabGL Entry` t1, tabAccount t2
where t1.account = t2.name and t1.posting_date between %s and %s
and t2.debit_or_credit = 'Credit' and t2.is_pl_account = 'Yes'
and t2.docstatus < 2 and t2.company = %s""",
(self.year_start_date, self.doc.posting_date, self.doc.company))
expense_bal = webnotes.conn.sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \
from `tabGL Entry` t1, tabAccount t2 where t1.account = t2.name \
and t1.posting_date between '%s' and '%s' and t2.debit_or_credit = 'Debit' \
and t2.group_or_ledger = 'Ledger' and t2.is_pl_account = 'Yes' and t2.docstatus < 2 \
and t2.company = '%s'" % (self.year_start_date, self.doc.posting_date, self.doc.company))
expense_bal = webnotes.conn.sql("""
select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0))
from `tabGL Entry` t1, tabAccount t2
where t1.account = t2.name and t1.posting_date between %s and %s
and t2.debit_or_credit = 'Debit' and t2.is_pl_account = 'Yes'
and t2.docstatus < 2 and t2.company=%s""",
(self.year_start_date, self.doc.posting_date, self.doc.company))
income_bal = income_bal and income_bal[0][0] or 0
expense_bal = expense_bal and expense_bal[0][0] or 0
if not income_bal and not expense_bal:
msgprint("Both Income and Expense balances are zero. No Need to make Period Closing Entry.")
raise Exception
webnotes.throw(_("Both Income and Expense balances are zero. \
No Need to make Period Closing Entry."))
def get_pl_balances(self, d_or_c):
"""Get account (pl) specific balance"""
acc_bal = webnotes.conn.sql("select t1.account, sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \
from `tabGL Entry` t1, `tabAccount` t2 where t1.account = t2.name and t2.group_or_ledger = 'Ledger' \
and ifnull(t2.is_pl_account, 'No') = 'Yes' and ifnull(is_cancelled, 'No') = 'No' \
and t2.debit_or_credit = '%s' and t2.docstatus < 2 and t2.company = '%s' \
and t1.posting_date between '%s' and '%s' group by t1.account " \
% (d_or_c, self.doc.company, self.year_start_date, self.doc.posting_date))
return acc_bal
def get_pl_balances(self):
"""Get balance for pl accounts"""
return webnotes.conn.sql("""
select t1.account, sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) as balance
from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and ifnull(t2.is_pl_account, 'No') = 'Yes'
and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s
group by t1.account
""", (self.doc.company, self.year_start_date, self.doc.posting_date), as_dict=1)
def make_gl_entries(self, acc_det):
for a in acc_det:
if flt(a[1]):
fdict = {
'account': a[0],
'cost_center': '',
'against': '',
'debit': flt(a[1]) < 0 and -1*flt(a[1]) or 0,
'credit': flt(a[1]) > 0 and flt(a[1]) or 0,
'remarks': self.doc.remarks,
'voucher_type': self.doc.doctype,
'voucher_no': self.doc.name,
'transaction_date': self.doc.transaction_date,
'posting_date': self.doc.posting_date,
'fiscal_year': self.doc.fiscal_year,
'against_voucher': '',
'against_voucher_type': '',
'company': self.doc.company,
'is_opening': 'No',
'aging_date': self.doc.posting_date
}
def make_gl_entries(self):
gl_entries = []
net_pl_balance = 0
pl_accounts = self.get_pl_balances()
for acc in pl_accounts:
if flt(acc.balance):
gl_entries.append(self.get_gl_dict({
"account": acc.account,
"debit": abs(flt(acc.balance)) if flt(acc.balance) < 0 else 0,
"credit": abs(flt(acc.balance)) if flt(acc.balance) > 0 else 0,
}))
self.save_entry(fdict)
net_pl_balance += flt(acc.balance)
def save_entry(self, fdict, is_cancel = 'No'):
# Create new GL entry object and map values
le = Document('GL Entry')
for k in fdict:
le.fields[k] = fdict[k]
le_obj = get_obj(doc=le)
# validate except on_cancel
if is_cancel == 'No':
le_obj.validate()
if net_pl_balance:
gl_entries.append(self.get_gl_dict({
"account": self.doc.closing_account_head,
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0
}))
# update total debit / credit except on_cancel
self.td += flt(le.credit)
self.tc += flt(le.debit)
# save
le.save(1)
le_obj.on_update(adv_adj = '', cancel = '')
def validate(self):
# validate account head
self.validate_account_head()
# validate posting date
self.validate_posting_date()
# check if pl balance:
self.validate_pl_balances()
def on_submit(self):
# Makes closing entries for Expense Account
in_acc_det = self.get_pl_balances('Credit')
self.make_gl_entries(in_acc_det)
# Makes closing entries for Expense Account
ex_acc_det = self.get_pl_balances('Debit')
self.make_gl_entries(ex_acc_det)
# Makes Closing entry for Closing Account Head
bal = self.tc - self.td
self.make_gl_entries([[self.doc.closing_account_head, flt(bal)]])
def on_cancel(self):
# get all submit entries of current closing entry voucher
gl_entries = webnotes.conn.sql("select account, debit, credit from `tabGL Entry` where voucher_type = 'Period Closing Voucher' and voucher_no = '%s' and ifnull(is_cancelled, 'No') = 'No'" % (self.doc.name))
# Swap Debit & Credit Column and make gl entry
for gl in gl_entries:
fdict = {'account': gl[0], 'cost_center': '', 'against': '', 'debit': flt(gl[2]), 'credit' : flt(gl[1]), 'remarks': "cancelled", 'voucher_type': self.doc.doctype, 'voucher_no': self.doc.name, 'transaction_date': self.doc.transaction_date, 'posting_date': self.doc.posting_date, 'fiscal_year': self.doc.fiscal_year, 'against_voucher': '', 'against_voucher_type': '', 'company': self.doc.company, 'is_opening': 'No', 'aging_date': 'self.doc.posting_date'}
self.save_entry(fdict, is_cancel = 'Yes')
# Update is_cancelled = 'Yes' to all gl entries for current voucher
webnotes.conn.sql("update `tabGL Entry` set is_cancelled = 'Yes' where voucher_type = '%s' and voucher_no = '%s'" % (self.doc.doctype, self.doc.name))
from accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)

View File

@ -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"
}]

View File

@ -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()

View File

@ -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",

View File

@ -8,6 +8,7 @@ cur_frm.cscript.other_fname = "purchase_tax_details";
wn.provide("erpnext.accounts");
wn.require('app/accounts/doctype/purchase_taxes_and_charges_master/purchase_taxes_and_charges_master.js');
wn.require('app/buying/doctype/purchase_common/purchase_common.js');
wn.require('app/accounts/doctype/sales_invoice/pos.js');
erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
onload: function() {

View File

@ -61,19 +61,23 @@ class DocType(BuyingController):
"purchase_receipt_details")
def get_credit_to(self):
acc_head = webnotes.conn.sql("""select name, credit_days from `tabAccount`
where (name = %s or (master_name = %s and master_type = 'supplier'))
and docstatus != 2 and company = %s""",
(cstr(self.doc.supplier) + " - " + self.company_abbr,
self.doc.supplier, self.doc.company))
ret = {}
if acc_head and acc_head[0][0]:
ret['credit_to'] = acc_head[0][0]
if not self.doc.due_date:
ret['due_date'] = add_days(cstr(self.doc.posting_date), acc_head and cint(acc_head[0][1]) or 0)
elif not acc_head:
msgprint("%s does not have an Account Head in %s. You must first create it from the Supplier Master" % (self.doc.supplier, self.doc.company))
if self.doc.supplier:
acc_head = webnotes.conn.sql("""select name, credit_days from `tabAccount`
where (name = %s or (master_name = %s and master_type = 'supplier'))
and docstatus != 2 and company = %s""",
(cstr(self.doc.supplier) + " - " + self.company_abbr,
self.doc.supplier, self.doc.company))
if acc_head and acc_head[0][0]:
ret['credit_to'] = acc_head[0][0]
if not self.doc.due_date:
ret['due_date'] = add_days(cstr(self.doc.posting_date),
acc_head and cint(acc_head[0][1]) or 0)
elif not acc_head:
msgprint("%s does not have an Account Head in %s. \
You must first create it from the Supplier Master" % \
(self.doc.supplier, self.doc.company))
return ret
def set_supplier_defaults(self):
@ -84,14 +88,6 @@ class DocType(BuyingController):
super(DocType, self).get_advances(self.doc.credit_to,
"Purchase Invoice Advance", "advance_allocation_details", "debit")
def get_rate(self,arg):
return get_obj('Purchase Common').get_rate(arg,self)
def get_rate1(self,acc):
rate = webnotes.conn.sql("select tax_rate from `tabAccount` where name='%s'"%(acc))
ret={'add_tax_rate' :rate and flt(rate[0][0]) or 0 }
return ret
def check_active_purchase_items(self):
for d in getlist(self.doclist, 'entries'):
if d.item_code: # extra condn coz item_code is not mandatory in PV
@ -211,28 +207,29 @@ class DocType(BuyingController):
raise Exception
def set_against_expense_account(self):
auto_inventory_accounting = \
cint(webnotes.defaults.get_global_default("auto_inventory_accounting"))
auto_accounting_for_stock = cint(webnotes.defaults.get_global_default("auto_accounting_for_stock"))
if auto_inventory_accounting:
if auto_accounting_for_stock:
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
against_accounts = []
stock_items = self.get_stock_items()
for item in self.doclist.get({"parentfield": "entries"}):
if auto_inventory_accounting and item.item_code in self.stock_items:
if auto_accounting_for_stock and item.item_code in stock_items:
# in case of auto inventory accounting, against expense account is always
# Stock Received But Not Billed for a stock item
item.expense_head = item.cost_center = None
item.expense_head = stock_not_billed_account
item.cost_center = None
if stock_not_billed_account not in against_accounts:
against_accounts.append(stock_not_billed_account)
elif not item.expense_head:
msgprint(_("""Expense account is mandatory for item: """) + (item.item_code or item.item_name),
raise_exception=1)
msgprint(_("Expense account is mandatory for item") + ": " +
(item.item_code or item.item_name), raise_exception=1)
elif item.expense_head not in against_accounts:
# if no auto_inventory_accounting or not a stock item
# if no auto_accounting_for_stock or not a stock item
against_accounts.append(item.expense_head)
self.doc.against_expense_account = ",".join(against_accounts)
@ -313,9 +310,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 = []
@ -333,7 +329,7 @@ class DocType(BuyingController):
)
# tax table gl entries
valuation_tax = 0
valuation_tax = {}
for tax in self.doclist.get({"parentfield": "purchase_tax_details"}):
if tax.category in ("Total", "Valuation and Total") and flt(tax.tax_amount):
gl_entries.append(
@ -348,21 +344,22 @@ class DocType(BuyingController):
)
# accumulate valuation tax
if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount):
valuation_tax += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount)
if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount) \
and tax.cost_center:
valuation_tax.setdefault(tax.cost_center, 0)
valuation_tax[tax.cost_center] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount)
# item gl entries
stock_item_and_auto_inventory_accounting = False
if auto_inventory_accounting:
stock_account = self.get_company_default("stock_received_but_not_billed")
stock_item_and_auto_accounting_for_stock = False
stock_items = self.get_stock_items()
for item in self.doclist.get({"parentfield": "entries"}):
if auto_inventory_accounting and item.item_code in self.stock_items:
if auto_accounting_for_stock and item.item_code in stock_items:
if flt(item.valuation_rate):
# if auto inventory accounting enabled and stock item,
# then do stock related gl entries
# expense will be booked in sales invoice
stock_item_and_auto_inventory_accounting = True
stock_item_and_auto_accounting_for_stock = True
valuation_amt = (flt(item.amount, self.precision("amount", item)) +
flt(item.item_tax_amount, self.precision("item_tax_amount", item)) +
@ -370,7 +367,7 @@ class DocType(BuyingController):
gl_entries.append(
self.get_gl_dict({
"account": stock_account,
"account": item.expense_head,
"against": self.doc.credit_to,
"debit": valuation_amt,
"remarks": self.doc.remarks or "Accounting Entry for Stock"
@ -389,18 +386,22 @@ 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"),
"against": self.doc.credit_to,
"credit": valuation_tax,
"remarks": self.doc.remarks or "Accounting Entry for Stock"
})
)
expenses_included_in_valuation = \
self.get_company_default("expenses_included_in_valuation")
for cost_center, amount in valuation_tax.items():
gl_entries.append(
self.get_gl_dict({
"account": expenses_included_in_valuation,
"cost_center": cost_center,
"against": self.doc.credit_to,
"credit": amount,
"remarks": self.doc.remarks or "Accounting Entry for Stock"
})
)
# writeoff account includes petty difference in the invoice amount
# and the amount that is paid
@ -416,6 +417,7 @@ class DocType(BuyingController):
)
if gl_entries:
from accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2))
def on_cancel(self):
@ -455,4 +457,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)})

View File

@ -2,12 +2,13 @@
{
"creation": "2013-05-21 16:16:39",
"docstatus": 0,
"modified": "2013-08-09 14:45:35",
"modified": "2013-10-02 14:24:55",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_attach": 1,
"allow_import": 1,
"autoname": "naming_series:",
"doctype": "DocType",
"icon": "icon-file-text",

View File

@ -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])

View File

@ -4,6 +4,8 @@
//
//--------- ONLOAD -------------
wn.require("app/js/controllers/accounts.js");
cur_frm.cscript.onload = function(doc, cdt, cdn) {
}
@ -134,20 +136,6 @@ cur_frm.fields_dict['purchase_tax_details'].grid.get_field("cost_center").get_qu
}
}
cur_frm.cscript.account_head = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if(!d.charge_type && d.account_head){
alert("Please select Charge Type first");
validated = false;
d.account_head = '';
}
else if(d.account_head && d.charge_type) {
arg = "{'charge_type' : '" + d.charge_type + "', 'account_head' : '" + d.account_head + "'}";
return get_server_fields('get_rate', arg, 'purchase_tax_details', doc, cdt, cdn, 1);
}
refresh_field('account_head',d.name,'purchase_tax_details');
}
cur_frm.cscript.rate = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if(!d.charge_type && d.rate) {

View File

@ -14,9 +14,4 @@ from webnotes.model.code import get_obj
class DocType:
def __init__(self, doc, doclist=[]):
self.doc = doc
self.doclist = doclist
# Get Tax Rate if account type is Tax
# ===================================================================
def get_rate(self, arg):
return get_obj('Purchase Common').get_rate(arg, self)
self.doclist = doclist

View File

@ -7,7 +7,7 @@ erpnext.POS = Class.extend({
this.frm = frm;
this.wrapper.html('<div class="container">\
<div class="row">\
<div class="customer-area col-sm-3 col-xs-6"></div>\
<div class="party-area col-sm-3 col-xs-6"></div>\
<div class="barcode-area col-sm-3 col-xs-6"></div>\
<div class="search-area col-sm-3 col-xs-6"></div>\
<div class="item-group-area col-sm-3 col-xs-6"></div>\
@ -71,7 +71,18 @@ erpnext.POS = Class.extend({
</div>\
</div>\
</div></div>');
if (wn.meta.has_field(cur_frm.doc.doctype, "customer")) {
this.party = "Customer";
this.price_list = this.frm.doc.selling_price_list;
this.sales_or_purchase = "Sales";
}
else if (wn.meta.has_field(cur_frm.doc.doctype, "supplier")) {
this.party = "Supplier";
this.price_list = this.frm.doc.buying_price_list;
this.sales_or_purchase = "Purchase";
}
this.make();
var me = this;
@ -88,28 +99,29 @@ erpnext.POS = Class.extend({
});
},
make: function() {
this.make_customer();
this.make_party();
this.make_item_group();
this.make_search();
this.make_barcode();
this.make_item_list();
},
make_customer: function() {
make_party: function() {
var me = this;
this.customer = wn.ui.form.make_control({
this.party_field = wn.ui.form.make_control({
df: {
"fieldtype": "Link",
"options": "Customer",
"label": "Customer",
"fieldname": "pos_customer",
"placeholder": "Customer"
"options": this.party,
"label": this.party,
"fieldname": "pos_party",
"placeholder": this.party
},
parent: this.wrapper.find(".customer-area")
parent: this.wrapper.find(".party-area")
});
this.customer.make_input();
this.customer.$input.on("change", function() {
if(!me.customer.autocomplete_open)
wn.model.set_value("Sales Invoice", me.frm.docname, "customer", this.value);
this.party_field.make_input();
this.party_field.$input.on("change", function() {
if(!me.party_field.autocomplete_open)
wn.model.set_value(me.frm.doctype, me.frm.docname,
me.party.toLowerCase(), this.value);
});
},
make_item_group: function() {
@ -120,7 +132,7 @@ erpnext.POS = Class.extend({
"options": "Item Group",
"label": "Item Group",
"fieldname": "pos_item_group",
"placeholder": "Filter by Item Group"
"placeholder": "Item Group"
},
parent: this.wrapper.find(".item-group-area")
});
@ -138,7 +150,7 @@ erpnext.POS = Class.extend({
"options": "Item",
"label": "Item",
"fieldname": "pos_item",
"placeholder": "Select Item"
"placeholder": "Item"
},
parent: this.wrapper.find(".search-area")
});
@ -155,7 +167,7 @@ erpnext.POS = Class.extend({
"fieldtype": "Data",
"label": "Barcode",
"fieldname": "pos_barcode",
"placeholder": "Select Barcode"
"placeholder": "Barcode"
},
parent: this.wrapper.find(".barcode-area")
});
@ -171,7 +183,8 @@ erpnext.POS = Class.extend({
wn.call({
method: 'accounts.doctype.sales_invoice.pos.get_items',
args: {
price_list: cur_frm.doc.selling_price_list,
sales_or_purchase: this.sales_or_purchase,
price_list: this.price_list,
item_group: this.item_group.$input.val(),
item: this.search.$input.val()
},
@ -200,15 +213,18 @@ erpnext.POS = Class.extend({
});
// if form is local then allow this function
if (cur_frm.doc.docstatus===0) {
$("div.pos-item").on("click", function() {
if(!cur_frm.doc.customer) {
msgprint("Please select customer first.");
$(me.wrapper).find("div.pos-item").on("click", function() {
if(me.frm.doc.docstatus==0) {
if(!me.frm.doc[me.party.toLowerCase()] && ((me.frm.doctype == "Quotation" &&
me.frm.doc.quotation_to == "Customer")
|| me.frm.doctype != "Quotation")) {
msgprint("Please select " + me.party + " first.");
return;
}
me.add_to_cart($(this).attr("data-item_code"));
});
}
else
me.add_to_cart($(this).attr("data-item_code"));
}
});
}
});
},
@ -217,12 +233,12 @@ erpnext.POS = Class.extend({
var caught = false;
// get no_of_items
no_of_items = me.wrapper.find("#cart tbody").length;
var no_of_items = me.wrapper.find("#cart tbody tr").length;
// check whether the item is already added
if (no_of_items != 0) {
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice"), function(i, d) {
$.each(wn.model.get_children(this.frm.doctype + " Item", this.frm.doc.name,
this.frm.cscript.fname, this.frm.doctype), function(i, d) {
if (d.item_code == item_code)
caught = true;
});
@ -233,15 +249,16 @@ erpnext.POS = Class.extend({
me.update_qty(item_code, 1);
}
else {
var child = wn.model.add_child(me.frm.doc, "Sales Invoice Item", "entries");
var child = wn.model.add_child(me.frm.doc, this.frm.doctype + " Item",
this.frm.cscript.fname);
child.item_code = item_code;
me.frm.cscript.item_code(me.frm.doc, child.doctype, child.name);
}
},
update_qty: function(item_code, qty, textbox_qty) {
var me = this;
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice"), function(i, d) {
$.each(wn.model.get_children(this.frm.doctype + " Item", this.frm.doc.name,
this.frm.cscript.fname, this.frm.doctype), function(i, d) {
if (d.item_code == item_code) {
if (textbox_qty) {
if (qty == 0 && d.item_code == item_code)
@ -259,14 +276,24 @@ erpnext.POS = Class.extend({
},
refresh: function() {
var me = this;
this.customer.set_input(this.frm.doc.customer);
this.party_field.set_input(this.frm.doc[this.party.toLowerCase()]);
this.barcode.set_input("");
// add items
var $items = me.wrapper.find("#cart tbody").empty();
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice"), function(i, d) {
$.each(wn.model.get_children(this.frm.doctype + " Item", this.frm.doc.name,
this.frm.cscript.fname, this.frm.doctype), function(i, d) {
if (me.sales_or_purchase == "Sales") {
item_amount = d.export_amount;
rate = d.export_rate;
}
else {
item_amount = d.import_amount;
rate = d.import_rate;
}
$(repl('<tr id="%(item_code)s" data-selected="false">\
<td>%(item_code)s%(item_name)s</td>\
<td><input type="text" value="%(qty)s" \
@ -277,16 +304,16 @@ erpnext.POS = Class.extend({
item_code: d.item_code,
item_name: d.item_name===d.item_code ? "" : ("<br>" + d.item_name),
qty: d.qty,
rate: format_currency(d.ref_rate, cur_frm.doc.price_list_currency),
amount: format_currency(d.export_amount, cur_frm.doc.price_list_currency)
rate: format_currency(rate, me.frm.doc.currency),
amount: format_currency(item_amount, me.frm.doc.currency)
}
)).appendTo($items);
});
// taxes
var taxes = wn.model.get_children("Sales Taxes and Charges", this.frm.doc.name, "other_charges",
"Sales Invoice");
$(".tax-table")
var taxes = wn.model.get_children(this.sales_or_purchase + " Taxes and Charges",
this.frm.doc.name, this.frm.cscript.other_fname, this.frm.doctype);
$(this.wrapper).find(".tax-table")
.toggle((taxes && taxes.length) ? true : false)
.find("tbody").empty();
@ -297,30 +324,39 @@ erpnext.POS = Class.extend({
<tr>', {
description: d.description,
rate: d.rate,
tax_amount: format_currency(d.tax_amount, me.frm.doc.price_list_currency)
tax_amount: format_currency(flt(d.tax_amount)/flt(me.frm.doc.conversion_rate),
me.frm.doc.currency)
})).appendTo(".tax-table tbody");
});
// set totals
this.wrapper.find(".net-total").text(format_currency(this.frm.doc.net_total_export,
cur_frm.doc.price_list_currency));
this.wrapper.find(".grand-total").text(format_currency(this.frm.doc.grand_total_export,
cur_frm.doc.price_list_currency));
if (this.sales_or_purchase == "Sales") {
this.wrapper.find(".net-total").text(format_currency(this.frm.doc.net_total_export,
me.frm.doc.currency));
this.wrapper.find(".grand-total").text(format_currency(this.frm.doc.grand_total_export,
me.frm.doc.currency));
}
else {
this.wrapper.find(".net-total").text(format_currency(this.frm.doc.net_total_import,
me.frm.doc.currency));
this.wrapper.find(".grand-total").text(format_currency(this.frm.doc.grand_total_import,
me.frm.doc.currency));
}
// if form is local then only run all these functions
if (cur_frm.doc.docstatus===0) {
$("input.qty").on("focus", function() {
if (this.frm.doc.docstatus===0) {
$(this.wrapper).find("input.qty").on("focus", function() {
$(this).select();
});
// append quantity to the respective item after change from input box
$("input.qty").on("change", function() {
$(this.wrapper).find("input.qty").on("change", function() {
var item_code = $(this).closest("tr")[0].id;
me.update_qty(item_code, $(this).val(), true);
});
// on td click toggle the highlighting of row
$("#cart tbody tr td").on("click", function() {
$(this.wrapper).find("#cart tbody tr td").on("click", function() {
var row = $(this).closest("tr");
if (row.attr("data-selected") == "false") {
row.attr("class", "warning");
@ -335,20 +371,37 @@ erpnext.POS = Class.extend({
});
me.refresh_delete_btn();
cur_frm.pos.barcode.$input.focus();
this.barcode.$input.focus();
}
// if form is submitted & cancelled then disable all input box & buttons
if (cur_frm.doc.docstatus>=1 && cint(cur_frm.doc.is_pos)) {
me.wrapper.find('input, button').each(function () {
if (this.frm.doc.docstatus>=1) {
$(this.wrapper).find('input, button').each(function () {
$(this).prop('disabled', true);
});
$(".delete-items").hide();
$(".make-payment").hide();
$(this.wrapper).find(".delete-items").hide();
$(this.wrapper).find(".make-payment").hide();
}
else {
$(this.wrapper).find('input, button').each(function () {
$(this).prop('disabled', false);
});
$(this.wrapper).find(".make-payment").show();
}
// Show Make Payment button only in Sales Invoice
if (this.frm.doctype != "Sales Invoice")
$(this.wrapper).find(".make-payment").hide();
// If quotation to is not Customer then remove party
if (this.frm.doctype == "Quotation") {
this.party_field.$wrapper.remove();
if (this.frm.doc.quotation_to == "Customer")
this.make_party();
}
},
refresh_delete_btn: function() {
$(".delete-items").toggle($(".item-cart .warning").length ? true : false);
$(this.wrapper).find(".delete-items").toggle($(".item-cart .warning").length ? true : false);
},
add_item_thru_barcode: function() {
var me = this;
@ -370,32 +423,32 @@ erpnext.POS = Class.extend({
remove_selected_item: function() {
var me = this;
var selected_items = [];
var no_of_items = $("#cart tbody tr").length;
var no_of_items = $(this.wrapper).find("#cart tbody tr").length;
for(var x=0; x<=no_of_items - 1; x++) {
var row = $("#cart tbody tr:eq(" + x + ")");
var row = $(this.wrapper).find("#cart tbody tr:eq(" + x + ")");
if(row.attr("data-selected") == "true") {
selected_items.push(row.attr("id"));
}
}
var child = wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
"Sales Invoice");
var child = wn.model.get_children(this.frm.doctype + " Item", this.frm.doc.name,
this.frm.cscript.fname, this.frm.doctype);
$.each(child, function(i, d) {
for (var i in selected_items) {
if (d.item_code == selected_items[i]) {
// cur_frm.fields_dict["entries"].grid.grid_rows[d.idx].remove();
wn.model.clear_doc(d.doctype, d.name);
}
}
});
cur_frm.fields_dict["entries"].grid.refresh();
cur_frm.script_manager.trigger("calculate_taxes_and_totals");
this.frm.fields_dict[this.frm.cscript.fname].grid.refresh();
this.frm.script_manager.trigger("calculate_taxes_and_totals");
me.frm.dirty();
me.refresh();
},
make_payment: function() {
var me = this;
var no_of_items = $("#cart tbody tr").length;
var no_of_items = $(this.wrapper).find("#cart tbody tr").length;
var mode_of_payment = [];
if (no_of_items == 0)
@ -423,15 +476,15 @@ erpnext.POS = Class.extend({
"total_amount": $(".grand-total").text()
});
dialog.show();
cur_frm.pos.barcode.$input.focus();
me.barcode.$input.focus();
dialog.get_input("total_amount").prop("disabled", true);
dialog.fields_dict.pay.input.onclick = function() {
cur_frm.set_value("mode_of_payment", dialog.get_values().mode_of_payment);
cur_frm.set_value("paid_amount", dialog.get_values().total_amount);
cur_frm.cscript.mode_of_payment(cur_frm.doc);
cur_frm.save();
me.frm.set_value("mode_of_payment", dialog.get_values().mode_of_payment);
me.frm.set_value("paid_amount", dialog.get_values().total_amount);
me.frm.cscript.mode_of_payment(me.frm.doc);
me.frm.save();
dialog.hide();
me.refresh();
};

View File

@ -3,17 +3,21 @@
from __future__ import unicode_literals
import webnotes
from webnotes import msgprint
@webnotes.whitelist()
def get_items(price_list, item=None, item_group=None):
def get_items(price_list, sales_or_purchase, item=None, item_group=None):
condition = ""
if sales_or_purchase == "Sales":
condition = "i.is_sales_item='Yes'"
else:
condition = "i.is_purchase_item='Yes'"
if item_group and item_group != "All Item Groups":
condition = "and i.item_group='%s'" % item_group
condition += " and i.item_group='%s'" % item_group
if item:
condition = "and i.name='%s'" % item
condition += " and i.name='%s'" % item
return webnotes.conn.sql("""select i.name, i.item_name, i.image,
pl_items.ref_rate, pl_items.currency
@ -24,13 +28,18 @@ def get_items(price_list, item=None, item_group=None):
ON
pl_items.item_code=i.name
where
i.is_sales_item='Yes'%s""" % ('%s', condition), (price_list), as_dict=1)
%s""" % ('%s', condition), (price_list), as_dict=1)
@webnotes.whitelist()
def get_item_from_barcode(barcode):
return webnotes.conn.sql("""select name from `tabItem` where barcode=%s""",
(barcode), as_dict=1)
@webnotes.whitelist()
def get_item_from_serial_no(serial_no):
return webnotes.conn.sql("""select name, item_code from `tabSerial No` where
name=%s""", (serial_no), as_dict=1)
@webnotes.whitelist()
def get_mode_of_payment():
return webnotes.conn.sql("""select name from `tabMode of Payment`""", as_dict=1)

View File

@ -1,15 +0,0 @@
.pos-item {
height: 200px;
overflow: hidden;
cursor: pointer;
padding-left: 5px !important;
padding-right: 5px !important;
}
.pos-bill {
padding: 20px 5px;
font-family: Monospace;
border: 1px solid #eee;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
}

View File

@ -29,9 +29,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
// toggle to pos view if is_pos is 1 in user_defaults
if ((cint(wn.defaults.get_user_defaults("is_pos"))===1 || cur_frm.doc.is_pos) &&
cint(wn.defaults.get_user_defaults("fs_pos_view"))===1) {
this.frm.set_value("is_pos", 1);
this.is_pos();
cur_frm.cscript.toggle_pos(true);
if(this.frm.doc.__islocal && !this.frm.doc.amended_from) {
this.frm.set_value("is_pos", 1);
this.is_pos(function() {cur_frm.cscript.toggle_pos(true);});
}
}
// if document is POS then change default print format to "POS Invoice"
@ -78,14 +79,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
cur_frm.add_custom_button('Make Payment Entry', cur_frm.cscript.make_bank_voucher);
}
if (doc.docstatus===0) {
// Show buttons only when pos view is active
if (doc.docstatus===0 && !this.pos_active) {
cur_frm.cscript.sales_order_btn();
cur_frm.cscript.delivery_note_btn();
}
// Show POS button only if it enabled from features setup
if(cint(sys_defaults.fs_pos_view)===1)
cur_frm.cscript.pos_btn();
},
sales_order_btn: function() {
@ -124,62 +122,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
});
});
},
pos_btn: function() {
if(cur_frm.$pos_btn)
cur_frm.$pos_btn.remove();
if(!cur_frm.pos_active) {
var btn_label = wn._("POS View"),
icon = "icon-desktop";
cur_frm.cscript.sales_order_btn();
cur_frm.cscript.delivery_note_btn();
} else {
var btn_label = wn._("Invoice View"),
icon = "icon-file-text";
if (cur_frm.doc.docstatus===0) {
this.$delivery_note_btn.remove();
this.$sales_order_btn.remove();
}
}
cur_frm.$pos_btn = cur_frm.add_custom_button(btn_label, function() {
cur_frm.cscript.toggle_pos();
cur_frm.cscript.pos_btn();
}, icon);
},
toggle_pos: function(show) {
if (!this.frm.doc.selling_price_list)
msgprint(wn._("Please select Price List"))
else {
if((show===true && cur_frm.pos_active) || (show===false && !cur_frm.pos_active)) return;
// make pos
if(!cur_frm.pos) {
cur_frm.layout.add_view("pos");
cur_frm.pos = new erpnext.POS(cur_frm.layout.views.pos, cur_frm);
}
// toggle view
cur_frm.layout.set_view(cur_frm.pos_active ? "" : "pos");
cur_frm.pos_active = !cur_frm.pos_active;
// refresh
if(cur_frm.pos_active)
cur_frm.pos.refresh();
}
},
tc_name: function() {
this.get_terms();
},
is_pos: function() {
is_pos: function(callback_fn) {
cur_frm.cscript.hide_fields(this.frm.doc);
if(cint(this.frm.doc.is_pos)) {
if(!this.frm.doc.company) {
this.frm.set_value("is_pos", 0);
@ -192,11 +141,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
callback: function(r) {
if(!r.exc) {
me.frm.script_manager.trigger("update_stock");
if(callback_fn) callback_fn()
}
}
});
}
}
},
debit_to: function() {
@ -385,7 +336,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: {

View File

@ -82,15 +82,13 @@ class DocType(SellingController):
def on_submit(self):
if cint(self.doc.update_stock) == 1:
self.update_stock_ledger(update_stock=1)
self.update_serial_nos()
self.update_stock_ledger()
else:
# Check for Approving Authority
if not self.doc.recurring_id:
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,8 +109,7 @@ class DocType(SellingController):
def on_cancel(self):
if cint(self.doc.update_stock) == 1:
self.update_stock_ledger(update_stock = -1)
self.update_serial_nos(cancel = True)
self.update_stock_ledger()
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_stop_sales_order(self)
@ -196,9 +193,7 @@ class DocType(SellingController):
pos = get_pos_settings(self.doc.company)
if pos:
self.doc.conversion_rate = flt(pos.conversion_rate)
if not for_validate:
if not for_validate and not self.doc.customer:
self.doc.customer = pos.customer
self.set_customer_defaults()
@ -269,13 +264,7 @@ class DocType(SellingController):
def get_adj_percent(self, arg=''):
"""Fetch ref rate from item master as per selected price list"""
get_obj('Sales Common').get_adj_percent(self)
def get_rate(self,arg):
"""Get tax rate if account type is tax"""
get_obj('Sales Common').get_rate(arg)
get_obj('Sales Common').get_adj_percent(self)
def get_comm_rate(self, sales_partner):
"""Get Commission rate of Sales Partner"""
@ -526,41 +515,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 +550,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 +595,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:
@ -993,8 +957,7 @@ def make_delivery_note(source_name, target_doclist=None):
"doctype": "Delivery Note Item",
"field_map": {
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
"parent": "against_sales_invoice",
"serial_no": "serial_no"
},
"postprocess": update_item

View File

@ -2,12 +2,13 @@
{
"creation": "2013-05-24 19:29:05",
"docstatus": 0,
"modified": "2013-09-01 05:26:13",
"modified": "2013-10-03 18:54:31",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_attach": 1,
"allow_import": 1,
"autoname": "naming_series:",
"default_print_format": "Standard",
"doctype": "DocType",
@ -411,7 +412,7 @@
"doctype": "DocField",
"fieldname": "other_charges",
"fieldtype": "Table",
"label": "Taxes and Charges1",
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges",

View File

@ -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 \
@ -642,14 +641,19 @@ 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
# if yearly, test 1 repetition, else test 5 repetitions
count = 1 if (no_of_months == 12) else 5
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
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
from stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item()
serial_nos = get_serial_nos(se.doclist[1].serial_no)
@ -670,7 +674,7 @@ class TestSalesInvoice(unittest.TestCase):
return si
def test_serialized_cancel(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
from stock.doctype.serial_no.serial_no import get_serial_nos
si = self.test_serialized()
si.cancel()
@ -682,7 +686,7 @@ class TestSalesInvoice(unittest.TestCase):
"delivery_document_no"))
def test_serialize_status(self):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
from stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item()

View File

@ -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",

View File

@ -2,6 +2,9 @@
// License: GNU General Public License v3. See license.txt
//--------- ONLOAD -------------
wn.require("app/js/controllers/accounts.js");
cur_frm.cscript.onload = function(doc, cdt, cdn) {
if(doc.doctype === "Sales Taxes and Charges Master")
erpnext.add_for_territory();
@ -142,21 +145,6 @@ cur_frm.fields_dict['other_charges'].grid.get_field("cost_center").get_query = f
}
}
cur_frm.cscript.account_head = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if(!d.charge_type && d.account_head){
alert("Please select Charge Type first");
validated = false;
d.account_head = '';
}
else if(d.account_head && d.charge_type) {
arg = "{'charge_type' : '" + d.charge_type +"', 'account_head' : '" + d.account_head + "'}";
return get_server_fields('get_rate', arg, 'other_charges', doc, cdt, cdn, 1);
}
refresh_field('account_head',d.name,'other_charges');
}
cur_frm.cscript.rate = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if(!d.charge_type && d.rate) {

View File

@ -6,11 +6,7 @@ import webnotes
from webnotes.utils import cint
from webnotes.model.controller import DocListController
class DocType(DocListController):
def get_rate(self, arg):
from webnotes.model.code import get_obj
return get_obj('Sales Common').get_rate(arg)
class DocType(DocListController):
def validate(self):
if self.doc.is_default == 1:
webnotes.conn.sql("""update `tabSales Taxes and Charges Master` set is_default = 0

View File

@ -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)

View File

@ -15,7 +15,7 @@ def get_companies():
@webnotes.whitelist()
def get_children():
args = webnotes.form_dict
args = webnotes.local.form_dict
ctype, company = args['ctype'], args['comp']
# root

View File

@ -64,10 +64,10 @@ def upload():
data, start_idx = get_data(rows, company_abbr, rows[0][0])
except Exception, e:
err_msg = webnotes.message_log and "<br>".join(webnotes.message_log) or cstr(e)
err_msg = webnotes.local.message_log and "<br>".join(webnotes.local.message_log) or cstr(e)
messages.append("""<p style='color: red'>%s</p>""" % (err_msg or "No message"))
webnotes.errprint(webnotes.getTraceback())
webnotes.message_log = []
webnotes.local.message_log = []
return messages
return import_vouchers(common_values, data, start_idx, rows[0][0])
@ -104,7 +104,7 @@ def import_vouchers(common_values, data, start_idx, import_type):
if account.master_name:
map_fields(["against_sales_invoice:against_invoice",
"against_purhase_invoice:against_voucher",
"against_purchase_invoice:against_voucher",
"against_journal_voucher:against_jv"], d, detail.fields)
webnotes.conn.commit()
@ -117,11 +117,11 @@ def import_vouchers(common_values, data, start_idx, import_type):
d = data[i][0]
if import_type == "Voucher Import: Two Accounts" and flt(d.get("amount")) == 0:
webnotes.message_log = ["Amount not specified"]
webnotes.local.message_log = ["Amount not specified"]
raise Exception
elif import_type == "Voucher Import: Multiple Accounts" and \
(flt(d.get("total_debit")) == 0 or flt(d.get("total_credit")) == 0):
webnotes.message_log = ["Total Debit and Total Credit amount can not be zero"]
webnotes.local.message_log = ["Total Debit and Total Credit amount can not be zero"]
raise Exception
else:
d.posting_date = parse_date(d.posting_date)
@ -174,7 +174,7 @@ def import_vouchers(common_values, data, start_idx, import_type):
details.append(detail)
if not details:
webnotes.message_log = ["""No accounts found.
webnotes.local.message_log = ["""No accounts found.
If you entered accounts correctly, please check template once"""]
raise Exception
@ -193,12 +193,12 @@ def import_vouchers(common_values, data, start_idx, import_type):
webnotes.conn.commit()
except Exception, e:
webnotes.conn.rollback()
err_msg = webnotes.message_log and "<br>".join(webnotes.message_log) or cstr(e)
err_msg = webnotes.local.message_log and "<br>".join(webnotes.local.message_log) or cstr(e)
messages.append("""<p style='color: red'>[row #%s] %s failed: %s</p>"""
% ((start_idx + 1) + i, jv.name or "", err_msg or "No message"))
messages.append("<p style='color: red'>All transactions rolled back</p>")
webnotes.errprint(webnotes.getTraceback())
webnotes.message_log = []
webnotes.local.message_log = []
return messages

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -52,11 +52,10 @@ def get_stock_ledger_entries(filters):
query = """select item_code, voucher_type, voucher_no,
voucher_detail_no, posting_date, posting_time, stock_value,
warehouse, actual_qty as qty
from `tabStock Ledger Entry`
where ifnull(`is_cancelled`, "No") = "No" """
from `tabStock Ledger Entry`"""
if filters.get("company"):
query += """ and company=%(company)s"""
query += """ where company=%(company)s"""
query += " order by item_code desc, warehouse desc, posting_date desc, posting_time desc, name desc"

View File

@ -81,12 +81,12 @@ def get_tax_accounts(item_list, columns):
if account_head not in tax_accounts:
tax_accounts.append(account_head)
invoice = item_tax.setdefault(parent, {})
if item_wise_tax_detail:
try:
item_wise_tax_detail = json.loads(item_wise_tax_detail)
for item, tax_amount in item_wise_tax_detail.items():
invoice.setdefault(item, {})[account_head] = flt(tax_amount)
item_tax.setdefault(parent, {}).setdefault(item, {})[account_head] = \
flt(tax_amount[1])
except ValueError:
continue

View File

@ -9,7 +9,7 @@ def execute(filters=None):
if not filters: filters = {}
columns = get_columns()
last_col = len(columns)
item_list = get_items(filters)
item_tax, tax_accounts = get_tax_accounts(item_list, columns)
@ -21,7 +21,7 @@ def execute(filters=None):
for tax in tax_accounts:
row.append(item_tax.get(d.parent, {}).get(d.item_code, {}).get(tax, 0))
total_tax = sum(row[last_col:])
row += [total_tax, d.amount + total_tax]
@ -71,19 +71,19 @@ def get_tax_accounts(item_list, columns):
tax_details = webnotes.conn.sql("""select parent, account_head, item_wise_tax_detail
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
and docstatus = 1 and ifnull(account_head, '') != ''
and parent in (%s)""" % ', '.join(['%s']*len(item_list)), tuple([item.parent for item in item_list]))
and parent in (%s)""" % ', '.join(['%s']*len(item_list)),
tuple([item.parent for item in item_list]))
for parent, account_head, item_wise_tax_detail in tax_details:
if account_head not in tax_accounts:
tax_accounts.append(account_head)
invoice = item_tax.setdefault(parent, {})
if item_wise_tax_detail:
try:
item_wise_tax_detail = json.loads(item_wise_tax_detail)
for item, tax_amount in item_wise_tax_detail.items():
invoice.setdefault(item, {})[account_head] = flt(tax_amount)
item_tax.setdefault(parent, {}).setdefault(item, {})[account_head] = \
flt(tax_amount[1])
except ValueError:
continue

View File

@ -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':
@ -111,7 +108,7 @@ def get_balance_on(account=None, date=None):
@webnotes.whitelist()
def add_ac(args=None):
if not args:
args = webnotes.form_dict
args = webnotes.local.form_dict
args.pop("cmd")
ac = webnotes.bean(args)
@ -124,7 +121,7 @@ def add_ac(args=None):
@webnotes.whitelist()
def add_cc(args=None):
if not args:
args = webnotes.form_dict
args = webnotes.local.form_dict
args.pop("cmd")
cc = webnotes.bean(args)
@ -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,91 @@ def fix_total_debit_credit():
webnotes.conn.sql("""update `tabGL Entry` set %s = %s + %s
where voucher_type = %s and voucher_no = %s and %s > 0 limit 1""" %
(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
(d.diff, d.voucher_type, d.voucher_no))
(d.diff, d.voucher_type, d.voucher_no))
def get_stock_and_account_difference(account_list=None, posting_date=None):
from stock.utils import get_stock_balance_on
if not posting_date: posting_date = nowdate()
difference = {}
account_warehouse = dict(webnotes.conn.sql("""select name, master_name from tabAccount
where account_type = 'Warehouse' and ifnull(master_name, '') != ''
and name in (%s)""" % ', '.join(['%s']*len(account_list)), account_list))
for account, warehouse in account_warehouse.items():
account_balance = get_balance_on(account, posting_date)
stock_value = get_stock_balance_on(warehouse, posting_date)
if abs(flt(stock_value) - flt(account_balance)) > 0.005:
difference.setdefault(account, flt(stock_value) - flt(account_balance))
return difference
def validate_expense_against_budget(args):
args = webnotes._dict(args)
if webnotes.conn.get_value("Account", {"name": args.account, "is_pl_account": "Yes",
"debit_or_credit": "Debit"}):
budget = webnotes.conn.sql("""
select bd.budget_allocated, cc.distribution_id
from `tabCost Center` cc, `tabBudget Detail` bd
where cc.name=bd.parent and cc.name=%s and account=%s and bd.fiscal_year=%s
""", (args.cost_center, args.account, args.fiscal_year), as_dict=True)
if budget and budget[0].budget_allocated:
yearly_action, monthly_action = webnotes.conn.get_value("Company", args.company,
["yearly_bgt_flag", "monthly_bgt_flag"])
action_for = action = ""
if monthly_action in ["Stop", "Warn"]:
budget_amount = get_allocated_budget(budget[0].distribution_id,
args.posting_date, args.fiscal_year, budget[0].budget_allocated)
args["month_end_date"] = webnotes.conn.sql("select LAST_DAY(%s)",
args.posting_date)[0][0]
action_for, action = "Monthly", monthly_action
elif yearly_action in ["Stop", "Warn"]:
budget_amount = budget[0].budget_allocated
action_for, action = "Monthly", yearly_action
if action_for:
actual_expense = get_actual_expense(args)
if actual_expense > budget_amount:
webnotes.msgprint(action_for + _(" budget ") + cstr(budget_amount) +
_(" for account ") + args.account + _(" against cost center ") +
args.cost_center + _(" will exceed by ") +
cstr(actual_expense - budget_amount) + _(" after this transaction.")
, raise_exception=BudgetError if action=="Stop" else False)
def get_allocated_budget(distribution_id, posting_date, fiscal_year, yearly_budget):
if distribution_id:
distribution = {}
for d in webnotes.conn.sql("""select bdd.month, bdd.percentage_allocation
from `tabBudget Distribution Detail` bdd, `tabBudget Distribution` bd
where bdd.parent=bd.name and bd.fiscal_year=%s""", fiscal_year, as_dict=1):
distribution.setdefault(d.month, d.percentage_allocation)
dt = webnotes.conn.get_value("Fiscal Year", fiscal_year, "year_start_date")
budget_percentage = 0.0
while(dt <= getdate(posting_date)):
if distribution_id:
budget_percentage += distribution.get(getdate(dt).strftime("%B"), 0)
else:
budget_percentage += 100.0/12
dt = add_months(dt, 1)
return yearly_budget * budget_percentage / 100
def get_actual_expense(args):
args["condition"] = " and posting_date<='%s'" % args.month_end_date \
if args.get("month_end_date") else ""
return webnotes.conn.sql("""
select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
from `tabGL Entry`
where account='%(account)s' and cost_center='%(cost_center)s'
and fiscal_year='%(fiscal_year)s' and company='%(company)s' %(condition)s
""" % (args))[0][0]

View File

@ -8,6 +8,7 @@
wn.provide("erpnext.buying");
wn.require("app/js/transaction.js");
wn.require("app/js/controllers/accounts.js");
erpnext.buying.BuyingController = erpnext.TransactionController.extend({
onload: function() {
@ -108,8 +109,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
var item = wn.model.get_doc(cdt, cdn);
if(item.item_code) {
if(!this.validate_company_and_party("supplier")) {
item.item_code = null;
refresh_field("item_code", item.name, item.parentfield);
cur_frm.fields_dict[me.frm.cscript.fname].grid.grid_rows[item.idx - 1].remove();
} else {
return this.frm.call({
method: "buying.utils.get_item_details",

View File

@ -36,13 +36,6 @@ class DocType(BuyingController):
msgprint("Supplier : %s does not exists" % (name))
raise Exception
# Get Available Qty at Warehouse
def get_bin_details( self, arg = ''):
arg = eval(arg)
bin = webnotes.conn.sql("select projected_qty from `tabBin` where item_code = %s and warehouse = %s", (arg['item_code'], arg['warehouse']), as_dict=1)
ret = { 'projected_qty' : bin and flt(bin[0]['projected_qty']) or 0 }
return ret
def update_last_purchase_rate(self, obj, is_submit):
"""updates last_purchase_rate in item table for each item"""
@ -196,18 +189,3 @@ class DocType(BuyingController):
if not submitted:
msgprint(cstr(doctype) + ": " + cstr(submitted[0][0])
+ _(" not submitted"), raise_exception=1)
def get_rate(self, arg, obj):
arg = eval(arg)
rate = webnotes.conn.sql("select account_type, tax_rate from `tabAccount` where name = %s"
, (arg['account_head']), as_dict=1)
return {'rate': rate and (rate[0]['account_type'] == 'Tax' \
and not arg['charge_type'] == 'Actual') and flt(rate[0]['tax_rate']) or 0 }
def get_prevdoc_date(self, obj):
for d in getlist(obj.doclist, obj.fname):
if d.prevdoc_doctype and d.prevdoc_docname:
dt = webnotes.conn.sql("select transaction_date from `tab%s` where name = %s"
% (d.prevdoc_doctype, '%s'), (d.prevdoc_docname))
d.prevdoc_date = dt and dt[0][0].strftime('%Y-%m-%d') or ''

View File

@ -10,6 +10,7 @@ cur_frm.cscript.other_fname = "purchase_tax_details";
wn.require('app/accounts/doctype/purchase_taxes_and_charges_master/purchase_taxes_and_charges_master.js');
wn.require('app/utilities/doctype/sms_control/sms_control.js');
wn.require('app/buying/doctype/purchase_common/purchase_common.js');
wn.require('app/accounts/doctype/sales_invoice/pos.js');
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
refresh: function(doc, cdt, cdn) {
@ -184,9 +185,9 @@ cur_frm.pformat.indent_no = function(doc, cdt, cdn){
if(cl[i].prevdoc_doctype == 'Material Request' && cl[i].prevdoc_docname && prevdoc_list.indexOf(cl[i].prevdoc_docname) == -1) {
prevdoc_list.push(cl[i].prevdoc_docname);
if(prevdoc_list.length ==1)
out += make_row(cl[i].prevdoc_doctype, cl[i].prevdoc_docname, cl[i].prevdoc_date,0);
out += make_row(cl[i].prevdoc_doctype, cl[i].prevdoc_docname, null,0);
else
out += make_row('', cl[i].prevdoc_docname, cl[i].prevdoc_date,0);
out += make_row('', cl[i].prevdoc_docname,null,0);
}
}
}

View File

@ -41,7 +41,6 @@ class DocType(BuyingController):
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)
self.validate_uom_is_integer("uom", "qty")
@ -65,10 +64,6 @@ class DocType(BuyingController):
}
})
# get available qty at warehouse
def get_bin_details(self, arg = ''):
return get_obj(dt='Purchase Common').get_bin_details(arg)
def get_schedule_dates(self):
for d in getlist(self.doclist, 'po_details'):
if d.prevdoc_detail_docname and not d.schedule_date:
@ -88,6 +83,7 @@ class DocType(BuyingController):
def update_bin(self, is_submit, is_stopped = 0):
from stock.utils import update_bin
pc_obj = get_obj('Purchase Common')
for d in getlist(self.doclist, 'po_details'):
#1. Check if is_stock_item == 'Yes'
@ -122,12 +118,13 @@ class DocType(BuyingController):
# Update ordered_qty and indented_qty in bin
args = {
"item_code" : d.item_code,
"ordered_qty" : (is_submit and 1 or -1) * flt(po_qty),
"indented_qty" : (is_submit and 1 or -1) * flt(ind_qty),
"item_code": d.item_code,
"warehouse": d.warehouse,
"ordered_qty": (is_submit and 1 or -1) * flt(po_qty),
"indented_qty": (is_submit and 1 or -1) * flt(ind_qty),
"posting_date": self.doc.transaction_date
}
get_obj("Warehouse", d.warehouse).update_bin(args)
update_bin(args)
def check_modified_date(self):
mod_db = webnotes.conn.sql("select modified from `tabPurchase Order` where name = '%s'" % self.doc.name)
@ -183,9 +180,6 @@ class DocType(BuyingController):
def on_update(self):
pass
def get_rate(self,arg):
return get_obj('Purchase Common').get_rate(arg,self)
@webnotes.whitelist()
def make_purchase_receipt(source_name, target_doclist=None):
from webnotes.model.mapper import get_mapped_doclist

View File

@ -2,12 +2,13 @@
{
"creation": "2013-05-21 16:16:39",
"docstatus": 0,
"modified": "2013-09-12 18:34:54",
"modified": "2013-10-02 14:24:49",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_attach": 1,
"allow_import": 1,
"autoname": "naming_series:",
"doctype": "DocType",
"document_type": "Transaction",
@ -132,7 +133,6 @@
"fieldtype": "Date",
"in_filter": 1,
"label": "Purchase Order Date",
"no_copy": 1,
"oldfieldname": "transaction_date",
"oldfieldtype": "Date",
"reqd": 1,

View File

@ -6,9 +6,10 @@ from __future__ import unicode_literals
import unittest
import webnotes
import webnotes.defaults
from webnotes.utils import flt
class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_receipt(self):
def test_make_purchase_receipt(self):
from buying.doctype.purchase_order.purchase_order import make_purchase_receipt
po = webnotes.bean(copy=test_records[0]).insert()
@ -18,6 +19,7 @@ class TestPurchaseOrder(unittest.TestCase):
po = webnotes.bean("Purchase Order", po.doc.name)
po.submit()
pr = make_purchase_receipt(po.doc.name)
pr[0]["supplier_warehouse"] = "_Test Warehouse 1 - _TC"
@ -25,7 +27,52 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(len(pr), len(test_records[0]))
pr[0].naming_series = "_T-Purchase Receipt-"
webnotes.bean(pr).insert()
pr_bean = webnotes.bean(pr)
pr_bean.insert()
def test_ordered_qty(self):
webnotes.conn.sql("delete from tabBin")
from buying.doctype.purchase_order.purchase_order import make_purchase_receipt
po = webnotes.bean(copy=test_records[0]).insert()
self.assertRaises(webnotes.ValidationError, make_purchase_receipt,
po.doc.name)
po = webnotes.bean("Purchase Order", po.doc.name)
po.doc.is_subcontracted = "No"
po.doclist[1].item_code = "_Test Item"
po.submit()
self.assertEquals(webnotes.conn.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty"), 10)
pr = make_purchase_receipt(po.doc.name)
self.assertEquals(pr[0]["doctype"], "Purchase Receipt")
self.assertEquals(len(pr), len(test_records[0]))
pr[0].naming_series = "_T-Purchase Receipt-"
pr[1].qty = 4.0
pr_bean = webnotes.bean(pr)
pr_bean.insert()
pr_bean.submit()
self.assertEquals(flt(webnotes.conn.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0)
webnotes.conn.set_value('Item', '_Test Item', 'tolerance', 50)
pr1 = make_purchase_receipt(po.doc.name)
pr1[0].naming_series = "_T-Purchase Receipt-"
pr1[1].qty = 8
pr1_bean = webnotes.bean(pr1)
pr1_bean.insert()
pr1_bean.submit()
self.assertEquals(flt(webnotes.conn.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0)
def test_make_purchase_invocie(self):
from buying.doctype.purchase_order.purchase_order import make_purchase_invoice
@ -51,12 +98,12 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(len(po.doclist.get({"parentfield": "po_raw_material_details"})), 2)
def test_warehouse_company_validation(self):
from controllers.buying_controller import WrongWarehouseCompany
from stock.utils import InvalidWarehouseCompany
po = webnotes.bean(copy=test_records[0])
po.doc.company = "_Test Company 1"
po.doc.conversion_rate = 0.0167
self.assertRaises(WrongWarehouseCompany, po.insert)
self.assertRaises(InvalidWarehouseCompany, po.insert)
def test_uom_integer_validation(self):
from utilities.transaction_base import UOMMustBeIntegerError
po = webnotes.bean(copy=test_records[0])

View File

@ -2,7 +2,7 @@
{
"creation": "2013-05-24 19:29:06",
"docstatus": 0,
"modified": "2013-08-07 14:44:12",
"modified": "2013-10-10 17:01:57",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -306,21 +306,6 @@
"search_index": 1,
"width": "120px"
},
{
"doctype": "DocField",
"fieldname": "prevdoc_date",
"fieldtype": "Date",
"hidden": 1,
"in_filter": 1,
"in_list_view": 0,
"label": "Material Request Date",
"no_copy": 1,
"oldfieldname": "prevdoc_date",
"oldfieldtype": "Date",
"print_hide": 1,
"read_only": 1,
"search_index": 0
},
{
"doctype": "DocField",
"fieldname": "prevdoc_detail_docname",

View File

@ -1,11 +1,6 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt
cur_frm.cscript.item_code = function(doc, cdt, cdn) {
if (doc.item_code)
return get_server_fields('get_purchase_receipt_item_details','','',doc,cdt,cdn,1);
}
cur_frm.cscript.inspection_type = function(doc, cdt, cdn) {
if(doc.inspection_type == 'Incoming'){
doc.delivery_note_no = '';
@ -30,13 +25,22 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
// item code based on GRN/DN
cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
var filter = {};
if (doc.purchase_receipt_no) filter['parent'] = doc.purchase_receipt_no;
else if (doc.delivery_note_no) filter['parent'] = doc.delivery_note_no;
return{
filters: filter
if (doc.purchase_receipt_no) {
return {
query: "buying.doctype.quality_inspection.quality_inspection.item_query",
filters: {
"from": "Purchase Receipt Item",
"parent": doc.purchase_receipt_no
}
}
} else if (doc.delivery_note_no) {
return {
query: "buying.doctype.quality_inspection.quality_inspection.item_query",
filters: {
"from": "Delivery Note Item",
"parent": doc.delivery_note_no
}
}
}
}

View File

@ -33,3 +33,17 @@ class DocType:
webnotes.conn.sql("update `tabPurchase Receipt Item` t1, `tabPurchase Receipt` t2 set t1.qa_no = '', t2.modified = '%s' \
where t1.parent = '%s' and t1.item_code = '%s' and t1.parent = t2.name" \
% (self.doc.modified, self.doc.purchase_receipt_no, self.doc.item_code))
def item_query(doctype, txt, searchfield, start, page_len, filters):
if filters.get("from"):
from webnotes.widgets.reportview import get_match_cond
filters.update({
"txt": txt,
"mcond": get_match_cond(filters["from"], searchfield),
"start": start,
"page_len": page_len
})
return webnotes.conn.sql("""select item_code from `tab%(from)s`
where parent='%(parent)s' and docstatus < 2 and item_code like '%%%(txt)s%%' %(mcond)s
order by item_code limit %(start)s, %(page_len)s""" % filters)

View File

@ -9,6 +9,7 @@ cur_frm.cscript.other_fname = "purchase_tax_details";
// attach required files
wn.require('app/accounts/doctype/purchase_taxes_and_charges_master/purchase_taxes_and_charges_master.js');
wn.require('app/buying/doctype/purchase_common/purchase_common.js');
wn.require('app/accounts/doctype/sales_invoice/pos.js');
erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({
refresh: function() {

View File

@ -54,7 +54,6 @@ class DocType(BuyingController):
def validate_common(self):
pc = get_obj('Purchase Common')
pc.validate_for_items(self)
pc.get_prevdoc_date(self)
@webnotes.whitelist()
def make_purchase_order(source_name, target_doclist=None):

View File

@ -2,12 +2,13 @@
{
"creation": "2013-05-21 16:16:45",
"docstatus": 0,
"modified": "2013-08-09 14:45:58",
"modified": "2013-10-02 14:24:44",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_attach": 1,
"allow_import": 1,
"autoname": "naming_series:",
"doctype": "DocType",
"document_type": "Transaction",

View File

@ -2,7 +2,7 @@
{
"creation": "2013-05-22 12:43:10",
"docstatus": 0,
"modified": "2013-08-07 14:44:18",
"modified": "2013-10-10 17:02:11",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -259,21 +259,6 @@
"search_index": 1,
"width": "120px"
},
{
"doctype": "DocField",
"fieldname": "prevdoc_date",
"fieldtype": "Date",
"hidden": 1,
"in_filter": 1,
"in_list_view": 0,
"label": "Material Request Date",
"no_copy": 1,
"oldfieldname": "prevdoc_date",
"oldfieldtype": "Date",
"print_hide": 1,
"read_only": 1,
"search_index": 0
},
{
"doctype": "DocField",
"fieldname": "prevdoc_detail_docname",

View File

@ -145,6 +145,11 @@ wn.module_page["Buying"] = [
route: "query-report/Purchase Order Trends",
doctype: "Purchase Order"
},
{
"label":wn._("Supplier Addresses And Contacts"),
route: "query-report/Supplier Addresses and Contacts",
doctype: "Supplier"
},
]
}
]

View File

@ -0,0 +1,22 @@
[
{
"creation": "2013-10-09 10:38:40",
"docstatus": 0,
"modified": "2013-10-09 10:53:52",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"query": "SELECT\n `tabSupplier`.name as \"Supplier:Link/Supplier:120\",\n\t`tabSupplier`.supplier_name as \"Supplier Name::120\",\n\t`tabSupplier`.supplier_type as \"Supplier Type:Link/Supplier Type:120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2), \n\t\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\n concat_ws(', ', `tabContact`.first_name, `tabContact`.last_name) as 'Contact Name::180',\n\t`tabContact`.phone as \"Phone\",\n\t`tabContact`.mobile_no as \"Mobile No\",\n\t`tabContact`.email_id as \"Email Id::120\",\n\t`tabContact`.is_primary_contact as \"Is Primary Contact::120\"\nFROM\n\t`tabSupplier`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.supplier=`tabSupplier`.name\n\t)\n\tleft join `tabContact` on (\n\t\t`tabContact`.supplier=`tabSupplier`.name\n\t)\nWHERE\n\t`tabSupplier`.docstatus<2\nORDER BY\n\t`tabSupplier`.name asc",
"ref_doctype": "Supplier",
"report_name": "Supplier Addresses and Contacts",
"report_type": "Query Report"
},
{
"doctype": "Report",
"name": "Supplier Addresses and Contacts"
}
]

View File

@ -65,7 +65,7 @@ def _get_basic_details(args, item_bean):
out = webnotes._dict({
"description": item.description_html or item.description,
"qty": 0.0,
"qty": 1.0,
"uom": item.stock_uom,
"conversion_factor": 1.0,
"warehouse": args.warehouse or item.default_warehouse,
@ -98,8 +98,8 @@ def _get_price_list_rate(args, item_bean, meta):
from utilities.transaction_base import validate_currency
validate_currency(args, item_bean.doc, meta)
out.import_ref_rate = \
flt(price_list_rate[0].ref_rate * args.plc_conversion_rate / args.conversion_rate)
out.import_ref_rate = flt(price_list_rate[0].ref_rate) * \
flt(args.plc_conversion_rate) / flt(args.conversion_rate)
# if not found, fetch from last purchase transaction
if not out.import_ref_rate:

View File

@ -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):
@ -428,3 +423,7 @@ class AccountsController(TransactionBase):
self._abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr")
return self._abbr
@webnotes.whitelist()
def get_tax_rate(account_head):
return webnotes.conn.get_value("Account", account_head, "tax_rate")

View File

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
import webnotes, json
from webnotes import _, msgprint
from webnotes.utils import flt, _round
@ -11,9 +11,8 @@ from setup.utils import get_company_currency
from controllers.stock_controller import StockController
class WrongWarehouseCompany(Exception): pass
class BuyingController(StockController):
def onload_post_render(self):
# contact, address, item details
self.set_missing_values()
@ -24,7 +23,7 @@ class BuyingController(StockController):
self.doc.supplier_name = webnotes.conn.get_value("Supplier",
self.doc.supplier, "supplier_name")
self.validate_stock_or_nonstock_items()
self.validate_warehouse_belongs_to_company()
self.validate_warehouse()
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@ -49,20 +48,23 @@ class BuyingController(StockController):
if supplier:
self.doc.supplier = supplier
break
def validate_warehouse(self):
from stock.utils import validate_warehouse_user, validate_warehouse_company
warehouses = list(set([d.warehouse for d in
self.doclist.get({"doctype": self.tname}) if d.warehouse]))
for w in warehouses:
validate_warehouse_user(w)
validate_warehouse_company(w, self.doc.company)
def get_purchase_tax_details(self):
self.doclist = self.doc.clear_table(self.doclist, "purchase_tax_details")
self.set_taxes("purchase_tax_details", "purchase_other_charges")
def validate_warehouse_belongs_to_company(self):
for warehouse, company in webnotes.conn.get_values("Warehouse",
self.doclist.get_distinct_values("warehouse"), "company").items():
if company and company != self.doc.company:
webnotes.msgprint(_("Company mismatch for Warehouse") + (": %s" % (warehouse,)),
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"]]

View File

@ -14,7 +14,10 @@ class SellingController(StockController):
def onload_post_render(self):
# contact, address, item details and pos details (if applicable)
self.set_missing_values()
def get_sender(self, comm):
return webnotes.conn.get_value('Sales Email Settings', None, 'email_id')
def set_missing_values(self, for_validate=False):
super(SellingController, self).set_missing_values(for_validate)
@ -23,7 +26,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 +86,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"
@ -273,34 +236,4 @@ class SellingController(StockController):
self.doc.order_type = "Sales"
elif self.doc.order_type not in valid_types:
msgprint(_(self.meta.get_label("order_type")) + " " +
_("must be one of") + ": " + comma_or(valid_types),
raise_exception=True)
def update_serial_nos(self, cancel=False):
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
update_serial_nos_after_submit(self, self.doc.doctype, self.fname)
update_serial_nos_after_submit(self, self.doc.doctype, "packing_details")
for table_fieldname in (self.fname, "packing_details"):
for d in self.doclist.get({"parentfield": table_fieldname}):
for serial_no in get_serial_nos(d.serial_no):
sr = webnotes.bean("Serial No", serial_no)
if cancel:
sr.doc.status = "Available"
for fieldname in ("warranty_expiry_date", "delivery_document_type",
"delivery_document_no", "delivery_date", "delivery_time", "customer",
"customer_name"):
sr.doc.fields[fieldname] = None
else:
sr.doc.delivery_document_type = self.doc.doctype
sr.doc.delivery_document_no = self.doc.name
sr.doc.delivery_date = self.doc.posting_date
sr.doc.delivery_time = self.doc.posting_time
sr.doc.customer = self.doc.customer
sr.doc.customer_name = self.doc.customer_name
if sr.doc.warranty_period:
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date),
cint(sr.doc.warranty_period))
sr.doc.status = 'Delivered'
sr.save()
_("must be one of") + ": " + comma_or(valid_types), raise_exception=True)

View File

@ -8,6 +8,51 @@ from webnotes import msgprint
from webnotes.model.controller import DocListController
status_map = {
"Contact": [
["Replied", "communication_sent"],
["Open", "communication_received"]
],
"Job Applicant": [
["Replied", "communication_sent"],
["Open", "communication_received"]
],
"Lead": [
["Replied", "communication_sent"],
["Converted", "has_customer"],
["Opportunity", "has_opportunity"],
["Open", "communication_received"],
],
"Opportunity": [
["Draft", None],
["Submitted", "eval:self.doc.docstatus==1"],
["Lost", "eval:self.doc.status=='Lost'"],
["Quotation", "has_quotation"],
["Replied", "communication_sent"],
["Cancelled", "eval:self.doc.docstatus==2"],
["Open", "communication_received"],
],
"Quotation": [
["Draft", None],
["Submitted", "eval:self.doc.docstatus==1"],
["Lost", "eval:self.doc.status=='Lost'"],
["Ordered", "has_sales_order"],
["Replied", "communication_sent"],
["Cancelled", "eval:self.doc.docstatus==2"],
["Open", "communication_received"],
],
"Sales Order": [
["Draft", None],
["Submitted", "eval:self.doc.docstatus==1"],
["Stopped", "eval:self.doc.status=='Stopped'"],
["Cancelled", "eval:self.doc.docstatus==2"],
],
"Support Ticket": [
["Replied", "communication_sent"],
["Open", "communication_received"]
],
}
class StatusUpdater(DocListController):
"""
Updates the status of the calling records
@ -20,6 +65,45 @@ class StatusUpdater(DocListController):
self.update_qty()
self.validate_qty()
def set_status(self, update=False):
if self.doc.get("__islocal"):
return
if self.doc.doctype in status_map:
sl = status_map[self.doc.doctype][:]
sl.reverse()
for s in sl:
if not s[1]:
self.doc.status = s[0]
break
elif s[1].startswith("eval:"):
if eval(s[1][5:]):
self.doc.status = s[0]
break
elif getattr(self, s[1])():
self.doc.status = s[0]
break
if update:
webnotes.conn.set_value(self.doc.doctype, self.doc.name, "status", self.doc.status)
def on_communication(self):
self.communication_set = True
self.set_status(update=True)
del self.communication_set
def communication_received(self):
if getattr(self, "communication_set", False):
last_comm = self.doclist.get({"doctype":"Communication"})
if last_comm:
return last_comm[-1].sent_or_received == "Received"
def communication_sent(self):
if getattr(self, "communication_set", False):
last_comm = self.doclist.get({"doctype":"Communication"})
if last_comm:
return last_comm[-1].sent_or_received == "Sent"
def validate_qty(self):
"""
Validates qty at row level

View File

@ -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()

View 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 companys 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 companys assets, often known as stock-in-hand.
The difference between the value of the items remaining to be sold and the previous periods 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)

View 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)

View File

@ -15,6 +15,9 @@
}
---
![Accounts](img/accounts-image.png)
At end of the sales and purchase cycle is billing and payments. You may have an accountant in your team, or you may be doing accounting yourself or you may have outsourced your accounting. Financial accounting forms the core of any business management system like an ERP.

View File

@ -0,0 +1,10 @@
---
{
"_label": "Opening Entry"
}
---
If you are a new company you can start using ERPNext accounting module by going to chart of accounts.
However, if you are migrating from a legacy accounting system like Tally or a Fox Pro based software, please visit [Opening Entry](docs.user.setup.opening.html)

View File

@ -0,0 +1,44 @@
---
{
"_label": "Opening Stock"
}
---
You can upload your opening stock in the system using Stock Reconciliation. Stock Reconciliation will update your stock for a given Item on a given date for a given Warehouse to the given quantity.
Stock Reconciliation will make a “difference entry” (the difference between the system stock and the actual stock in your file) for the Item.
Tip: Stock Reconciliation can also be used to update the “value” of an Item.
To make a Stock Reconciliation, go to:
> Stock > Stock Reconciliation > New Stock Reconciliation
and follow the steps mentioned on the page.
#### Step 1: Download Template
![Stock Reconciliation](img/stock-reconciliation-1.png)
#### Step 2: Enter Data in csv file.
![Stock Reconciliation](img/stock-reconciliation-csv-1.png)
The csv format is case-sensitive. Thus special care should be taken to avoid spelling errors or wrong names. Even if you do not list some quantities or valuation rates, the file will still process the data.
#### Step 3: Upload the csv file with data
![Stock Reconciliation](img/stock-reconciliation-2.png)
<br>
#### Step 4: Attach the uploaded file.
![Stock Reconciliation](img/stock-reconciliation-3.png)
After reviewing saved Reconciliation Data, submit the Stock Reconciliation. On successful submission, the data will be updated in the system. To check the uploaded data go to Stock and view Stock Level Report.

View File

@ -44,7 +44,7 @@ To see entries in your Purchase Invoice after you “Submit”, click on “View
---
#### Is a purchase an “Expense” or “Asset”?
#### Is purchase an “Expense” or an “Asset”?
If the Item is consumed immediately on purchase, or if it is a service, then the purchase becomes an “Expense”. For example, a telephone bill or travel bill is an “Expense” - it is already consumed.

View File

@ -20,7 +20,15 @@ You can create a new Supplier via:
> Tip: When you select a Supplier in any transaction, one Contact and Address gets pre-selected. This is the “Default Contact or Address”. So make sure you set your defaults correctly!
### Integration with Accounts
In ERPNext, there is a separate Account record for each Supplier, of Each company.
When you create a new Supplier, ERPNext will automatically create an Account Ledger for the Supplier under “Accounts Receivable” in the Company set in the Supplier record.
> Advanced Tip: If you want to change the Account Group under which the Supplier Account is created, you can set it in the Company master.
If you want to create an Account in another Company, just change the Company value and “Save” the Supplier again.
> Buying > Contact > New Contact

View File

@ -3,9 +3,11 @@
"_label": "Supplier Type"
}
---
A supplier may be distinguished from a contractor or subcontractor, who commonly adds specialized input to deliverables. A supplier is also known as a vendor. There are different types of suppliers based on their goods and products.
Based on what the suppliers supply, they are classified into different categories called Supplier Type.
There can be different types of suppliers. You can create your own category of Supplier Type.
ERPNext allows you to create your own categories of suppliers. These categories are known as Supplier Type. For Example, if your suppliers are mainly pharmaceutical companies and FMCG distributors, You can create a new Type for them and name them accordingly.
Based on what the suppliers supply, they are classified into different categories called Supplier Type. There can be different types of suppliers. You can create your own category of Supplier Type.
> Buying > Supplier Type > New Supplier Type

View File

@ -7,6 +7,10 @@ A very common customization is adding of custom fields. You can add Custom Field
> Setup > Custom Field > New Custom Field
![Custome Field](img/custom-field.png)
In the form:
- Select the Document on which you want to add the Custom Field.

View File

@ -9,9 +9,13 @@ You can Customize Forms by changing its layout, making certain fields mandatory,
> Setup > Customize ERPNext > Customize Forms
![Customize Forms](img/customize-form-1.png)
Select the Form you want to customize and the fields table will be updated with the fields from that form. Here you can:
- Change field types (for e.g. you want to increase the number of decimal places, you can convert come fields from Float to Currency).
- Change field types (for e.g. you want to increase the number of decimal places, you can convert some fields from Float to Currency).
- Change labels to suit your industry / language.
- Make certain fields mandatory.
- Hide certain fields.
@ -20,4 +24,12 @@ Select the Form you want to customize and the fields table will be updated with
You can also allow attachments, set max number of attachments and set the default Print Format.
![Customize Forms](img/customize-form-2.png)
<br>
> Though we want you to do everything you can to customize your ERP based on your business needs, we recommend that you do not make “wild” changes to the forms. This is because, these changes may affect certain operations and may mess up your forms. Make small changes and see its effect before doing some more.

View File

@ -3,4 +3,10 @@
"_label": "Custom Scripts"
}
---
If you wish to change any ERPNext form formats, you can do so by using Custom Scripts. For example, if you wish to add a submit button after saving, to a Lead form, you can do so by creating your own script.
> Setup > Customization > Custom Script
![Custom Script](img/custom-script.png)

View File

@ -7,7 +7,10 @@
As you have seen from this manual that ERPNext contains tons of features which you may not use. We have observed that most users start with using 20% of the features, though a different 20%. To hide fields belonging to features you dont require, go to:
> Setup > Customize ERPNext > Disable Features.
> Setup > Tools > Hide/Unhide Features
![Hide Features](img/hide-features.png)
Check / uncheck the features you want to use and refresh your page for the changes to take effect.
@ -17,6 +20,9 @@ Check / uncheck the features you want to use and refresh your page for the chang
To hide modules (icons) from the home page, go to:
Setup > Customize ERPNext > Modules Setup
Setup > Tools> Modules Setup
![Hide/Unhide Modules](img/hide-module.png)
> Note: Modules are automatically hidden for users that have no permissions on the documents within that module. For example, if a user has no permissions on Purchase Order, Purchase Request, Supplier, the “Buying” module will automatically be hidden.

View File

@ -8,11 +8,18 @@ Print Formats are the layouts that are generated when you want to Print or Email
- The auto-generated “Standard” Print Format: This type of format follows the same layout as the form and is generated automatically by ERPNext.
- Based on the Print Format document. There are templates in HTML that will be rendered with data.
ERPNext comes with a number of pre-defined templates in three styles: Modern, Classic and Spartan. You modify these templates or create their own. Editing ERPNext templates is not allowed because they may be over-written in an upcoming release.
ERPNext comes with a number of pre-defined templates in three styles: Modern, Classic and Spartan. You modify these templates or create your own. Editing ERPNext templates is not allowed because they may be over-written in an upcoming release.
To create your own versions, open an existing template from:
> Setup > Branding and Printing > Print Formats
> Setup > Printing > Print Formats
![Print Format](img/print-format.png)
<br>
Select the type of Print Format you want to edit and click on the “Copy” button on the right column. A new Print Format will open up with “Is Standard” set as “No” and you can edit the Print Format.

View File

@ -15,7 +15,8 @@
"docs.user.projects",
"docs.user.website",
"docs.user.tools",
"docs.user.customize"
"docs.user.customize",
"docs.user.knowledge"
],
"_no_toc": 1
}
@ -84,10 +85,14 @@ Contents
1. [Purchase Receipt](docs.user.stock.purchase_receipt.html)
1. [Delivery Note](docs.user.stock.delivery_note.html)
1. [Stock Entry / Material Transfer](docs.user.stock.stock_entry.html)
1. [Opening Stock](docs.user.accounts.opening_stock.html)
1. [Material Issue](docs.user.stock.material_issue.html)
1. [Sales Return](docs.user.stock.sales_return.html)
1. [Purchase Return](docs.user.stock.purchase_return.html)
1. [Projected Quantity](docs.user.stock.projected_quantity.html)
1. [Accounting for Stock](docs.user.stock.accounting_for_stock.html)
1. [Perpetual Inventory](docs.user.stock.perpetual_inventory.html)
1. [Migrate to Perpetual Inventory](docs.user.stock.migrate_to_perpetual.html)
1. [Accounts](docs.user.accounts.html)
1. [Chart of Accounts](docs.user.setup.accounting.html)
1. [Chart of Cost Centers](docs.user.setup.cost_centers.html)
@ -98,7 +103,6 @@ Contents
1. [Opening Entry](docs.user.accounts.opening_entry.html)
1. [Period Closing](docs.user.accounts.closing.html)
1. [Accounting Reports](docs.user.accounts.reports.html)
1. [Upload Journal Entries in Bulk](docs.user.accounts.voucher_import.html)
1. [Point of Sale (POS) Invoice](docs.user.accounts.pos.html)
1. [Human Resources (HR)](docs.user.hr.html)
1. [HR Setup](docs.user.hr.setup.html)
@ -118,10 +122,11 @@ Contents
1. [Maintenance Schedule](docs.user.support.maintenance_schedule.html)
1. [Maintenance Visit](docs.user.support.maintenance_visit.html)
1. [Maintenance Schedule](docs.user.support.maintenance_schedule.html)
1. [Newsletter](docs.user.support.newsletter.html)
1. [Projects](docs.user.projects.html)
1. [Projects](docs.user.projects.projects.html)
1. [Task](docs.user.projects.task.html)
1. [Time Log](docs.user.projects.time_log.html)
1. [Time Log](docs.user.projects.timelog.html)
1. [Website](docs.user.website.html)
1. [Setup](docs.user.website.setup.html)
1. [Web Pages](docs.user.website.web_page.html)
@ -133,9 +138,20 @@ Contents
1. [Calendar](docs.user.tools.calendar.html)
1. [Assignments](docs.user.tools.assignment.html)
1. [Tags](docs.user.tools.tags.html)
1. [Forms](docs.user.tools.form_tools.html)
1. [Messages](docs.user.tools.messages.html)
1. [Notes](docs.user.tools.notes.html)
1. [Customize](docs.user.customize.html)
1. [Custom Fields](docs.user.customize.custom_field.html)
1. [Customize Form](docs.user.customize.custom_form.html)
1. [Hide / Unhide modules](docs.user.customize.hide_unhide.html)
1. [Print Formats](docs.user.customize.print_formats.html)
1. [Custom Scripts](docs.user.customize.custom_scripts.html)
1. [Custom Scripts](docs.user.customize.custom_scripts.html)
1. [Knowledge Library](docs.user.knowledge.html)
1. [Fiscal Year](docs.user.knowledge.fiscal_year.html)
1. [Accounting Knowledge](docs.user.knowledge.accounting.html)
1. [Accounting Entries](docs.user.knowledge.accounting_entries.html)
1. [DocType Definitions](docs.user.knowledge.doctype.html)
1. [Attachment and CSV Files](docs.user.knowledge.attachment_csv.html)
1. [Format using Markdown](docs.user.knowledge.markdown.html)

View File

@ -17,7 +17,7 @@ The Salary Structure represents how Salaries are calculated based on Earnings an
![Salary Structure](img/salary-structure.png)
s
### In the Salary Structure,

View File

@ -13,7 +13,7 @@ Before you start implementation, lets get familiar with the terminology that is
This represents the Company records for which ERPNext is setup. With this same setup, you can create multiple Company records, each representing a different legal entity. The accounting for each Company will be different, but they will share the Customer, Supplier and Item records.
> Setup > Company > Companies
> Setup > Company
#### Customer
@ -59,11 +59,11 @@ A list of all Communication with a Contact or Lead. All emails sent from the sys
#### Price List
A table of sale price for an Item. An Item can have multiple prices based on Customer / Supplier or Territory etc..
A Price List is a place where different rate plans can be stored. Its a name you give to a set of Item Prices stored under a particular List.
> Selling > Setup > Price List
> Selling > Price List
> Buying > Setup > Price List
> Buying > Price List
---
@ -73,7 +73,7 @@ A table of sale price for an Item. An Item can have multiple prices based on Cus
Represents a Financial Year or Accounting Year. You can operate multiple Fiscal Years at the same time. Each Fiscal Year has a start date and an end date and transactions can only be recorded in this period. When you “close” a fiscal year, it's balances are transferred as “opening” balances for the next fiscal year.
> Setup > Company > Fiscal Years
> Setup > Company > Fiscal Year
#### Cost Center
@ -85,7 +85,7 @@ A Cost Center is like an Account, but the only difference is that its structure
A document that contains General Ledger (GL) entries and the sum of Debits and Credits of those entries is the same. In ERPNext you can update Payments, Returns etc using Journal Vouchers.
> Accounts > Journal Vouchers
> Accounts > Journal Voucher
#### Sales Invoice
@ -103,7 +103,7 @@ A bill sent by a Supplier for delivery of Items (goods or services).
ERPNext allows you to book transactions in multiple currencies. There is only one currency for your book of accounts though. While posting your Invoices, payments in different currencies, the amount is converted to the default currency by the specified conversion rate.
> Setup > Company > Currencies
> Setup > Currency
---
@ -113,7 +113,7 @@ ERPNext allows you to book transactions in multiple currencies. There is only on
A classification of Customers, usually based on market segment.
> Selling > Setup (sidebar) > Customer Group
> Selling > Setup > Customer Group
#### Lead
@ -215,19 +215,19 @@ A unified table for all material movement from one warehouse to another. This is
Update Stock of multiple Items from a spreadsheet (CSV) file.
> Stock > Stock Reconciliation (in sidebar)
> Stock > Stock Reconciliation
#### Quality Inspection
A note prepared to record certain parameters of an Item at the time of Receipt from Supplier, or Delivery to Customer.
> Stock > Tools > Quality Inspection
> Stock > Quality Inspection
#### Item Group
A classification of Item.
> Stock > Setup (sidebar) > Item Group
> Stock > Setup > Item Group
---
@ -249,7 +249,7 @@ A record of an approved or rejected request for leave.
A type of leave (for example, Sick Leave, Maternity Leave etc.)
> Human Resource > Leave and Attendance (sidebar) > Leave Type
> Human Resource > Leave and Attendance > Leave Type
#### Salary Manager
@ -357,7 +357,7 @@ A title that can be set on a transaction just for printing. For example, you wan
Text of your terms of contract.
> Selling > Setup > Terms and Conditions Template
> Selling > Setup > Terms and Conditions
#### Unit of Measure (UOM)

View File

@ -7,6 +7,11 @@
]
}
---
![Implementation](img/implementation-image.png)
We have seen dozens of ERP implementations over the past few years and we realize that successful implementation is a lot about intangibles and attitude.
**ERPs are not required.**

View File

@ -0,0 +1,110 @@
---
{
"_label": "Accounting Knowledge"
}
---
Chart of Accounts represents a tree like representation of all accounting heads, used to represent company's financial information. There are two types of accounts: Balance Sheet and Profit and Loss Statement accounts. Balance Sheet consists of Assets and Liabilities accounts and Profit and Loss Statement accounts consists of Incomes and Expenses.
**Assets:** Bank and Cash balances, outstanding amounts of customers and all other assets are recorded here.
**Liabilities:** All the company's liabilities like shareholder's capital, outstanding amount to be paid to suppliers, taxes to be paid to concerned to authorities are recorded under this group.
**Income:** Income from direct/indirect sales.
**Expenses:** All the expenses to run the business, like salaries, purchases, rent etc. are recorded here.
### Debit and Credit
Each of these accounts are either "Debit" or "Credit" type. Assets, Expenses are "Debit" accounts and Liabilities, Incomes are "Credit" accounts.
Accounting Entries
The balance of account can be increased / decreased, depending on account type and transaction type.
<table class="table table-bordered text-center">
<thead>
<tr class="active">
<td>Account Type</td>
<td>Transaction Type</td>
<td>Effect on account balance</td>
</tr>
</thead>
<tbody>
<tr>
<td>Debit</td>
<td>Debit</td>
<td>Increases</td>
</tr>
<tr>
<td>Debit</td>
<td>Credit</td>
<td>Decreases</td>
</tr>
<tr>
<td>Credit</td>
<td>Credit</td>
<td>Increases</td>
</tr>
<tr>
<td>Credit</td>
<td>Debit</td>
<td>Decreases</td>
</tr>
</tbody>
</table>
### Double Entry
This means that every accounting entry has two parts, one debit and one credit and must affect two separate accounts. If you add or deduct to one account, some other account somewhere else must also be affected. See the example below:
1. Company sells a laptop worth 50000 to Customer A and delivers that with an invoice.
As the company will receive a payment from customer, the customer is considered as an asset account. For booking income, company maintains an account called "Sales of Laptop". So, entries will be done in the following manner:
<table class="table table-bordered text-center">
<thead>
<tr class="active">
<td>Account</td>
<td>Debit</td>
<td>Credit</td>
</tr>
</thead>
<tbody>
<tr>
<td>Customer A</td>
<td>50000</td>
<td></td>
</tr>
<tr>
<td>Sales of Laptop</td>
<td></td>
<td>50000</td>
</tr>
</tbody>
</table>
Customer A has made the payment, so customer balance should decreased based on the paid amount, which will increase "Cash" balance.
<table class="table table-bordered text-center">
<thead>
<tr class="active">
<td>Account</td>
<td>Debit</td>
<td>Credit</td>
</tr>
</thead>
<tbody>
<tr>
<td>Customer A</td>
<td></td>
<td>50000</td>
</tr>
<tr>
<td>Cash</td>
<td>50000</td>
<td></td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,38 @@
---
{
"_label": "Accounting Entries"
}
---
The concept of accounting is explained with an example given below:
We will take a "Tea Stall" as a company and see how to book accounting entries for the business.
- Mama (The Tea-stall owner) invests Rs 25000 to start the business.
![A&L](img/assets-1.png)
Analysis: Mama invested 25000 in company, hoping to get some profit. In other words, company is liable to pay 25000 to Mama in the future. So, account "Mama" is a liability account and it is credited. Company's cash balance will be increased due to the investment, "Cash" is an asset to the company and it will debited.
- The company needs equipments (Stove, teapot, cups etc) and raw materials (tea, sugar, milk etc) immediately. He decides to buy from the nearest general store "Super Bazaar" who is a friend so that he gets some credit. Equipments cost him 2800 and raw materials worth of 2200. He pays 2000 out of total cost 5000.
![A&L](img/assets-2.png)
Analysis: Equipments are "Fixed Assets" (because they have a long life) of the company and raw materials "Current Assets" (since they are used for day-to-day business), of the company. So, "Equipments" and "Stock in Hand" accounts have been debited to increase the value. He pays 2000, so "Cash" account will be reduced by that amount, hence credited and he is liable to pay 3000 to "Super Bazaar" later, so Super Bazaar will be credited by 3000.
- Mama (who takes care of all entries) decides to book sales at the end of the every day, so that he can analyze daily sales. At the end of the very first day, the tea stall sells 325 cups of tea, which gives net sales of Rs. 1575. The owner happily books his first day sales.
![A&L](img/assets-3.png)
Analysis: Income has been booked in "Sales of Tea" account which has been credited to increase the value and the same amount will be debited to "Cash" account. Lets say, to make 325 cups of tea, it costs Rs. 800, so "Stock in Hand" will be reduced (Cr) by 800 and expense will be booked in "Cost of goods sold" account by same amount.
At the end of the month, the company paid the rent amount of stall (5000) and salary of one employee (8000), who joined from the very first day.
![A&L](img/assets-4.png)
### Booking Profit
As month progress, company purchased more raw materials for the business. After a month he books profit to balance the "Balance Sheet" and "Profit and Loss Statements" statements. Profit belongs to Mama and not the company hence its a liability for the company (it has to pay it to Mama). When the Balance Sheet is not balanced i.e. Debit is not equal to Credit, the profit has not yet been booked. To book profit, the following entry has to be made:
![A&L](img/assets-5.png)
Explanation: Company's net sales and expenses are 40000 and 20000 respectively. So, company made a profit of 20000. To make the profit booking entry, "Profit or Loss" account has been debited and "Capital Account" has been credited. Company's net cash balance is 44000 and there is some raw materials available worth 1000 rupees.

View File

@ -0,0 +1,16 @@
---
{
"_label": "Attachement and CSV files"
}
---
#### How to Attach files?
When you open a form, on the right sidebar, you will see a section to attach files. Click on “Add” and select the file you want to attach. Click on “Upload” and you are set.
#### What is a CSV file?
A CSV (Comma Separated Value) file is a data file that you can upload into ERPNext to update various data. Any spreadsheet file from popular spreadsheet applications like MS Excel or Open Office Spreadsheet can be saved as a CSV file.
If you are using Microsoft Excel and using non-English characters, make sure to save your file encoded as UTF-8. For older versions of Excel, there is no clear way of saving as UTF-8. So save your file as a CSV, then open it in Notepad, and save as “UTF-8”. (Sorry blame Microsoft for this!)

View File

@ -0,0 +1,267 @@
---
{
"_label": "DocType"
}
---
ERPNext is a based on a “metadata” (data about data) framework that helps define all the different types of documents in the system. The basic building block of ERPNext is a DocType.
A DocType represents both a table in the database and a form from which a user can enter data.
Many DocTypes are single tables, but some work in groups. For example, Quotation has a “Quotation” DocType and a “Quotation Item” doctype for the Items table, among others. DocTypes contain a collection of fields called DocFields that form the basis of the columns in the database and the layout of the form.
<table class="table table-bordered text-left">
<thead>
<tr class="active">
<td width="30%">Column</td>
<td>Description</td>
</tr>
</thead>
<tbody>
<tr>
<td>Name</td>
<td>Name of the record</td>
</tr>
<tr>
<td>Owner</td>
<td>Creator and Owner of the record</td>
</tr>
<tr>
<td>Created on</td>
<td>Date and Time of Creation</td>
</tr>
<tr>
<td>Modified On </td>
<td>Date and Time of Modification</td>
</tr>
<tr>
<td>Docstatus</td>
<td>Status of the record<br>
0 = Saved/Draft<br>
1 = Submitted<br>
2 = Cancelled/Deleted
</td>
</tr>
<tr>
<td>Parent</td>
<td>Name of the Parent</td>
</tr>
<tr>
<td>Parent Type</td>
<td>Type of Parent</td>
</tr>
<tr>
<td>Parent Field</td>
<td>Specifying the relationship with the parent (there can be multiple child relationships with the same DocType).</td>
</tr>
<tr>
<td>Index(idx)</td>
<td>Index (sequence) of the record in the child table.</td>
</tr>
</tbody>
</table>
#### Single DocType
There are a certain type of DocTypes that are “Single”, i.e. they have no table associated and have only one record of its fields. DocTypes such as Global Defaults, Production Planning Tool are “Single” DocTypes.
#### Field Columns
In the fields table, there are many columns, here is an explanation of the columns of the field table.
<table class="table table-bordered text-left">
<thead>
<tr class="active">
<td width="30%">Column</td>
<td>Description</td>
</tr>
</thead>
<tbody>
<tr>
<td>Label</td>
<td>Field Label (that appears in the form).</td>
</tr>
<tr>
<td>Type</td>
<td>Field Type</td>
</tr>
<tr>
<td>Name</td>
<td>Column name in the database, must be code friendly with no white spaces, special characters and capital letters.</td>
</tr>
<tr>
<td>options</td>
<td>Field settings:<br>
For Select: List of options (each on a new line).<br>
For Link: DocType that is “linked”.<br>
For HTML: HTML Content
</tr>
<tr>
<td>Perm Level</td>
<td>Permission level (number) of the field. You can group fields by numbers, called levels, and apply rules on the levels.</td>
</tr>
<tr>
<td>Width</td>
<td>Width of the field (in pixels) - useful for “Table” types.</td>
</tr>
<tr>
<td>Reqd</td>
<td>Checked if field is mandatory (required).</td>
</tr>
<tr>
<td>In Filter</td>
<td>Checked if field appears as a standard filter in old style reports.</td>
</tr>
<tr>
<td>Hidden</td>
<td>Checked if field is hidden.</td>
</tr>
<tr>
<td>Print Hide</td>
<td>Checked if field is hidden in Print Formats.</td>
</tr>
<tr>
<td>Report Hide</td>
<td>Checked if field is hidden in old style reports.</td>
</tr>
<tr>
<td>Allow on Submit</td>
<td>Checked if this field can be edited after the document is “Submitted”.</td>
</tr>
<tr>
<td>Depends On</td>
<td>The fieldname of the field that will decide whether this field will be shown or hidden. It is useful to hide un-necessary fields.</td>
</tr>
<tr>
<td>Description</td>
<td>Description of the field</td>
</tr>
<tr>
<td>Default</td>
<td>Default value when a new record is created.<br>
Note: “user” will set the current user as default and “today” will set todays date (if the field is a Date field).</td>
</tr>
<tbody>
<table>
#### Field Types and Options
Here is a list of the different types of fields used to make / customize forms in ERPNext.
<table class="table table-bordered text-left">
<thead>
<tr class="active">
<td width="30%">Type</td>
<td>Description</td>
<td>Options/Setting</td>
</tr>
</thead>
<tbody>
<tr>
<td>Data</td>
<td>Single line text field with 180 characters</td>
<td> </td>
</tr>
<tr>
<td>Select</td>
<td>Select from a pre-determined items in a drop-down.</td>
<td>The “Options” contains the drop-down items, each on a new row</td>
</tr>
<tr>
<td>Link</td>
<td>Link an existing document / record</td>
<td>Options contains the name of the type of document (DocType)</td>
</tr>
<tr>
<td>Currency</td>
<td>Number with 2 decimal places, that will be shown separated by commas for thousands etc. in Print.</td>
<td>e.g. 1,000,000.00</td>
</tr>
<tr>
<td>Float</td>
<td>Number with 6 decimal places.</td>
<td>e.g. 3.141593</td>
</tr>
<tr>
<td>Int</td>
<td>Integer (no decimals)</td>
<td>e.g. 100</td>
</tr>
<tr>
<td>Date</td>
<td>Date</td>
<td>Format can be selected in Global Defaults</td>
</tr>
<tr>
<td>Time</td>
<td>Time</td>
<td></td>
</tr>
<tr>
<td colspan="3" class="active">Text</td>
</tr>
<tr>
<td>Text</td>
<td>Multi-line text box without formatting features</td>
<td></td>
</tr>
<tr>
<td>Text editor</td>
<td>Multi-line text box with formatting toolbar etc</td>
<td></td>
</tr>
<tr>
<td>Code</td>
<td>Code Editor</td>
<td>Options can include the type of language for syntax formatting.
Eg JS / Python / HTML</td>
</tr>
<tr>
<td colspan="3" class="active">Table (Grid)</td>
</tr>
<tr>
<td>Table</td>
<td>Table of child items linked to the record.</td>
<td>Options contains the name of the DocType of the child table. For example “Sales Invoice Item” for “Sales Invoice”</td>
</tr>
<tr>
<td colspan="3" class="active">Layout</td>
</tr>
<tr>
<td>Section Break</td>
<td>Break into a new horizontal section.</td>
<td>The layout in ERPNext is evaluated from top to bottom.</td>
</tr>
<tr>
<td>Column Break</td>
<td>Break into a new vertical column.</td>
<td></td>
</tr>
<tr>
<td>HTML</td>
<td>Add a static text / help / link etc in HTML</td>
<td>Options contains the HTML.</td>
</tr>
<tr>
<td colspan="3" class="active">Action</td>
</tr>
<tr>
<td>Button</td>
<td>Button</td>
<td>[for developers only]</td>
</tr>
<tbody>
<table>

View File

@ -0,0 +1,10 @@
---
{
"_label": "Fiscal-Year"
}
---
A fiscal year is also known as a financial year or a budget year. It is used for calculating financial statements in businesses and other organisations. The fiscal year may or may not be the same as a calendar year. For tax purposes, companies can choose to be calendar-year taxpayers or fiscal-year taxpayers. In many jurisdictions, regulatory laws regarding accounting and taxation require such reports once per twelve months. However, it is not mandatory that the period should be a calendar year (that is, 1 January to 31 December).
A fiscal year usually starts at the beginning of a quarter, such as April 1, July 1 or October 1. However, most companies' fiscal year also coincides with the calendar year, which starts January 1. For the most part, it is simpler and easier that way. For some organizations, there are advantages in starting the fiscal year at a different time. For example, businesses that are seasonal might start their fiscal year on July 1 or October 1. A business that has most of its income in the fall and most of its expenses in the spring might also choose to start its fiscal year on October 1. That way, they know what their income will be for that year, and can adjust their expenses to maintain their desired profit margins.

View File

@ -0,0 +1,84 @@
---
{
"_label": "Format Using Markdown"
}
---
Markdown is a simple way of writing text to format your content. Markdown allows you easy ways to format.
1. Headings (h1 (largest), h2, h3, h4 and so on)
1. Paragraphs
1. Lists (numbered or bulleted)
1. Hyper links (links to other pages)
1. Images
1. Code
1. Embed HTML (HTML tags within your text)
#### Headings
Headings are specified by adding a `#` (hash) at the beginning of the line. The more the number of hashes, the smaller the heading:
# This is a large heading.
### This is a smaller heading.
#### Paragraphs
To start a new paragraph, just make sure that there is an empty line at the beginning and end of the paragraph.
To format text as **bold** or with _italics_ format as follows:
**This text** is **bold** and _this one_ is with _italics_
#### Lists
To define numbered lists, start your link with a number and a dot (.) and ensure there is a blank line before and after the list. The numbers are automatically generated so it does not matter what number you put:
1. list 1st item
1. second item
1. and so on
1. and so forth
To define bulleted lists, start your items with a hyphen (-)
- item 1
- item 2
- item 3
To nest lists within one another, put four spaces to indent your inner list as follows:
1. item 1
1. item 2
- sub item 1
- sub item 2
1. item 3
#### Links (to other pages)
Links to other pages can be defined by adding your text in box brackets [] followed by the link in round brackets ()
[This is an external link](http://example.com)
[A link within the site](my-page.html)
#### Images
Images can be added by adding an exclamation ! before the link.
![A flower](files/flower.gif)
#### Code
To add a code block, just leave a blank line before and after the block and make sure all code line are indented by four spaces:
This is normal text
This is a code block
#### HTML
You can embed any kind of HTML tags within your code. Any content written within HTML tags will not be formatted.
[Detailed description of the markdown format](http://daringfireball.net/projects/markdown/syntax)

View File

@ -0,0 +1,15 @@
---
{
"_label": "Knowledge Library",
"_toc": [
"docs.user.knowledge.fiscal_year",
"docs.user.knowledge.accounting",
"docs.user.knowledge.accounting_entries",
"docs.user.knowledge.doctype",
"docs.user.knowledge.attachment_csv",
"docs.user.knowledge.markdown"
]
}
---
Knowledge Library contains definitions and explanations of various management concepts. This page is created for users who wish to elaborate their conceptual knowledge.

View File

@ -3,16 +3,26 @@
"_label": "Bill of Materials"
}
---
At the heart of the Manufacturing system is the **Bill of Materials** (BOM). The **BOM** is a list of all material (either bought or made) and operations that go into a finished product or sub-Item. In ERPNext, the component could have its own BOM hence forming a tree of Items with multiple levels.
At the heart of the Manufacturing system is the **Bill of Materials** (BOM). The **BOM** is a list of all materials (either bought or made) and operations that go into a finished product or sub-Item. In ERPNext, the component could have its own BOM hence forming a tree of Items with multiple levels.
To make accurate Purchase Requests, you must always maintain your correct BOMs. To make a new BOM:
To make accurate Purchase Requests, you must always maintain correct BOMs. To make a new BOM:
> Manufacturing > Bill of Materials > New BOM
![Bill of Materials](img/bom.png)
In the BOM form:
- Select the Item for which you want to make the BOM.
- Add the operations that you have to go through to make that particular Item in the “Operations” table. For each operation, you will be asked to enter a Workstation. You must create new Workstations as and when necessary.
![Bill of Materials with Operations](img/mfg-bom-3.png)
- Add the list of Items you require for each operation, with its quantity. This Item could be a purchased Item or a sub-assembly with its own BOM. If the row Item is a manufactured Item and has multiple BOMs, select the appropriate BOM.You can also define if a part of the Item goes into scrap.
Workstations are defined only for product costing purposes not inventory. Inventory is tracked in Warehouses not Workstations.

View File

@ -8,13 +8,13 @@
]
}
---
The Manufacturing module in ERPNext helps you to maintain multi-level Bill of Materials (BOMs) for your Items. It helps you in Product Costing, planing your production via Production Plan, creating Production Orders for your manufacturing shop floor,s and planing your inventory by getting your material requirement via BOMs (also called Material Requirements Planning MRP).
The Manufacturing module in ERPNext helps you to maintain multi-level Bill of Materials (BOMs) for your Items. It helps you in Product Costing, planing your production via Production Plan, creating Production Orders for your manufacturing shop floors and planing your inventory by getting your material requirement via BOMs (also called Material Requirements Planning MRP).
### Types of Production Planning
Broadly there are three types of Production Planning Systems
- Make-to-Stock: In these systems, production is planned based on a forecast and then the Items are sold to distributors or customers. All fast moving consumer goods that are sold in retail shops like soaps, packaged water etc and electronics like phones etc are Made to Stock.
- Make-to-Stock: In these systems, production is planned based on a forecast and the Items are then sold to distributors or customers. All fast moving consumer goods that are sold in retail shops like soaps, packaged water etc and electronics like phones etc are Made to Stock.
- Make-to-Order: In these systems, manufacturing takes place after a firm order is placed by a Customer.
- Engineer-to-Order: In this case each sale is a separate Project and has to be designed and engineered to the requirements of the Customer. Common examples of this are any custom business like furniture, machine tools, speciality devices, metal fabrication etc.
@ -26,7 +26,7 @@ For engineer-to-order systems, the Manufacturing module should be used along wit
You can track work-in-progress by creating work-in-progress Warehouses.
ERPNext will help you track material movement by automatically creating Stock Entries from your Production Orders by building form Bill of Materials.
ERPNext will help you track material movement by automatically creating Stock Entries from your Production Orders by building from Bill of Materials.
---
@ -35,7 +35,7 @@ ERPNext will help you track material movement by automatically creating Stock En
The earliest ERP systems were made for manufacturing. The earliest adopters were automobile companies who had thousands of raw materials and sub-assemblies and found it very hard to keep track of requirements and plan purchases. They started using computers to build the material requirements from forecasts and Bill of Materials.
Later these systems were expanded to include Financial, Payroll, Order Processing and Purchasing and became the more generic Enterprise Resource Systems (ERP). More recently Customer Relationship Management (CRM) was added as a function and is now an integral part of ERP systems.
Later these systems were expanded to include Finances, Payroll, Order Processing, and Purchasing and thus became the more generic Enterprise Resource Systems (ERP). More recently Customer Relationship Management (CRM) was added as a function and is now an integral part of ERP systems.
These days the term ERP is used to describe systems that help manage any kind of organization like education institutes (Education ERP) or Hospitals (Hospital ERP) and so on.
@ -43,7 +43,7 @@ These days the term ERP is used to describe systems that help manage any kind of
### Best Practice: Lean Manufacturing
The state of art manufacturing philosophy (the rationale behind the planning processes) comes from Japanese auto major Toyota. At the time when American manufacturers depended on MRP systems to plan their manufacturing based on their sales forecasts, they turned the problem on its head and discovered a leaner way of planning their production. They realized that:
The state of art manufacturing philosophy (the rationale behind the planning processes) comes from Japanese auto major Toyota. At the time when American manufacturers depended on MRP systems to plan their manufacturing based on their sales forecasts, they turned around the problem by discovering a leaner way of planning their production. They realized that:
The biggest cause of wastage in manufacturing is variation (in product and quantity).
@ -53,6 +53,4 @@ Their card signaling system kanban, would notify all their suppliers to stop pro
They combined this system with neatly managed factories with well labeled racks.
Like we discussed before, small manufacturing companies are usually make-to-order or engineer-to-order and can hardly afford to have a high level of standardization. But that should be the aim. Small manufacturing businesses should aim for repeatability by innovating processes so that there is a common platform for products.
Small manufacturing companies are usually make-to-order or engineer-to-order and can hardly afford to have a high level of standardization. Thus small manufacturing businesses should aim for repeatability by innovating processes and creating a common platform for products.

View File

@ -14,6 +14,11 @@ To use the Production Planning Tool, go to:
> Manufacturing > Production Planning Tool
![Production Planning Tool](img/production-planning-tool.png)
The Production Planning Tool is used in two stages:
- Selection of Open Sales Orders for the period based on “Expected Delivery Date”.

View File

@ -9,6 +9,11 @@ The **Production Order** is generated directly from the **Production Planning To
> Manufacturing > Production Order > New Production Order
![Production Order](img/production-order.png)
- Select the Item to be produced (must have a Bill of Materials).
- Select the BOM
- Select Quantities
@ -19,9 +24,13 @@ and “Submit” the Production Order.
Once you “Submit”, you will see two more buttons:
1. Make Transfer: This will create a Stock Entry with all the Items required to complete this Production Order to be added to the WIP Warehouse. (this will add sub-Items with BOM as one Item or explode their children based on your setting above).
1. Back Flush: This will create a Stock Entry that will deduct all the sub-Items from the WIP Warehouse and add them to the Finished Goods Warehouse.
![Production Order](img/production-order-2.png)
When you Back Flush your Items back to the Finished Goods Warehouse, the “Produced Quantity” will be updated in the Production Order.
> Tip: You can also partially complete a Production Order by updating the Finished Goods stock creating a Stock Entry and selecting “Back flush” as the type.
1. Transfer Raw Material: This will create a Stock Entry with all the Items required to complete this Production Order to be added to the WIP Warehouse. (this will add sub-Items with BOM as one Item or explode their children based on your setting above).
1. Update Finished Goods: This will create a Stock Entry that will deduct all the sub-Items from the WIP Warehouse and add them to the Finished Goods Warehouse.
> Tip: You can also partially complete a Production Order by updating the Finished Goods stock creating a Stock Entry.
When you Update Finished Goods to the Finished Goods Warehouse, the “Produced Quantity” will be updated in the Production Order.

View File

@ -4,7 +4,9 @@
"_title_image": "img/customers.png"
}
---
You can either directly create your Customers via
A customer, who is sometimes known as a client, buyer, or purchaser is the one who receives goods, services, products, or ideas, from a seller for a monetary consideration. A customer can also receive goods or services from a vendor or a supplier for other valuable considerations.
You can either directly create your Customers via
> Selling > Customer

Some files were not shown because too many files have changed in this diff Show More