new feature: validation on account negative balance
diff --git a/accounts/doctype/account/account.txt b/accounts/doctype/account/account.txt
index a0354c2..e515b34 100644
--- a/accounts/doctype/account/account.txt
+++ b/accounts/doctype/account/account.txt
@@ -1,8 +1,8 @@
 [
  {
-  "creation": "2013-01-19 10:23:33", 
+  "creation": "2013-01-30 12:49:46", 
   "docstatus": 0, 
-  "modified": "2013-01-29 16:27:57", 
+  "modified": "2013-02-05 15:38:32", 
   "modified_by": "Administrator", 
   "owner": "Administrator"
  }, 
@@ -233,6 +233,15 @@
   "permlevel": 0
  }, 
  {
+  "default": "1", 
+  "depends_on": "eval:doc.group_or_ledger==\"Ledger\"", 
+  "doctype": "DocField", 
+  "fieldname": "allow_negative_balance", 
+  "fieldtype": "Check", 
+  "label": "Allow Negative Balance", 
+  "permlevel": 0
+ }, 
+ {
   "doctype": "DocField", 
   "fieldname": "lft", 
   "fieldtype": "Int", 
@@ -263,12 +272,20 @@
   "read_only": 1
  }, 
  {
+  "cancel": 1, 
+  "create": 1, 
+  "doctype": "DocPerm", 
+  "permlevel": 0, 
+  "role": "Accounts User", 
+  "write": 1
+ }, 
+ {
   "cancel": 0, 
   "create": 0, 
   "doctype": "DocPerm", 
   "permlevel": 0, 
   "role": "Auditor", 
-  "write": 1
+  "write": 0
  }, 
  {
   "cancel": 0, 
@@ -290,9 +307,9 @@
   "cancel": 0, 
   "create": 0, 
   "doctype": "DocPerm", 
-  "permlevel": 0, 
-  "role": "Accounts User", 
-  "write": 1
+  "permlevel": 2, 
+  "role": "Auditor", 
+  "write": 0
  }, 
  {
   "cancel": 1, 
@@ -309,5 +326,13 @@
   "permlevel": 2, 
   "role": "Accounts Manager", 
   "write": 1
+ }, 
+ {
+  "cancel": 0, 
+  "create": 0, 
+  "doctype": "DocPerm", 
+  "permlevel": 2, 
+  "role": "Accounts User", 
+  "write": 0
  }
 ]
\ No newline at end of file
diff --git a/accounts/doctype/gl_entry/gl_entry.py b/accounts/doctype/gl_entry/gl_entry.py
index eeb9c05..9d53994 100644
--- a/accounts/doctype/gl_entry/gl_entry.py
+++ b/accounts/doctype/gl_entry/gl_entry.py
@@ -17,9 +17,9 @@
 from __future__ import unicode_literals
 import webnotes
 
-from webnotes.utils import flt, fmt_money, get_first_day, get_last_day, getdate
+from webnotes.utils import flt, fmt_money, getdate
 from webnotes.model.code import get_obj
-from webnotes import msgprint
+from webnotes import msgprint, _
 
 sql = webnotes.conn.sql
 	
@@ -27,122 +27,6 @@
 	def __init__(self,d,dl):
 		self.doc, self.doclist = d, dl
 
-	# Validate mandatory
-	#-------------------
-	def check_mandatory(self):
-		# Following fields are mandatory in GL Entry
-		mandatory = ['account','remarks','voucher_type','voucher_no','fiscal_year','company']
-		for k in mandatory:
-			if not self.doc.fields.get(k):
-				msgprint("%s is mandatory for GL Entry" % k, raise_exception=1)
-				
-		# 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 %s" % self.doc.account)
-			raise Exception
-			
-	def pl_must_have_cost_center(self):
-		if sql("select name from tabAccount where name=%s and is_pl_account='Yes'", self.doc.account):
-			if not self.doc.cost_center and self.doc.voucher_type != 'Period Closing Voucher':
-				msgprint("Error: Cost Center must be specified for PL Account: %s" %
-				 	self.doc.account, raise_exception=1)
-		else: # not pl
-			if self.doc.cost_center:
-				self.doc.cost_center = ''
-		
-	# Account must be ledger, active and not freezed
-	#-----------------------------------------------
-	def validate_account_details(self, adv_adj):
-		ret = sql("select group_or_ledger, docstatus, freeze_account, company from tabAccount where name=%s", self.doc.account)
-		
-		# 1. Checks whether Account type is group or ledger
-		if ret and ret[0][0]=='Group':
-			msgprint("Error: All accounts must be Ledgers. Account %s is a group" % self.doc.account)
-			raise Exception
-
-		# 2. Checks whether Account is active
-		if ret and ret[0][1]==2:
-			msgprint("Error: All accounts must be Active. Account %s moved to Trash" % self.doc.account)
-			raise Exception
-			
-		# 3. Account has been freezed for other users except account manager
-		if ret and ret[0][2]== 'Yes' and not adv_adj and not 'Accounts Manager' in webnotes.user.get_roles():
-			msgprint("Error: Account %s has been freezed. Only Accounts Manager can do transaction against this account." % self.doc.account)
-			raise Exception
-			
-		# 4. Check whether account is within the company
-		if ret and ret[0][3] != self.doc.company:
-			msgprint("Account: %s does not belong to the company: %s" % (self.doc.account, self.doc.company))
-			raise Exception
-			
-	# Posting date must be in selected fiscal year and fiscal year is active
-	#-------------------------------------------------------------------------
-	def validate_posting_date(self):
-		fy = sql("select docstatus, year_start_date from `tabFiscal Year` where name=%s ", self.doc.fiscal_year)
-		ysd = fy[0][1]
-		yed = get_last_day(get_first_day(ysd,0,11))
-		pd = getdate(self.doc.posting_date)
-		if fy[0][0] == 2:
-			msgprint("Fiscal Year is not active. You can restore it from Trash")
-			raise Exception
-		if pd < ysd or pd > yed:
-			msgprint("Posting date must be in the Selected Financial Year")
-			raise Exception
-			
-	
-	# Nobody can do GL Entries where posting date is before freezing date except authorized person
-	#----------------------------------------------------------------------------------------------
-	def check_freezing_date(self, adv_adj):
-		if not adv_adj:
-			acc_frozen_upto = webnotes.conn.get_value('Global Defaults', None, 'acc_frozen_upto')
-			if acc_frozen_upto:
-				bde_auth_role = webnotes.conn.get_value( 'Global Defaults', 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 accounting entries before %s." % getdate(acc_frozen_upto).strftime('%d-%m-%Y'), raise_exception=1)
-
-	def update_outstanding_amt(self):
-		# get final outstanding amt
-		bal = flt(sql("select sum(debit)-sum(credit) from `tabGL Entry` where against_voucher=%s and against_voucher_type=%s and ifnull(is_cancelled,'No') = 'No'", (self.doc.against_voucher, self.doc.against_voucher_type))[0][0] or 0.0)
-		
-		if self.doc.against_voucher_type=='Purchase Invoice':
-			# amount to debit
-			bal = -bal
-			
-		# Validation : Outstanding can not be negative
-		if bal < 0 and self.doc.is_cancelled == 'No':
-			msgprint("""Outstanding for Voucher %s will become %s. 
-				Outstanding cannot be less than zero. Please match exact outstanding.""" % 
-				 (self.doc.against_voucher, fmt_money(bal)))
-			raise Exception
-			
-		# Update outstanding amt on against voucher
-		sql("update `tab%s` set outstanding_amount=%s where name='%s'"%
-		 	(self.doc.against_voucher_type, bal, self.doc.against_voucher))
-		
-					
-	# Total outstanding can not be greater than credit limit for any time for any customer
-	#---------------------------------------------------------------------------------------------
-	def check_credit_limit(self):
-		#check for user role Freezed
-		master_type=sql("select master_type, master_name from `tabAccount` where name='%s' " %self.doc.account)
-		tot_outstanding = 0	#needed when there is no GL Entry in the system for that acc head
-		if (self.doc.voucher_type=='Journal Voucher' or self.doc.voucher_type=='Sales Invoice') and (master_type and master_type[0][0]=='Customer' and master_type[0][1]):
-			dbcr = sql("select sum(debit),sum(credit) from `tabGL Entry` where account = '%s' and is_cancelled='No'" % self.doc.account)
-			if dbcr:
-				tot_outstanding = flt(dbcr[0][0])-flt(dbcr[0][1])+flt(self.doc.debit)-flt(self.doc.credit)
-			get_obj('Account',self.doc.account).check_credit_limit(self.doc.account, self.doc.company, tot_outstanding)
-	
-	#for opening entry account can not be pl account
-	#-----------------------------------------------
-	def check_pl_account(self):
-		if self.doc.is_opening=='Yes':
-			is_pl_account=sql("select is_pl_account from `tabAccount` where name='%s'"%(self.doc.account))
-			if is_pl_account and is_pl_account[0][0]=='Yes':
-				msgprint("For opening balance entry account can not be a PL account")
-				raise Exception
-
-	# Validate
-	# --------
 	def validate(self):	# not called on cancel
 		self.check_mandatory()
 		self.pl_must_have_cost_center()
@@ -151,15 +35,131 @@
 		self.check_credit_limit()
 		self.check_pl_account()
 
-	# On Update
-	#----------
 	def on_update(self,adv_adj, cancel, update_outstanding = 'Yes'):
-		# Account must be ledger, active and not freezed
 		self.validate_account_details(adv_adj)
-		
-		# Posting date must be after freezing date
 		self.check_freezing_date(adv_adj)
+		self.check_negative_balance(adv_adj)
 
 		# Update outstanding amt on against voucher
-		if self.doc.against_voucher and self.doc.against_voucher_type not in ('Journal Voucher','POS') and update_outstanding == 'Yes':
+		if self.doc.against_voucher and self.doc.against_voucher_type not in \
+				('Journal Voucher','POS') and update_outstanding == 'Yes':
 			self.update_outstanding_amt()
+
+	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)
+				
+		# 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)
+			
+	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 = ""
+		
+	def validate_posting_date(self):
+		from accounts.utils import get_fiscal_year
+		fiscal_year = get_fiscal_year(self.doc.posting_date)[0]
+		
+		if fiscal_year != self.doc.fiscal_year:
+			msgprint(_("Posting date must be in the Selected Fiscal Year"), raise_exception=1)
+
+	def check_credit_limit(self):
+		master_type, master_name = webnotes.conn.get_value("Account", 
+			self.doc.account, ["master_type", "master_name"])
+			
+		tot_outstanding = 0	#needed when there is no GL Entry in the system for that acc head
+		if (self.doc.voucher_type=='Journal Voucher' or self.doc.voucher_type=='Sales Invoice') \
+				and (master_type =='Customer' and master_name):
+			dbcr = sql("""select sum(debit), sum(credit) from `tabGL Entry` 
+				where account = '%s' and is_cancelled='No'""" % self.doc.account)
+			if dbcr:
+				tot_outstanding = flt(dbcr[0][0]) - flt(dbcr[0][1]) + \
+					flt(self.doc.debit) - flt(self.doc.credit)
+			get_obj('Account',self.doc.account).check_credit_limit(self.doc.account, 
+				self.doc.company, tot_outstanding)
+
+	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)			
+
+	def validate_account_details(self, adv_adj):
+		"""Account must be ledger, active and not freezed"""
+		
+		ret = sql("""select group_or_ledger, docstatus, freeze_account, company 
+			from tabAccount where name=%s""", self.doc.account, as_dict=1)
+		
+		if ret and ret[0]["group_or_ledger"]=='Group':
+			msgprint(_("Account: ") + self.doc.account + _(" is not a ledger"), raise_exception=1)
+
+		if ret and ret[0]["docstatus"]==2:
+			msgprint(_("Account: ") + self.doc.account + _(" is not active"), raise_exception=1)
+			
+		# 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 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)
+
+	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('Global Defaults', None, 'acc_frozen_upto')
+			if acc_frozen_upto:
+				bde_auth_role = webnotes.conn.get_value( 'Global Defaults', 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)
+						
+	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 \
+					balance[0][0] or -1*balance[0][0]
+			
+				if flt(balance) < 0:
+					msgprint(_("Negative balance is not allowed for account ") + self.doc.account, 
+						raise_exception=1)
+
+	def update_outstanding_amt(self):
+		# get final outstanding amt
+		bal = flt(sql("""select sum(debit) - sum(credit) from `tabGL Entry` 
+			where against_voucher=%s and against_voucher_type=%s 
+			and ifnull(is_cancelled,'No') = 'No'""", 
+			(self.doc.against_voucher, self.doc.against_voucher_type))[0][0] or 0.0)
+		
+		if self.doc.against_voucher_type=='Purchase Invoice':
+			# amount to debit
+			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
+		sql("update `tab%s` set outstanding_amount=%s where name='%s'"%
+		 	(self.doc.against_voucher_type, bal, self.doc.against_voucher))
\ No newline at end of file
diff --git a/patches/february_2013/account_negative_balance.py b/patches/february_2013/account_negative_balance.py
new file mode 100644
index 0000000..059ab63
--- /dev/null
+++ b/patches/february_2013/account_negative_balance.py
@@ -0,0 +1,4 @@
+def execute():
+	import webnotes
+	webnotes.reload_doc("accounts", "doctype", "Account")
+	webnotes.conn.sql("update `tabAccount` set allow_negative_balance = 1")
\ No newline at end of file
diff --git a/patches/patch_list.py b/patches/patch_list.py
index 3c7fad2..80cdb4d 100644
--- a/patches/patch_list.py
+++ b/patches/patch_list.py
@@ -164,4 +164,5 @@
 	"patches.february_2013.reload_bom_replace_tool_permission",
 	"patches.february_2013.payment_reconciliation_reset_values",
 	"patches.february_2013.remove_sales_order_pending_items",
+	"patches.february_2013.account_negative_balance",
 ]
\ No newline at end of file