From 4a7248ee0b188f104b251eb7cfbb0ab30ae6de5f Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Wed, 27 Feb 2013 18:10:30 +0530 Subject: [PATCH] commonified some purchase code, added test case for auto inventory accounting for purchase invoice --- .../purchase_invoice/purchase_invoice.py | 139 +++++++++++------- .../purchase_invoice/test_purchase_invoice.py | 85 ++++++++++- controllers/accounts_controller.py | 9 +- controllers/buying_controller.py | 12 +- setup/doctype/company/company.py | 6 + .../purchase_receipt/purchase_receipt.py | 13 +- .../purchase_receipt/test_purchase_receipt.py | 2 - 7 files changed, 186 insertions(+), 80 deletions(-) diff --git a/accounts/doctype/purchase_invoice/purchase_invoice.py b/accounts/doctype/purchase_invoice/purchase_invoice.py index 950cac38ea..4e53d04201 100644 --- a/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -35,7 +35,7 @@ class DocType(BuyingController): self.fname = 'entries' def get_credit_to(self): - acc_head = sql("select name, credit_days from `tabAccount` where (name = %s or (master_name = %s and master_type = 'supplier')) and docstatus != 2", (cstr(self.doc.supplier) + " - " + self.get_company_abbr(),self.doc.supplier)) + acc_head = sql("select name, credit_days from `tabAccount` where (name = %s or (master_name = %s and master_type = 'supplier')) and docstatus != 2", (cstr(self.doc.supplier) + " - " + self.company_abbr,self.doc.supplier)) ret = {} if acc_head and acc_head[0][0]: @@ -54,20 +54,32 @@ class DocType(BuyingController): ret['due_date'] = add_days(cstr(self.doc.posting_date), acc and cint(acc[1]) or 0) return ret - - def get_default_values(self,args): - import json - args = json.loads(args) - ret = {} - if sql("select name from `tabItem` where name = '%s'" % args['item_code']): - if not args['expense_head'] or args['expense_head'] == 'undefined': - expense_head = sql("select name from `tabAccount` where account_name in (select purchase_account from `tabItem` where name = '%s')" % args['item_code']) - ret['expense_head'] = expense_head and expense_head[0][0] or '' - if not args['cost_center'] or args['cost_center'] == 'undefined': - cost_center = sql("select cost_center from `tabItem` where name = '%s'" % args['item_code']) - ret['cost_center'] = cost_center and cost_center[0][0] or '' - return ret - + + def get_default_values(self, args): + if isinstance(args, basestring): + import json + args = json.loads(args) + + out = webnotes._dict() + + item = webnotes.conn.sql("""select name, purchase_account, cost_center, + is_stock_item from `tabItem` where name=%s""", args.get("item_code"), as_dict=1) + + if item and item[0]: + item = item[0] + + if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) and \ + item.is_stock_item == "Yes": + # unset expense head for stock item and auto inventory accounting + out.expense_head = out.cost_center = None + else: + if not args.get("expense_head"): + out.expense_head = item.purchase_account + if not args.get("cost_center"): + out.cost_center = item.cost_center + + return out + def pull_details(self): if self.doc.purchase_receipt_main: self.validate_duplicate_docname('purchase_receipt') @@ -86,9 +98,7 @@ class DocType(BuyingController): def get_expense_account(self, doctype): for d in getlist(self.doclist, doctype): if d.item_code: - item = webnotes.conn.sql("select purchase_account, cost_center from tabItem where name = '%s'" %(d.item_code), as_dict=1) - d.expense_head = item and item[0]['purchase_account'] or '' - d.cost_center = item and item[0]['cost_center'] or '' + d.fields.update(self.get_default_values(d.fields)) def get_advances(self): super(DocType, self).get_advances(self.doc.credit_to, @@ -110,9 +120,6 @@ class DocType(BuyingController): ret={'add_tax_rate' :rate and flt(rate[0][0]) or 0 } return ret - def get_company_abbr(self): - return sql("select abbr from tabCompany where name=%s", self.doc.company)[0][0] - def validate_duplicate_docname(self,doctype): for d in getlist(self.doclist, 'entries'): if doctype == 'purchase_receipt' and cstr(self.doc.purchase_receipt_main) == cstr(d.purchase_receipt): @@ -186,7 +193,7 @@ class DocType(BuyingController): if self.doc.supplier and self.doc.credit_to: acc_head = sql("select master_name from `tabAccount` where name = %s", self.doc.credit_to) - if (acc_head and cstr(acc_head[0][0]) != cstr(self.doc.supplier)) or (not acc_head and (self.doc.credit_to != cstr(self.doc.supplier) + " - " + self.get_company_abbr())): + if (acc_head and cstr(acc_head[0][0]) != cstr(self.doc.supplier)) or (not acc_head and (self.doc.credit_to != cstr(self.doc.supplier) + " - " + self.company_abbr)): msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.doc.credit_to,self.doc.supplier,self.doc.company), raise_exception=1) # Check for Stopped PO @@ -256,11 +263,25 @@ class DocType(BuyingController): raise Exception def set_against_expense_account(self): - against_acc = [] - for d in getlist(self.doclist, 'entries'): - if d.expense_account not in against_acc: - against_acc.append(d.expense_account) - self.doc.against_expense_account = ','.join(against_acc) + auto_inventory_accounting = \ + cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) + stock_not_billed_account = "Stock Received But Not Billed - %s" % self.company_abbr + + against_accounts = [] + for item in self.doclist.get({"parentfield": "entries"}): + if auto_inventory_accounting and item.item_code in self.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 + + if stock_not_billed_account not in against_accounts: + against_accounts.append(stock_not_billed_account) + + elif item.expense_head not in against_accounts: + # if no auto_inventory_accounting or not a stock item + against_accounts.append(item.expense_head) + + self.doc.against_expense_account = ",".join(against_accounts) def po_required(self): res = sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = 'po_required'") @@ -320,6 +341,8 @@ class DocType(BuyingController): self.doc.posting_date,'Posting Date') self.validate_write_off_account() + + self.update_valuation_rate("entries") def check_prev_docstatus(self): for d in getlist(self.doclist,'entries'): @@ -382,11 +405,10 @@ class DocType(BuyingController): def make_gl_entries(self, is_cancel = 0): from accounts.general_ledger import make_gl_entries + auto_inventory_accounting = \ + cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) + gl_entries = [] - valuation_tax = 0 - auto_inventory_accounting = webnotes.conn.get_value("Global Defaults", None, - "auto_inventory_accounting") - abbr = self.get_company_abbr() # parent's gl entry if self.doc.grand_total: @@ -402,11 +424,9 @@ class DocType(BuyingController): ) # tax table gl entries - for tax in getlist(self.doclist, "purchase_tax_details"): + valuation_tax = 0 + for tax in self.doclist.get({"parentfield": "purchase_tax_details"}): if tax.category in ("Total", "Valuation and Total") and flt(tax.tax_amount): - valuation_tax += tax.add_deduct_tax == "Add" \ - and flt(tax.tax_amount) or -1 * flt(tax.tax_amount) - gl_entries.append( self.get_gl_dict({ "account": tax.account_head, @@ -417,25 +437,31 @@ class DocType(BuyingController): "cost_center": tax.cost_center }, is_cancel) ) + + # 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) # item gl entries - stock_item_and_auto_accounting = False + stock_item_and_auto_inventory_accounting = False for item in self.doclist.get({"parentfield": "entries"}): - if auto_inventory_accounting and flt(item.valuation_rate) and \ - webnotes.conn.get_value("Item", item.item_code, "is_stock_item")=="Yes": - # if auto inventory accounting enabled and stock item, - # then do stock related gl entries, expense will be booked in sales invoice - gl_entries.append( - self.get_gl_dict({ - "account": "Stock Received But Not Billed - %s" % (abbr,), - "against": self.doc.credit_to, - "debit": flt(item.valuation_rate) * flt(item.conversion_factor) \ - * item.qty, - "remarks": self.doc.remarks or "Accounting Entry for Stock" - }, is_cancel) - ) - - stock_item_and_auto_accounting = True + if auto_inventory_accounting and item.item_code in self.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 + + gl_entries.append( + self.get_gl_dict({ + "account": "Stock Received But Not Billed - %s" % (self.company_abbr,), + "against": self.doc.credit_to, + "debit": flt(item.valuation_rate) * flt(item.conversion_factor) \ + * flt(item.qty), + "remarks": self.doc.remarks or "Accounting Entry for Stock" + }, is_cancel) + ) elif flt(item.amount): # if not a stock item or auto inventory accounting disabled, book the expense @@ -449,13 +475,13 @@ class DocType(BuyingController): }, is_cancel) ) - if stock_item_and_auto_accounting and valuation_tax: + if stock_item_and_auto_inventory_accounting 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": "Expenses Included In Valuation - %s" % (abbr,), - "cost_center": "ERP - %s" % abbr, # to-do + "account": "Expenses Included In Valuation - %s" % (self.company_abbr,), + "cost_center": "Auto Inventory Accounting - %s" % self.company_abbr, "against": self.doc.credit_to, "credit": valuation_tax, "remarks": self.doc.remarks or "Accounting Entry for Stock" @@ -464,12 +490,12 @@ class DocType(BuyingController): # writeoff account includes petty difference in the invoice amount # and the amount that is paid - if self.doc.write_off_account and self.doc.write_off_amount: + if self.doc.write_off_account and flt(self.doc.write_off_amount): gl_entries.append( self.get_gl_dict({ "account": self.doc.write_off_account, "against": self.doc.credit_to, - "credit": self.doc.write_off_amount, + "credit": flt(self.doc.write_off_amount), "remarks": self.doc.remarks, "cost_center": self.doc.write_off_cost_center }, is_cancel) @@ -478,7 +504,6 @@ class DocType(BuyingController): if gl_entries: make_gl_entries(gl_entries, cancel=is_cancel) - def check_next_docstatus(self): submit_jv = sql("select t1.name from `tabJournal Voucher` t1,`tabJournal Voucher Detail` t2 where t1.name = t2.parent and t2.against_voucher = '%s' and t1.docstatus = 1" % (self.doc.name)) if submit_jv: diff --git a/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0944b81e35..c1c0540824 100644 --- a/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -20,11 +20,16 @@ import unittest import webnotes import webnotes.model import json +from webnotes.utils import cint +import webnotes.defaults test_dependencies = ["Item", "Cost Center"] 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"))) + wrapper = webnotes.bean(copy=test_records[0]) wrapper.run_method("calculate_taxes_and_totals") wrapper.insert() @@ -49,7 +54,36 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) def test_gl_entries_with_auto_inventory_accounting(self): + print "Testing with auto inventory" + webnotes.defaults.set_global_default("auto_inventory_accounting", 1) + self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) + pi = webnotes.bean(copy=test_records[1]) + pi.run_method("calculate_taxes_and_totals") + pi.insert() + pi.submit() + + print "auto inventory submitted" + + gl_entries = webnotes.conn.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s + order by account desc""", pi.doc.name, as_dict=1) + self.assertTrue(gl_entries) + + expected_values = [ + ["_Test Supplier - _TC", 0, 720], + ["Stock Received But Not Billed - _TC", 750.0, 0], + ["_Test Account Shipping Charges - _TC", 100.0, 0], + ["_Test Account VAT - _TC", 120.0, 0], + ["Expenses Included In Valuation - _TC", 0, 250.0] + ].sort() + + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_values[i][0], gle.account) + self.assertEquals(expected_values[i][1], gle.debit) + self.assertEquals(expected_values[i][2], gle.credit) + + webnotes.defaults.set_global_default("auto_inventory_accounting", 0) def test_purchase_invoice_calculation(self): wrapper = webnotes.bean(copy=test_records[0]) @@ -115,7 +149,8 @@ test_records = [ "uom": "_Test UOM", "item_tax_rate": json.dumps({"_Test Account Excise Duty - _TC": 10}), "expense_head": "_Test Account Cost for Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC" + "cost_center": "_Test Cost Center - _TC", + "conversion_factor": 1.0, }, { @@ -130,7 +165,8 @@ test_records = [ "amount": 750, "uom": "_Test UOM", "expense_head": "_Test Account Cost for Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC" + "cost_center": "_Test Cost Center - _TC", + "conversion_factor": 1.0, }, # taxes { @@ -237,7 +273,8 @@ test_records = [ "fiscal_year": "_Test Fiscal Year 2013", "company": "_Test Company", "currency": "INR", - "conversion_rate": 1, + "conversion_rate": 1.0, + "grand_total_import": 0 # for feed }, # items { @@ -245,12 +282,46 @@ test_records = [ "parentfield": "entries", "item_code": "_Test Item", "item_name": "_Test Item", - "qty": 10, - "import_rate": 50, + "qty": 10.0, + "import_rate": 50.0, "uom": "_Test UOM", "expense_head": "_Test Account Cost for Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC" - + "cost_center": "_Test Cost Center - _TC", + "conversion_factor": 1.0, + }, + # taxes + { + "doctype": "Purchase Taxes and Charges", + "parentfield": "purchase_tax_details", + "charge_type": "Actual", + "account_head": "_Test Account Shipping Charges - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Shipping Charges", + "category": "Valuation and Total", + "add_deduct_tax": "Add", + "rate": 100.0 + }, + { + "doctype": "Purchase Taxes and Charges", + "parentfield": "purchase_tax_details", + "charge_type": "Actual", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "category": "Total", + "add_deduct_tax": "Add", + "rate": 120.0 + }, + { + "doctype": "Purchase Taxes and Charges", + "parentfield": "purchase_tax_details", + "charge_type": "Actual", + "account_head": "_Test Account Customs Duty - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Customs Duty", + "category": "Valuation", + "add_deduct_tax": "Add", + "rate": 150.0 }, ] ] \ No newline at end of file diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py index 47a0138fa8..aa52b5e412 100644 --- a/controllers/accounts_controller.py +++ b/controllers/accounts_controller.py @@ -86,4 +86,11 @@ class AccountsController(TransactionBase): from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \ (", ".join((["%s"]*len(item_codes))),), item_codes)] - return self._stock_items \ No newline at end of file + return self._stock_items + + @property + def company_abbr(self): + if not hasattr(self, "_abbr"): + self._abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr") + + return self._abbr \ No newline at end of file diff --git a/controllers/buying_controller.py b/controllers/buying_controller.py index 2514bfc956..8a230e1bb8 100644 --- a/controllers/buying_controller.py +++ b/controllers/buying_controller.py @@ -232,7 +232,7 @@ class BuyingController(AccountsController): else: self.doc.grand_total = flt(self.doc.net_total, self.precision.main.grand_total) - self.doc.grand_total_print = flt( + self.doc.grand_total_import = flt( self.doc.grand_total / self.doc.conversion_rate, self.precision.main.grand_total_import) @@ -327,6 +327,16 @@ class BuyingController(AccountsController): item.item_code in self.stock_items: item.item_tax_amount += flt(current_tax_amount, self.precision.item.item_tax_amount) + + # update valuation rate + def update_valuation_rate(self, parentfield): + for d in self.doclist.get({"parentfield": parentfield}): + if d.qty: + d.valuation_rate = (flt(d.purchase_rate or d.rate) + + (flt(d.item_tax_amount) + flt(d.rm_supp_cost)) / flt(d.qty) + ) / flt(d.conversion_factor) + else: + d.valuation_rate = 0.0 @property def precision(self): diff --git a/setup/doctype/company/company.py b/setup/doctype/company/company.py index 552dae2a9a..9cf722fca4 100644 --- a/setup/doctype/company/company.py +++ b/setup/doctype/company/company.py @@ -211,6 +211,12 @@ class DocType: 'company_name':self.doc.name, 'group_or_ledger':'Ledger', 'parent_cost_center':'Root - ' + self.doc.abbr + }, + { + 'cost_center_name': 'Auto Inventory Accounting', + 'company_name': self.doc.name, + 'group_or_ledger': 'Ledger', + 'parent_cost_center': 'Root - ' + self.doc.abbr } ] for cc in cc_list: diff --git a/stock/doctype/purchase_receipt/purchase_receipt.py b/stock/doctype/purchase_receipt/purchase_receipt.py index a957468032..f3f453e685 100644 --- a/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/stock/doctype/purchase_receipt/purchase_receipt.py @@ -89,16 +89,6 @@ class DocType(BuyingController): webnotes.msgprint("Another Purchase Receipt using the same Challan No. already exists.\ Please enter a valid Challan No.", raise_exception=1) - - # update valuation rate - def update_valuation_rate(self): - for d in self.doclist.get({"parentfield": "purchase_receipt_details"}): - if d.qty: - d.valuation_rate = (flt(d.purchase_rate) + flt(d.item_tax_amount)/flt(d.qty) - + flt(d.rm_supp_cost) / flt(d.qty)) / flt(d.conversion_factor) - else: - d.valuation_rate = 0.0 - #check in manage account if purchase order required or not. # ==================================================================================== def po_required(self): @@ -134,8 +124,7 @@ class DocType(BuyingController): pc_obj.validate_reference_value(self) self.check_for_stopped_status(pc_obj) - # update valuation rate - self.update_valuation_rate() + self.update_valuation_rate("purchase_receipt_details") # On Update diff --git a/stock/doctype/purchase_receipt/test_purchase_receipt.py b/stock/doctype/purchase_receipt/test_purchase_receipt.py index d833d65b2b..f99fe7fb78 100644 --- a/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -36,7 +36,6 @@ class TestPurchaseReceipt(unittest.TestCase): def test_purchase_receipt_gl_entry(self): webnotes.defaults.set_global_default("auto_inventory_accounting", 1) - self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) pr = webnotes.bean(copy=test_records[0]) @@ -47,7 +46,6 @@ class TestPurchaseReceipt(unittest.TestCase): gl_entries = webnotes.conn.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s order by account desc""", pr.doc.name, as_dict=1) - self.assertTrue(gl_entries) stock_in_hand_account = webnotes.conn.get_value("Company", pr.doc.company,