From b4a2eb2e65b0706ba7483215040855297b9b9ff8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 24 Aug 2022 12:29:15 +0530 Subject: [PATCH] fix: gl entries for asset repair --- erpnext/assets/doctype/asset/test_asset.py | 14 +- .../doctype/asset_repair/asset_repair.js | 2 +- .../doctype/asset_repair/asset_repair.json | 6 +- .../doctype/asset_repair/asset_repair.py | 146 +++++++++++------- .../doctype/asset_repair/test_asset_repair.py | 122 ++++++++++++++- erpnext/setup/doctype/company/company.json | 10 +- 6 files changed, 222 insertions(+), 78 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 986b7001ff..132840e38c 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1454,12 +1454,14 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass return item -def set_depreciation_settings_in_company(): - company = frappe.get_doc("Company", "_Test Company") - company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC" - company.depreciation_expense_account = "_Test Depreciations - _TC" - company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" - company.depreciation_cost_center = "_Test Cost Center - _TC" +def set_depreciation_settings_in_company(company=None): + if not company: + company = "_Test Company" + company = frappe.get_doc("Company", company) + company.accumulated_depreciation_account = "_Test Accumulated Depreciations - " + company.abbr + company.depreciation_expense_account = "_Test Depreciations - " + company.abbr + company.disposal_account = "_Test Gain/Loss on Asset Disposal - " + company.abbr + company.depreciation_cost_center = "Main - " + company.abbr company.save() # Enable booking asset depreciation entry automatically diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index f5e4e723b4..f9ed2cc344 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -76,7 +76,7 @@ frappe.ui.form.on('Asset Repair Consumed Item', { 'warehouse': frm.doc.warehouse, 'qty': item.consumed_quantity, 'serial_no': item.serial_no, - 'company': frm.doc.company + 'company': frm.doc.company, }; frappe.call({ diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index ba3189887c..accb5bf54b 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -238,7 +238,6 @@ "no_copy": 1 }, { - "depends_on": "eval:!doc.__islocal", "fieldname": "purchase_invoice", "fieldtype": "Link", "label": "Purchase Invoice", @@ -257,6 +256,7 @@ "fieldname": "stock_entry", "fieldtype": "Link", "label": "Stock Entry", + "no_copy": 1, "options": "Stock Entry", "read_only": 1 } @@ -264,10 +264,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-25 13:14:38.307723", + "modified": "2022-08-16 15:55:25.023471", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -303,6 +304,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "asset_name", "track_changes": 1, "track_seen": 1 diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 5bf6011cf8..b4316ced62 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -1,11 +1,11 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - import frappe from frappe import _ from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours +import erpnext from erpnext.accounts.general_ledger import make_gl_entries from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.controllers.accounts_controller import AccountsController @@ -17,7 +17,7 @@ class AssetRepair(AccountsController): self.update_status() if self.get("stock_items"): - self.set_total_value() + self.set_stock_items_cost() self.calculate_total_repair_cost() def update_status(self): @@ -26,7 +26,7 @@ class AssetRepair(AccountsController): else: self.asset_doc.set_status() - def set_total_value(self): + def set_stock_items_cost(self): for item in self.get("stock_items"): item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity) @@ -66,6 +66,7 @@ class AssetRepair(AccountsController): if self.get("capitalize_repair_cost"): self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.make_gl_entries(cancel=True) + self.db_set("stock_entry", None) if ( frappe.db.get_value("Asset", self.asset, "calculate_depreciation") and self.increase_in_asset_life @@ -133,6 +134,7 @@ class AssetRepair(AccountsController): "qty": stock_item.consumed_quantity, "basic_rate": stock_item.valuation_rate, "serial_no": stock_item.serial_no, + "cost_center": self.cost_center, }, ) @@ -142,72 +144,42 @@ class AssetRepair(AccountsController): self.db_set("stock_entry", stock_entry.name) def increase_stock_quantity(self): - stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) - stock_entry.flags.ignore_links = True - stock_entry.cancel() + if self.stock_entry: + stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) + stock_entry.flags.ignore_links = True + stock_entry.cancel() def make_gl_entries(self, cancel=False): - if flt(self.repair_cost) > 0: + if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() make_gl_entries(gl_entries, cancel) def get_gl_entries(self): gl_entries = [] - repair_and_maintenance_account = frappe.db.get_value( - "Company", self.company, "repair_and_maintenance_account" - ) + fixed_asset_account = get_asset_account( "fixed_asset_account", asset=self.asset, company=self.company ) - expense_account = ( + self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account) + self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account) + + return gl_entries + + def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account): + if flt(self.repair_cost) <= 0: + return + + pi_expense_account = ( frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account ) - gl_entries.append( - self.get_gl_dict( - { - "account": expense_account, - "credit": self.repair_cost, - "credit_in_account_currency": self.repair_cost, - "against": repair_and_maintenance_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": getdate(), - "company": self.company, - }, - item=self, - ) - ) - - if self.get("stock_consumption"): - # creating GL Entries for each row in Stock Items based on the Stock Entry created for it - stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) - for item in stock_entry.items: - gl_entries.append( - self.get_gl_dict( - { - "account": item.expense_account, - "credit": item.amount, - "credit_in_account_currency": item.amount, - "against": repair_and_maintenance_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": getdate(), - "company": self.company, - }, - item=self, - ) - ) - gl_entries.append( self.get_gl_dict( { "account": fixed_asset_account, - "debit": self.total_repair_cost, - "debit_in_account_currency": self.total_repair_cost, - "against": expense_account, + "debit": self.repair_cost, + "debit_in_account_currency": self.repair_cost, + "against": pi_expense_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, @@ -220,7 +192,75 @@ class AssetRepair(AccountsController): ) ) - return gl_entries + gl_entries.append( + self.get_gl_dict( + { + "account": pi_expense_account, + "credit": self.repair_cost, + "credit_in_account_currency": self.repair_cost, + "against": fixed_asset_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "company": self.company, + }, + item=self, + ) + ) + + def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account): + if not (self.get("stock_consumption") and self.get("stock_items")): + return + + # creating GL Entries for each row in Stock Items based on the Stock Entry created for it + stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) + + default_expense_account = None + if not erpnext.is_perpetual_inventory_enabled(self.company): + default_expense_account = frappe.get_cached_value( + "Company", self.company, "default_expense_account" + ) + if not default_expense_account: + frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company)) + + for item in stock_entry.items: + if flt(item.amount) > 0: + gl_entries.append( + self.get_gl_dict( + { + "account": item.expense_account or default_expense_account, + "credit": item.amount, + "credit_in_account_currency": item.amount, + "against": fixed_asset_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "company": self.company, + }, + item=self, + ) + ) + + gl_entries.append( + self.get_gl_dict( + { + "account": fixed_asset_account, + "debit": item.amount, + "debit_in_account_currency": item.amount, + "against": item.expense_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "against_voucher_type": "Stock Entry", + "against_voucher": self.stock_entry, + "company": self.company, + }, + item=self, + ) + ) def modify_depreciation_schedule(self): for row in self.asset_doc.finance_books: diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 4e7cf78090..6e06f52ac6 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -6,6 +6,7 @@ import unittest import frappe from frappe.utils import flt, nowdate +from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.assets.doctype.asset.test_asset import ( create_asset, create_asset_data, @@ -125,10 +126,109 @@ class TestAssetRepair(unittest.TestCase): asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1) self.assertTrue(asset_repair.purchase_invoice) - def test_gl_entries(self): - asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1) - gl_entry = frappe.get_last_doc("GL Entry") - self.assertEqual(asset_repair.name, gl_entry.voucher_no) + def test_gl_entries_with_perpetual_inventory(self): + set_depreciation_settings_in_company(company="_Test Company with perpetual inventory") + + asset_category = frappe.get_doc("Asset Category", "Computers") + asset_category.append( + "accounts", + { + "company_name": "_Test Company with perpetual inventory", + "fixed_asset_account": "_Test Fixed Asset - TCP1", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1", + "depreciation_expense_account": "_Test Depreciations - TCP1", + }, + ) + asset_category.save() + + asset_repair = create_asset_repair( + capitalize_repair_cost=1, + stock_consumption=1, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + submit=1, + ) + + gl_entries = frappe.db.sql( + """ + select + account, + sum(debit) as debit, + sum(credit) as credit + from `tabGL Entry` + where + voucher_type='Asset Repair' + and voucher_no=%s + group by + account + """, + asset_repair.name, + as_dict=1, + ) + + self.assertTrue(gl_entries) + + fixed_asset_account = get_asset_account( + "fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company + ) + pi_expense_account = ( + frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account + ) + stock_entry_expense_account = ( + frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account + ) + + expected_values = { + fixed_asset_account: [asset_repair.total_repair_cost, 0], + pi_expense_account: [0, asset_repair.repair_cost], + stock_entry_expense_account: [0, 100], + } + + for d in gl_entries: + self.assertEqual(expected_values[d.account][0], d.debit) + self.assertEqual(expected_values[d.account][1], d.credit) + + def test_gl_entries_with_periodical_inventory(self): + frappe.db.set_value( + "Company", "_Test Company", "default_expense_account", "Cost of Goods Sold - _TC" + ) + asset_repair = create_asset_repair( + capitalize_repair_cost=1, + stock_consumption=1, + submit=1, + ) + + gl_entries = frappe.db.sql( + """ + select + account, + sum(debit) as debit, + sum(credit) as credit + from `tabGL Entry` + where + voucher_type='Asset Repair' + and voucher_no=%s + group by + account + """, + asset_repair.name, + as_dict=1, + ) + + self.assertTrue(gl_entries) + + fixed_asset_account = get_asset_account( + "fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company + ) + default_expense_account = frappe.get_cached_value( + "Company", asset_repair.company, "default_expense_account" + ) + + expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]} + + for d in gl_entries: + self.assertEqual(expected_values[d.account][0], d.debit) + self.assertEqual(expected_values[d.account][1], d.credit) def test_increase_in_asset_life(self): asset = create_asset(calculate_depreciation=1, submit=1) @@ -160,7 +260,7 @@ def create_asset_repair(**args): if args.asset: asset = args.asset else: - asset = create_asset(is_existing_asset=1, submit=1) + asset = create_asset(is_existing_asset=1, submit=1, company=args.company) asset_repair = frappe.new_doc("Asset Repair") asset_repair.update( { @@ -192,7 +292,7 @@ def create_asset_repair(**args): if args.submit: asset_repair.repair_status = "Completed" - asset_repair.cost_center = "_Test Cost Center - _TC" + asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center") if args.stock_consumption: stock_entry = frappe.get_doc( @@ -204,6 +304,8 @@ def create_asset_repair(**args): "t_warehouse": asset_repair.warehouse, "item_code": asset_repair.stock_items[0].item_code, "qty": asset_repair.stock_items[0].consumed_quantity, + "basic_rate": args.rate if args.get("rate") is not None else 100, + "cost_center": asset_repair.cost_center, }, ) stock_entry.submit() @@ -213,7 +315,13 @@ def create_asset_repair(**args): asset_repair.repair_cost = 1000 if asset.calculate_depreciation: asset_repair.increase_in_asset_life = 12 - asset_repair.purchase_invoice = make_purchase_invoice().name + pi = make_purchase_invoice( + company=asset.company, + expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"), + cost_center=asset_repair.cost_center, + warehouse=asset_repair.warehouse, + ) + asset_repair.purchase_invoice = pi.name asset_repair.submit() return asset_repair diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index f34ec56dc0..f087d996ff 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -85,7 +85,6 @@ "depreciation_expense_account", "series_for_depreciation_entry", "expenses_included_in_asset_valuation", - "repair_and_maintenance_account", "column_break_40", "disposal_account", "depreciation_cost_center", @@ -234,7 +233,6 @@ "label": "Default Warehouse for Sales Return", "options": "Warehouse" }, - { "fieldname": "country", "fieldtype": "Link", @@ -678,12 +676,6 @@ "fieldtype": "Section Break", "label": "Fixed Asset Defaults" }, - { - "fieldname": "repair_and_maintenance_account", - "fieldtype": "Link", - "label": "Repair and Maintenance Account", - "options": "Account" - }, { "fieldname": "section_break_28", "fieldtype": "Section Break", @@ -709,7 +701,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2022-06-30 18:03:18.701314", + "modified": "2022-08-16 16:09:02.327724", "modified_by": "Administrator", "module": "Setup", "name": "Company",