diff --git a/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/accounts/doctype/period_closing_voucher/period_closing_voucher.py index c214a211c0..99282f57f1 100644 --- a/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -3,179 +3,102 @@ from __future__ import unicode_literals import webnotes - from webnotes.utils import cstr, flt, getdate -from webnotes.model import db_exists -from webnotes.model.doc import Document -from webnotes.model.bean import copy_doclist -from webnotes.model.code import get_obj -from webnotes import msgprint +from webnotes import msgprint, _ +from controllers.accounts_controller import AccountsController -sql = webnotes.conn.sql - - - -class DocType: +class DocType(AccountsController): def __init__(self,d,dl): self.doc, self.doclist = d, dl - self.td, self.tc = 0, 0 self.year_start_date = '' - self.year_end_date = '' + def validate(self): + self.validate_account_head() + self.validate_posting_date() + self.validate_pl_balances() + + def on_submit(self): + self.make_gl_entries() + + def on_cancel(self): + webnotes.conn.sql("""delete from `tabGL Entry` + where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.doc.name) def validate_account_head(self): - acc_det = sql("select debit_or_credit, is_pl_account, group_or_ledger, company \ - from `tabAccount` where name = '%s'" % (self.doc.closing_account_head)) - - # Account should be under liability - if cstr(acc_det[0][0]) != 'Credit' or cstr(acc_det[0][1]) != 'No': - msgprint("Account: %s must be created under 'Source of Funds'" % self.doc.closing_account_head) - raise Exception - - # Account must be a ledger - if cstr(acc_det[0][2]) != 'Ledger': - msgprint("Account %s must be a ledger" % self.doc.closing_account_head) - raise Exception - - # Account should belong to company selected - if cstr(acc_det[0][3]) != self.doc.company: - msgprint("Account %s does not belong to Company %s ." % (self.doc.closing_account_head, self.doc.company)) - raise Exception - + debit_or_credit, is_pl_account = webnotes.conn.get_value("Account", + self.doc.closing_account_head, ["debit_or_credit", "is_pl_account"]) + + if debit_or_credit != 'Credit' or is_pl_account != 'No': + webnotes.throw(_("Account") + ": " + self.doc.closing_account_head + + _("must be a Liability account")) def validate_posting_date(self): - yr = sql("""select year_start_date, adddate(year_start_date, interval 1 year) - from `tabFiscal Year` where name=%s""", (self.doc.fiscal_year, )) - self.year_start_date = yr and yr[0][0] or '' - self.year_end_date = yr and yr[0][1] or '' - - # Posting Date should be within closing year - if getdate(self.doc.posting_date) < getdate(self.year_start_date) or getdate(self.doc.posting_date) > getdate(self.year_end_date): - msgprint("Posting Date should be within Closing Fiscal Year") - raise Exception + from accounts.utils import get_fiscal_year + self.year_start_date = get_fiscal_year(self.doc.posting_date)[1] - # Period Closing Entry - pce = sql("select name from `tabPeriod Closing Voucher` \ - where posting_date > '%s' and fiscal_year = '%s' and docstatus = 1" \ - % (self.doc.posting_date, self.doc.fiscal_year)) + pce = webnotes.conn.sql("""select name from `tabPeriod Closing Voucher` + where posting_date > %s and fiscal_year = %s and docstatus = 1""", + (self.doc.posting_date, self.doc.fiscal_year)) if pce and pce[0][0]: - msgprint("Another Period Closing Entry: %s has been made after posting date: %s"\ - % (cstr(pce[0][0]), self.doc.posting_date)) - raise Exception + webnotes.throw(_("Another Period Closing Entry") + ": " + cstr(pce[0][0]) + + _("has been made after posting date") + ": " + self.doc.posting_date) - def validate_pl_balances(self): - income_bal = sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \ - from `tabGL Entry` t1, tabAccount t2 where t1.account = t2.name \ - and t1.posting_date between '%s' and '%s' and t2.debit_or_credit = 'Credit' \ - and t2.group_or_ledger = 'Ledger' and t2.is_pl_account = 'Yes' and t2.docstatus < 2 \ - and t2.company = '%s'" % (self.year_start_date, self.doc.posting_date, self.doc.company)) + income_bal = webnotes.conn.sql(""" + select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) + from `tabGL Entry` t1, tabAccount t2 + where t1.account = t2.name and t1.posting_date between %s and %s + and t2.debit_or_credit = 'Credit' and t2.is_pl_account = 'Yes' + and t2.docstatus < 2 and t2.company = %s""", + (self.year_start_date, self.doc.posting_date, self.doc.company)) - expense_bal = sql("select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \ - from `tabGL Entry` t1, tabAccount t2 where t1.account = t2.name \ - and t1.posting_date between '%s' and '%s' and t2.debit_or_credit = 'Debit' \ - and t2.group_or_ledger = 'Ledger' and t2.is_pl_account = 'Yes' and t2.docstatus < 2 \ - and t2.company = '%s'" % (self.year_start_date, self.doc.posting_date, self.doc.company)) + expense_bal = webnotes.conn.sql(""" + select sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) + from `tabGL Entry` t1, tabAccount t2 + where t1.account = t2.name and t1.posting_date between %s and %s + and t2.debit_or_credit = 'Debit' and t2.is_pl_account = 'Yes' + and t2.docstatus < 2 and t2.company=%s""", + (self.year_start_date, self.doc.posting_date, self.doc.company)) income_bal = income_bal and income_bal[0][0] or 0 expense_bal = expense_bal and expense_bal[0][0] or 0 if not income_bal and not expense_bal: - msgprint("Both Income and Expense balances are zero. No Need to make Period Closing Entry.") - raise Exception + webnotes.throw(_("Both Income and Expense balances are zero. \ + No Need to make Period Closing Entry.")) + def get_pl_balances(self): + """Get balance for pl accounts""" - def get_pl_balances(self, d_or_c): - """Get account (pl) specific balance""" - acc_bal = sql("select t1.account, sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) \ - from `tabGL Entry` t1, `tabAccount` t2 where t1.account = t2.name and t2.group_or_ledger = 'Ledger' \ - and ifnull(t2.is_pl_account, 'No') = 'Yes' and ifnull(is_cancelled, 'No') = 'No' \ - and t2.debit_or_credit = '%s' and t2.docstatus < 2 and t2.company = '%s' \ - and t1.posting_date between '%s' and '%s' group by t1.account " \ - % (d_or_c, self.doc.company, self.year_start_date, self.doc.posting_date)) - return acc_bal - + return webnotes.conn.sql(""" + select t1.account, sum(ifnull(t1.debit,0))-sum(ifnull(t1.credit,0)) as balance + from `tabGL Entry` t1, `tabAccount` t2 + where t1.account = t2.name and ifnull(t2.is_pl_account, 'No') = 'Yes' + and t2.docstatus < 2 and t2.company = %s + and t1.posting_date between %s and %s + group by t1.account + """, (self.doc.company, self.year_start_date, self.doc.posting_date), as_dict=1) - def make_gl_entries(self, acc_det): - for a in acc_det: - if flt(a[1]): - fdict = { - 'account': a[0], - 'cost_center': '', - 'against': '', - 'debit': flt(a[1]) < 0 and -1*flt(a[1]) or 0, - 'credit': flt(a[1]) > 0 and flt(a[1]) or 0, - 'remarks': self.doc.remarks, - 'voucher_type': self.doc.doctype, - 'voucher_no': self.doc.name, - 'transaction_date': self.doc.transaction_date, - 'posting_date': self.doc.posting_date, - 'fiscal_year': self.doc.fiscal_year, - 'against_voucher': '', - 'against_voucher_type': '', - 'company': self.doc.company, - 'is_opening': 'No', - 'aging_date': self.doc.posting_date - } + def make_gl_entries(self): + gl_entries = [] + net_pl_balance = 0 + pl_accounts = self.get_pl_balances() + for acc in pl_accounts: + if flt(acc.balance): + gl_entries.append(self.get_gl_dict({ + "account": acc.account, + "debit": abs(flt(acc.balance)) if flt(acc.balance) < 0 else 0, + "credit": abs(flt(acc.balance)) if flt(acc.balance) > 0 else 0, + })) - self.save_entry(fdict) - + net_pl_balance += flt(acc.balance) - def save_entry(self, fdict, is_cancel = 'No'): - # Create new GL entry object and map values - le = Document('GL Entry') - for k in fdict: - le.fields[k] = fdict[k] - - le_obj = get_obj(doc=le) - # validate except on_cancel - if is_cancel == 'No': - le_obj.validate() + if net_pl_balance: + gl_entries.append(self.get_gl_dict({ + "account": self.doc.closing_account_head, + "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, + "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0 + })) - # update total debit / credit except on_cancel - self.td += flt(le.credit) - self.tc += flt(le.debit) - - # save - le.save(1) - le_obj.on_update(adv_adj = '', cancel = '') - - - def validate(self): - # validate account head - self.validate_account_head() - - # validate posting date - self.validate_posting_date() - - # check if pl balance: - self.validate_pl_balances() - - - def on_submit(self): - - # Makes closing entries for Expense Account - in_acc_det = self.get_pl_balances('Credit') - self.make_gl_entries(in_acc_det) - - # Makes closing entries for Expense Account - ex_acc_det = self.get_pl_balances('Debit') - self.make_gl_entries(ex_acc_det) - - - # Makes Closing entry for Closing Account Head - bal = self.tc - self.td - self.make_gl_entries([[self.doc.closing_account_head, flt(bal)]]) - - - def on_cancel(self): - # get all submit entries of current closing entry voucher - gl_entries = sql("select account, debit, credit from `tabGL Entry` where voucher_type = 'Period Closing Voucher' and voucher_no = '%s' and ifnull(is_cancelled, 'No') = 'No'" % (self.doc.name)) - - # Swap Debit & Credit Column and make gl entry - for gl in gl_entries: - fdict = {'account': gl[0], 'cost_center': '', 'against': '', 'debit': flt(gl[2]), 'credit' : flt(gl[1]), 'remarks': "cancelled", 'voucher_type': self.doc.doctype, 'voucher_no': self.doc.name, 'transaction_date': self.doc.transaction_date, 'posting_date': self.doc.posting_date, 'fiscal_year': self.doc.fiscal_year, 'against_voucher': '', 'against_voucher_type': '', 'company': self.doc.company, 'is_opening': 'No', 'aging_date': 'self.doc.posting_date'} - self.save_entry(fdict, is_cancel = 'Yes') - - # Update is_cancelled = 'Yes' to all gl entries for current voucher - sql("update `tabGL Entry` set is_cancelled = 'Yes' where voucher_type = '%s' and voucher_no = '%s'" % (self.doc.doctype, self.doc.name)) \ No newline at end of file + from accounts.general_ledger import make_gl_entries + make_gl_entries(gl_entries) \ No newline at end of file diff --git a/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py new file mode 100644 index 0000000000..c21d63f6ec --- /dev/null +++ b/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -0,0 +1,53 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +# License: GNU General Public License v3. See license.txt + + +from __future__ import unicode_literals +import unittest +import webnotes + +class TestPeriodClosingVoucher(unittest.TestCase): + def test_closing_entry(self): + from accounts.doctype.journal_voucher.test_journal_voucher import test_records as jv_records + jv = webnotes.bean(copy=jv_records[2]) + jv.insert() + jv.submit() + + jv1 = webnotes.bean(copy=jv_records[0]) + jv1.doclist[2].account = "_Test Account Cost for Goods Sold - _TC" + jv1.doclist[2].debit = 600.0 + jv1.doclist[1].credit = 600.0 + jv1.insert() + jv1.submit() + + pcv = webnotes.bean(copy=test_record) + pcv.insert() + pcv.submit() + + gl_entries = webnotes.conn.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type='Period Closing Voucher' and voucher_no=%s + order by account asc, debit asc""", pcv.doc.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_gl_entries = sorted([ + ["_Test Account Reserves and Surplus - _TC", 200.0, 0.0], + ["_Test Account Cost for Goods Sold - _TC", 0.0, 600.0], + ["Sales - _TC", 400.0, 0.0] + ]) + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_gl_entries[i][0], gle.account) + self.assertEquals(expected_gl_entries[i][1], gle.debit) + self.assertEquals(expected_gl_entries[i][2], gle.credit) + + +test_dependencies = ["Customer", "Cost Center"] + +test_record = [{ + "doctype": "Period Closing Voucher", + "closing_account_head": "_Test Account Reserves and Surplus - _TC", + "company": "_Test Company", + "fiscal_year": "_Test Fiscal Year 2013", + "posting_date": "2013-03-31", + "remarks": "test" +}]