diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4cc9be5b05d..2bb5a099080 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -702,9 +702,9 @@ def create_asset(**args): "company": args.company or"_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, - "gross_purchase_amount": 100000, - "purchase_receipt_amount": 100000, - "expected_value_after_useful_life": 10000, + "gross_purchase_amount": args.asset_value or 100000, + "purchase_receipt_amount": args.asset_value or 100000, + "expected_value_after_useful_life": args.asset_value or 10000, "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": "2020-06-06", "location": "Test Location", diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index da128467fe7..9bfc88b28ca 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -1,9 +1,334 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe import unittest +import frappe +from frappe.utils import cint, flt, getdate, now_datetime + +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries +from erpnext.assets.doctype.asset.test_asset import ( + create_asset, + create_asset_data, + set_depreciation_settings_in_company, +) +from erpnext.stock.doctype.item.test_item import create_item + class TestAssetCapitalization(unittest.TestCase): - pass + def setUp(self): + set_depreciation_settings_in_company() + create_asset_data() + create_asset_capitalization_data() + frappe.db.sql("delete from `tabTax Rule`") + + def test_capitalization(self): + # Variables + consumed_asset_value = 100_000 + + stock_rate = 1000 + stock_qty = 2 + stock_amount = 2000 + + service_rate = 500 + service_qty = 2 + service_amount = 1000 + + total_amount = 103_000 + + # Create assets + target_asset = create_asset(asset_name='Asset Capitalization Target Asset', submit=1) + consumed_asset = create_asset(asset_name='Asset Capitalization Consumable Asset', asset_value=consumed_asset_value, + submit=1) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization(target_asset=target_asset.name, + stock_qty=stock_qty, stock_rate=stock_rate, + consumed_asset=consumed_asset.name, + service_qty=service_qty, service_rate=service_rate, + service_expense_account='Expenses Included In Asset Valuation - _TC', + submit=1) + + # Test Asset Capitalization values + self.assertEqual(asset_capitalization.entry_type, 'Capitalization') + self.assertEqual(asset_capitalization.target_qty, 1) + + self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate) + self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount) + self.assertEqual(asset_capitalization.stock_items_total, stock_amount) + + self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value) + self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value) + + self.assertEqual(asset_capitalization.service_items[0].amount, service_amount) + self.assertEqual(asset_capitalization.service_items_total, service_amount) + + self.assertEqual(asset_capitalization.total_value, total_amount) + self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) + + # Test Target Asset values + target_asset.reload() + self.assertEqual(target_asset.gross_purchase_amount, total_amount) + self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + + # Test Consumed Asset values + self.assertEqual(consumed_asset.db_get('status'), 'Capitalized') + + # Test General Ledger Entries + expected_gle = { + '_Test Fixed Asset - _TC': 3000, + 'Expenses Included In Asset Valuation - _TC': -1000, + 'Stock In Hand - _TC' : -2000 + } + actual_gle = get_actual_gle_dict(asset_capitalization.name) + + self.assertEqual(actual_gle, expected_gle) + + # Test Stock Ledger Entries + expected_sle = { + ('Capitalization Source Stock Item', '_Test Warehouse - _TC'): { + 'actual_qty': -stock_qty, 'stock_value_difference': -stock_amount + } + } + actual_sle = get_actual_sle_dict(asset_capitalization.name) + + self.assertEqual(actual_sle, expected_sle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.assertEqual(consumed_asset.db_get('status'), 'Submitted') + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + + def test_decapitalization_with_depreciation(self): + # Variables + purchase_date = '2020-01-01' + depreciation_start_date = '2020-12-31' + capitalization_date = '2021-06-30' + + total_number_of_depreciations = 3 + expected_value_after_useful_life = 10_000 + consumed_asset_purchase_value = 100_000 + consumed_asset_current_value = 70_000 + consumed_asset_value_before_disposal = 55_000 + + target_qty = 10 + target_incoming_rate = 5500 + + depreciation_before_disposal_amount = 15_000 + accumulated_depreciation = 45_000 + + # to accomodate for depreciation on disposal calculation bugs TODO remove this when bug is fixed + consumed_asset_value_before_disposal = 60_082.19 + target_incoming_rate = 6008.219 + depreciation_before_disposal_amount = 9917.81 + accumulated_depreciation = 39_917.81 + + # Create assets + consumed_asset = create_depreciation_asset( + asset_name='Asset Capitalization Consumable Asset', + asset_value=consumed_asset_purchase_value, + purchase_date=purchase_date, + depreciation_start_date=depreciation_start_date, + depreciation_method='Straight Line', + total_number_of_depreciations=total_number_of_depreciations, + frequency_of_depreciation=12, + expected_value_after_useful_life=expected_value_after_useful_life, + submit=1) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization( + posting_date=capitalization_date, # half a year + target_item_code="Capitalization Target Stock Item", + target_qty=target_qty, + consumed_asset=consumed_asset.name, + submit=1) + + # Test Asset Capitalization values + self.assertEqual(asset_capitalization.entry_type, 'Decapitalization') + + self.assertEqual(asset_capitalization.asset_items[0].current_asset_value, consumed_asset_current_value) + self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value_before_disposal) + self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value_before_disposal) + + self.assertEqual(asset_capitalization.total_value, consumed_asset_value_before_disposal) + self.assertEqual(asset_capitalization.target_incoming_rate, target_incoming_rate) + + # Test Consumed Asset values + consumed_asset.reload() + self.assertEqual(consumed_asset.status, 'Decapitalized') + + consumed_depreciation_schedule = [d for d in consumed_asset.schedules + if getdate(d.schedule_date) == getdate(capitalization_date)] + self.assertTrue(consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry) + self.assertEqual(consumed_depreciation_schedule[0].depreciation_amount, depreciation_before_disposal_amount) + + # Test General Ledger Entries + expected_gle = { + 'Stock In Hand - _TC': consumed_asset_value_before_disposal, + '_Test Accumulated Depreciations - _TC': accumulated_depreciation, + '_Test Fixed Asset - _TC': -consumed_asset_purchase_value, + } + actual_gle = get_actual_gle_dict(asset_capitalization.name) + + self.assertEqual(actual_gle, expected_gle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.assertEqual(consumed_asset.db_get('status'), 'Partially Depreciated') + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + + +def create_asset_capitalization_data(): + create_item("Capitalization Target Stock Item", + is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) + create_item("Capitalization Source Stock Item", + is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) + create_item("Capitalization Source Service Item", + is_stock_item=0, is_fixed_asset=0, is_purchase_item=0) + + +def create_asset_capitalization(**args): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + args = frappe._dict(args) + + now = now_datetime() + target_asset = frappe.get_doc("Asset", args.target_asset) if args.target_asset else frappe._dict() + target_item_code = target_asset.item_code or args.target_item_code + company = target_asset.company or args.company or "_Test Company" + warehouse = args.warehouse or create_warehouse("_Test Warehouse", company=company) + target_warehouse = args.target_warehouse or warehouse + source_warehouse = args.source_warehouse or warehouse + + asset_capitalization = frappe.new_doc("Asset Capitalization") + asset_capitalization.update({ + "company": company, + "posting_date": args.posting_date or now.strftime('%Y-%m-%d'), + "posting_time": args.posting_time or now.strftime('%H:%M:%S.%f'), + "target_item_code": target_item_code, + "target_asset": target_asset.name, + "target_warehouse": target_warehouse, + "target_qty": flt(args.target_qty) or 1, + "target_batch_no": args.target_batch_no, + "target_serial_no": args.target_serial_no, + "finance_book": args.finance_book + }) + + if args.posting_date or args.posting_time: + asset_capitalization.set_posting_time = 1 + + if flt(args.stock_rate): + asset_capitalization.append("stock_items", { + "item_code": args.stock_item or "Capitalization Source Stock Item", + "warehouse": source_warehouse, + "stock_qty": flt(args.stock_qty) or 1, + "batch_no": args.stock_batch_no, + "serial_no": args.stock_serial_no, + }) + + if args.consumed_asset: + asset_capitalization.append("asset_items", { + "asset": args.consumed_asset, + }) + + if flt(args.service_rate): + asset_capitalization.append("service_items", { + "item_code": args.service_item or "Capitalization Source Service Item", + "expense_account": args.service_expense_account, + "qty": flt(args.service_qty) or 1, + "rate": flt(args.service_rate) + }) + + if args.submit: + create_stock_reconciliation(asset_capitalization, stock_rate=args.stock_rate) + + asset_capitalization.insert() + + if args.submit: + asset_capitalization.submit() + + return asset_capitalization + + +def create_stock_reconciliation(asset_capitalization, stock_rate=0): + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, + ) + if not asset_capitalization.get('stock_items'): + return + + return create_stock_reconciliation( + item_code=asset_capitalization.stock_items[0].item_code, + warehouse=asset_capitalization.stock_items[0].warehouse, + qty=flt(asset_capitalization.stock_items[0].stock_qty), + rate=flt(stock_rate), + company=asset_capitalization.company) + + +def create_depreciation_asset(**args): + args = frappe._dict(args) + + asset = frappe.new_doc("Asset") + asset.is_existing_asset = 1 + asset.calculate_depreciation = 1 + asset.asset_owner = "Company" + + asset.company = args.company or "_Test Company" + asset.item_code = args.item_code or "Macbook Pro" + asset.asset_name = args.asset_name or asset.item_code + asset.location = args.location or "Test Location" + + asset.purchase_date = args.purchase_date or '2020-01-01' + asset.available_for_use_date = args.available_for_use_date or asset.purchase_date + + asset.gross_purchase_amount = args.asset_value or 100000 + asset.purchase_receipt_amount = asset.gross_purchase_amount + + finance_book = asset.append('finance_books') + finance_book.depreciation_start_date = args.depreciation_start_date or '2020-12-31' + finance_book.depreciation_method = args.depreciation_method or 'Straight Line' + finance_book.total_number_of_depreciations = cint(args.total_number_of_depreciations) or 3 + finance_book.frequency_of_depreciation = cint(args.frequency_of_depreciation) or 12 + finance_book.expected_value_after_useful_life = flt(args.expected_value_after_useful_life) + + if args.submit: + asset.submit() + + frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") + post_depreciation_entries(date=finance_book.depreciation_start_date) + asset.load_from_db() + + return asset + + +def get_actual_gle_dict(name): + return dict(frappe.db.sql(""" + select account, sum(debit-credit) as diff + from `tabGL Entry` + where voucher_type = 'Asset Capitalization' and voucher_no = %s + group by account + having diff != 0 + """, name)) + + +def get_actual_sle_dict(name): + sles = frappe.db.sql(""" + select + item_code, warehouse, + sum(actual_qty) as actual_qty, + sum(stock_value_difference) as stock_value_difference + from `tabStock Ledger Entry` + where voucher_type = 'Asset Capitalization' and voucher_no = %s + group by item_code, warehouse + having actual_qty != 0 + """, name, as_dict=1) + + sle_dict = {} + for d in sles: + sle_dict[(d.item_code, d.warehouse)] = { + 'actual_qty': d.actual_qty, 'stock_value_difference': d.stock_value_difference + } + + return sle_dict