diff --git a/accounts/doctype/account/account.txt b/accounts/doctype/account/account.txt index a0354c20d7..e515b34f3f 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" }, @@ -232,6 +232,15 @@ "options": "[Select]", "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", @@ -262,13 +271,21 @@ "print_hide": 1, "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 eeb9c057fd..9d53994e25 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 @@ class DocType: 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 @@ class DocType: 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 0000000000..059ab63918 --- /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 3c7fad2dd6..80cdb4df11 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -164,4 +164,5 @@ patch_list = [ "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