From d3fb0fbb204393cd30de4c4e825d5167c3b9b271 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 22 Jun 2018 15:40:59 +0530 Subject: [PATCH] Added test cases for asset accounting, asset value adjustment (#14572) * Added test cases for asset accounting, asset value adjustment * Accounting entry for the asset created manually * Added asset movement test cases and validation * Added validation to make asset from purchase receupt, invoice only --- erpnext/assets/doctype/asset/asset.js | 8 +- erpnext/assets/doctype/asset/asset.json | 37 +++- erpnext/assets/doctype/asset/asset.py | 53 +++-- erpnext/assets/doctype/asset/test_asset.py | 194 +++++++++++++++--- .../test_asset_maintenance.py | 47 +++-- .../doctype/asset_movement/asset_movement.py | 73 +++++-- .../asset_movement/test_asset_movement.py | 146 +++++++++++-- .../asset_value_adjustment.js | 4 +- .../asset_value_adjustment.json | 4 +- .../asset_value_adjustment.py | 11 +- .../test_asset_value_adjustment.py | 86 +++++++- erpnext/controllers/buying_controller.py | 1 - erpnext/hr/doctype/employee/test_employee.py | 2 - .../purchase_receipt/test_purchase_receipt.py | 7 +- 14 files changed, 568 insertions(+), 105 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 834e942f72..2021dddbcf 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -58,7 +58,7 @@ frappe.ui.form.on('Asset', { }); } - if (frm.doc.purchase_receipt) { + if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) { frm.add_custom_button("General Ledger", function() { frappe.route_options = { "voucher_no": frm.doc.name, @@ -89,6 +89,10 @@ frappe.ui.form.on('Asset', { frm.page.set_inner_btn_group_as_primary(__("Make")); frm.trigger("setup_chart"); } + + if (frm.doc.docstatus == 0) { + frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); + } }, setup_chart: function(frm) { @@ -261,6 +265,8 @@ frappe.ui.form.on('Asset', { }) } }) + + frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); } }); diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 7f77358de7..1da8760959 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -1628,7 +1628,7 @@ "in_standard_filter": 0, "label": "Booked Fixed Asset", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, @@ -1771,6 +1771,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "default_finance_book", + "fieldtype": "Read Only", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Finance Book", + "length": 0, + "no_copy": 0, + "options": "company.default_finance_book", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1815,7 +1848,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-06-15 13:56:35.211418", + "modified": "2018-06-18 19:20:27.668745", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 1a0e330daf..df69ed7e28 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff from frappe.model.document import Document @@ -16,19 +16,19 @@ from erpnext.controllers.accounts_controller import AccountsController class Asset(AccountsController): def validate(self): - self.status = self.get_status() + self.validate_asset_values() self.validate_item() self.set_missing_values() - self.validate_asset_values() if self.calculate_depreciation: self.make_depreciation_schedule() self.set_accumulated_depreciation() - get_depreciation_accounts(self) else: self.finance_books = [] if self.get("schedules"): self.validate_expected_value_after_useful_life() + self.status = self.get_status() + def on_submit(self): self.validate_in_use_date() self.set_status() @@ -71,8 +71,19 @@ class Asset(AccountsController): if not flt(self.gross_purchase_amount): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) + if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): + frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}"). + format(self.item_code)) + + if (not self.purchase_receipt and self.purchase_invoice + and not frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')): + frappe.throw(_("Update stock must be enable for the purchase invoice {0}"). + format(self.purchase_invoice)) + if not self.calculate_depreciation: return + elif not self.finance_books: + frappe.throw(_("Enter depreciation details")) if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(nowdate()): frappe.throw(_("Available-for-use Date is entered as past date")) @@ -303,23 +314,32 @@ class Asset(AccountsController): status = "Draft" elif self.docstatus == 1: status = "Submitted" - expected_value_after_useful_life = flt(sum([d.expected_value_after_useful_life - for d in self.get('finance_books')])) - value_after_depreciation = flt(sum([d.value_after_depreciation - for d in self.get('finance_books')])) + idx = self.get_default_finance_book_idx() or 0 + + expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life + value_after_depreciation = self.finance_books[idx].value_after_depreciation if self.journal_entry_for_scrap: status = "Scrapped" elif self.finance_books: if flt(value_after_depreciation) <= expected_value_after_useful_life: status = "Fully Depreciated" - elif flt(self.value_after_depreciation) < flt(self.gross_purchase_amount): + elif flt(value_after_depreciation) < flt(self.gross_purchase_amount): status = 'Partially Depreciated' elif self.docstatus == 2: status = "Cancelled" return status + def get_default_finance_book_idx(self): + if not self.get('default_finance_book') and self.company: + self.default_finance_book = erpnext.get_default_finance_book(self.company) + + if self.get('default_finance_book'): + for d in self.get('finance_books'): + if d.finance_books == self.default_finance_book: + return cint(d.idx) - 1 + def update_stock_movement(self): asset_movement = frappe.db.get_value('Asset Movement', {'asset': self.name, 'reference_name': self.purchase_receipt, 'docstatus': 0}, 'name') @@ -329,17 +349,17 @@ class Asset(AccountsController): doc.submit() def make_gl_entries(self): - if self.purchase_receipt and self.purchase_receipt_amount and self.available_for_use_date <= nowdate(): - from erpnext.accounts.general_ledger import make_gl_entries + gl_entries = [] - gl_entries = [] + if ((self.purchase_receipt or (self.purchase_invoice and + frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) + and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): + fixed_aseet_account = get_asset_category_account(self.name, 'fixed_asset_account', + asset_category = self.asset_category, company = self.company) cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company) - fixed_aseet_account = get_asset_category_account(self.name, 'fixed_asset_account', - asset_category = self.asset_category, company = self.company) - gl_entries.append(self.get_gl_dict({ "account": cwip_account, "against": fixed_aseet_account, @@ -358,6 +378,9 @@ class Asset(AccountsController): "debit_in_account_currency": self.purchase_receipt_amount })) + if gl_entries: + from erpnext.accounts.general_ledger import make_gl_entries + make_gl_entries(gl_entries) self.db_set('booked_fixed_asset', 1) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4ee4894f04..d855873d5a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -5,19 +5,38 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import cstr, nowdate, getdate, flt +from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, add_months from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase_invoice +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestAsset(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() remove_prorated_depreciation_schedule() - create_asset() + create_asset_data() frappe.db.sql("delete from `tabTax Rule`") def test_purchase_asset(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + + month_end_date = get_last_day(nowdate()) + purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + + asset.available_for_use_date = purchase_date + asset.purchase_date = purchase_date + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) asset.submit() pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, @@ -27,7 +46,7 @@ class TestAsset(unittest.TestCase): pi.submit() asset.load_from_db() self.assertEqual(asset.supplier, "_Test Supplier") - self.assertEqual(asset.purchase_date, getdate("2015-01-01")) + self.assertEqual(asset.purchase_date, getdate(purchase_date)) self.assertEqual(asset.purchase_invoice, pi.name) expected_gle = ( @@ -51,8 +70,15 @@ class TestAsset(unittest.TestCase): def test_schedule_for_straight_line_method(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { "expected_value_after_useful_life": 10000, "next_depreciation_date": "2020-12-31", @@ -61,7 +87,7 @@ class TestAsset(unittest.TestCase): "frequency_of_depreciation": 10, "depreciation_start_date": "2020-06-06" }) - asset.insert() + asset.save() self.assertEqual(asset.status, "Draft") expected_schedules = [ ["2020-06-06", 163.93, 163.93], @@ -75,8 +101,8 @@ class TestAsset(unittest.TestCase): self.assertEqual(schedules, expected_schedules) def test_schedule_for_straight_line_method_for_existing_asset(self): + create_asset(is_existing_asset=1) asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.is_existing_asset = 1 asset.calculate_depreciation = 1 asset.number_of_depreciations_booked = 1 asset.opening_accumulated_depreciation = 40000 @@ -101,8 +127,14 @@ class TestAsset(unittest.TestCase): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { "expected_value_after_useful_life": 10000, "next_depreciation_date": "2020-12-31", @@ -127,6 +159,7 @@ class TestAsset(unittest.TestCase): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method_for_existing_asset(self): + create_asset(is_existing_asset = 1) asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) asset.calculate_depreciation = 1 asset.is_existing_asset = 1 @@ -158,8 +191,13 @@ class TestAsset(unittest.TestCase): def test_schedule_for_prorated_straight_line_method(self): set_prorated_depreciation_schedule() - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.purchase_date = '2020-06-06' asset.is_existing_asset = 0 asset.available_for_use_date = "2020-01-30" asset.append("finance_books", { @@ -188,8 +226,13 @@ class TestAsset(unittest.TestCase): remove_prorated_depreciation_schedule() def test_depreciation(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.purchase_date = '2020-06-06' asset.available_for_use_date = "2020-01-30" asset.append("finance_books", { "expected_value_after_useful_life": 10000, @@ -201,7 +244,7 @@ class TestAsset(unittest.TestCase): asset.insert() asset.submit() asset.load_from_db() - self.assertEqual(asset.status, "Partially Depreciated") + self.assertEqual(asset.status, "Submitted") frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") post_depreciation_entries(date="2021-01-01") @@ -223,8 +266,14 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.get("value_after_depreciation"), 0) def test_depreciation_entry_cancellation(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", @@ -248,8 +297,14 @@ class TestAsset(unittest.TestCase): self.assertFalse(depr_entry) def test_scrap_asset(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", @@ -284,8 +339,14 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.status, "Partially Depreciated") def test_asset_sale(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", @@ -325,8 +386,14 @@ class TestAsset(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") def test_asset_expected_value_after_useful_life(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", @@ -343,16 +410,91 @@ class TestAsset(unittest.TestCase): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) - def tearDown(self): - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - if asset.docstatus == 1 and asset.status not in ("Scrapped", "Sold", "Draft", "Cancelled"): - asset.cancel() + def test_cwip_accounting(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_invoice as make_purchase_invoice_from_pr) - self.assertEqual(frappe.db.get_value("Asset", {"asset_name": "Macbook Pro 1"}, "status"), "Cancelled") + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=5000, do_not_submit=True, location="Test Location") - frappe.delete_doc("Asset", {"asset_name": "Macbook Pro 1"}) + pr.set('taxes', [{ + 'category': 'Total', + 'add_deduct_tax': 'Add', + 'charge_type': 'On Net Total', + 'account_head': '_Test Account Service Tax - _TC', + 'description': '_Test Account Service Tax', + 'cost_center': 'Main - _TC', + 'rate': 5.0 + }, { + 'category': 'Valuation and Total', + 'add_deduct_tax': 'Add', + 'charge_type': 'On Net Total', + 'account_head': '_Test Account Shipping Charges - _TC', + 'description': '_Test Account Shipping Charges', + 'cost_center': 'Main - _TC', + 'rate': 5.0 + }]) -def create_asset(): + pr.submit() + + expected_gle = ( + ("Asset Received But Not Billed - _TC", 0.0, 5250.0), + ("CWIP Account - _TC", 5250.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Purchase Receipt' and voucher_no = %s + order by account""", pr.name) + + self.assertEqual(gle, expected_gle) + + pi = make_purchase_invoice_from_pr(pr.name) + pi.submit() + + expected_gle = ( + ("_Test Account Service Tax - _TC", 250.0, 0.0), + ("_Test Account Shipping Charges - _TC", 250.0, 0.0), + ("Asset Received But Not Billed - _TC", 5250.0, 0.0), + ("Creditors - _TC", 0.0, 5500.0), + ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Purchase Invoice' and voucher_no = %s + order by account""", pi.name) + + self.assertEqual(gle, expected_gle) + + asset = frappe.db.get_value('Asset', + {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') + + asset_doc = frappe.get_doc('Asset', asset) + + month_end_date = get_last_day(nowdate()) + asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) + + asset_doc.append("finance_books", { + "expected_value_after_useful_life": 200, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + asset_doc.submit() + + expected_gle = ( + ("_Test Fixed Asset - _TC", 5250.0, 0.0), + ("CWIP Account - _TC", 0.0, 5250.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Asset' and voucher_no = %s + order by account""", asset_doc.name) + + self.assertEqual(gle, expected_gle) + +def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -365,6 +507,11 @@ def create_asset(): 'location_name': 'Test Location' }).insert() +def create_asset(**args): + args = frappe._dict(args) + + create_asset_data() + asset = frappe.get_doc({ "doctype": "Asset", "asset_name": "Macbook Pro 1", @@ -378,7 +525,8 @@ def create_asset(): "warehouse": "_Test Warehouse - _TC", "available_for_use_date": "2020-06-06", "location": "Test Location", - "asset_owner": "Company" + "asset_owner": "Company", + "is_existing_asset": args.is_existing_asset or 0 }) try: diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 1e78a6b535..735302a0c3 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -5,16 +5,40 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate, add_days +from frappe.utils import nowdate, get_last_day, add_days +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate_next_due_date class TestAssetMaintenance(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() - create_asset() + create_asset_data() create_maintenance_team() def test_create_asset_maintenance(self): + pr = make_purchase_receipt(item_code="Photocopier", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset_doc = frappe.get_doc('Asset', asset_name) + month_end_date = get_last_day(nowdate()) + + purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + + asset_doc.available_for_use_date = purchase_date + asset_doc.purchase_date = purchase_date + + asset_doc.calculate_depreciation = 1 + asset_doc.append("finance_books", { + "expected_value_after_useful_life": 200, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + + asset_doc.save() + if not frappe.db.exists("Asset Maintenance", "Photocopier"): asset_maintenance = frappe.get_doc({ "doctype": "Asset Maintenance", @@ -40,7 +64,7 @@ class TestAssetMaintenance(unittest.TestCase): next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly") self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) -def create_asset(): +def create_asset_data(): if not frappe.db.exists("Asset Category", "Equipment"): create_asset_category() @@ -62,23 +86,6 @@ def create_asset(): "asset_category": "Equipment" }).insert() - if not frappe.db.exists("Asset", "Photocopier"): - frappe.get_doc({ - "doctype": "Asset", - "asset_name": "Photocopier", - "item_code": "Photocopier", - "asset_category": "Equipment", - "gross_purchase_amount": 100000, - "expected_value_after_useful_life": 10000, - "warehouse": "_Test Warehouse - _TC", - "location": "Test Location", - "available_for_use_date": add_days(nowdate(),3), - "company": "_Test Company", - "purchase_date": nowdate(), - "maintenance_required": 1, - "asset_owner": "Company" - }).insert() - def create_maintenance_team(): user_list = ["marcus@abc.com", "thalia@abc.com", "mathias@abc.com"] if not frappe.db.exists("Role", "Technician"): diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index a89b312732..9d3af30007 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -11,7 +11,7 @@ from frappe.model.document import Document class AssetMovement(Document): def validate(self): self.validate_asset() - self.validate_warehouses() + self.validate_location() def validate_asset(self): status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) @@ -27,13 +27,33 @@ class AssetMovement(Document): if not(self.source_location or self.target_location or self.from_employee or self.to_employee): frappe.throw(_("Either location or employee must be required")) - def validate_warehouses(self): - if self.purpose in ['Transfer', 'Issue']: - self.source_location = frappe.db.get_value("Asset", self.asset, "location") + if (not self.serial_no and + frappe.db.get_value('Serial No', {'asset': self.asset}, 'name')): + frappe.throw(_("Serial no is required for the asset {0}").format(self.asset)) - if self.source_location == self.target_location and self.purpose == 'Transfer': + def validate_location(self): + if self.purpose in ['Transfer', 'Issue']: + if not self.serial_no and not (self.from_employee or self.to_employee): + self.source_location = frappe.db.get_value("Asset", self.asset, "location") + + if self.purpose == 'Issue' and not self.source_location: + frappe.throw(_("Source Location is required for the asset {0}").format(self.asset)) + + if self.serial_no and self.source_location: + s_nos = get_serial_nos(self.serial_no) + serial_nos = frappe.db.sql_list(""" select name from `tabSerial No` where location != '%s' + and name in (%s)""" %(self.source_location, ','.join(['%s'] * len(s_nos))), tuple(s_nos)) + + if serial_nos: + frappe.throw(_("Serial nos {0} does not belongs to the location {1}"). + format(','.join(serial_nos), self.source_location)) + + if self.source_location and self.source_location == self.target_location and self.purpose == 'Transfer': frappe.throw(_("Source and Target Location cannot be same")) + if self.purpose == 'Receipt' and not self.target_location: + frappe.throw(_("Target Location is required for the asset {0}").format(self.asset)) + def on_submit(self): self.set_latest_location_in_asset() @@ -41,19 +61,42 @@ class AssetMovement(Document): self.set_latest_location_in_asset() def set_latest_location_in_asset(self): - latest_movement_entry = frappe.db.sql("""select target_location from `tabAsset Movement` - where asset=%s and docstatus=1 and company=%s - order by transaction_date desc limit 1""", (self.asset, self.company)) - + location, employee = '', '' + cond = "1=1" + + args = { + 'asset': self.asset, + 'company': self.company + } + + if self.serial_no: + cond = "serial_no like %(txt)s" + args.update({ + 'txt': "%%%s%%" % self.serial_no + }) + + latest_movement_entry = frappe.db.sql("""select target_location, to_employee from `tabAsset Movement` + where asset=%(asset)s and docstatus=1 and company=%(company)s and {0} + order by transaction_date desc limit 1""".format(cond), args) + if latest_movement_entry: location = latest_movement_entry[0][0] - else: - location = frappe.db.sql("""select source_location from `tabAsset Movement` - where asset=%s and docstatus=2 and company=%s - order by transaction_date asc limit 1""", (self.asset, self.company))[0][0] + employee = latest_movement_entry[0][1] + elif self.purpose in ['Transfer', 'Receipt']: + movement_entry = frappe.db.sql("""select source_location, from_employee from `tabAsset Movement` + where asset=%(asset)s and docstatus=2 and company=%(company)s and {0} + order by transaction_date asc limit 1""".format(cond), args) + if movement_entry: + location = movement_entry[0][0] + employee = movement_entry[0][1] - frappe.db.set_value("Asset", self.asset, "location", location) + if not self.serial_no: + frappe.db.set_value("Asset", self.asset, "location", location) if self.serial_no: for d in get_serial_nos(self.serial_no): - frappe.db.set_value('Serial No', d, 'location', location) + if (location or self.purpose == 'Issue'): + frappe.db.set_value('Serial No', d, 'location', location) + + if employee: + frappe.db.set_value('Serial No', d, 'employee', employee) diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index be18d3a57b..4d85337445 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -4,14 +4,37 @@ from __future__ import unicode_literals import frappe -from frappe.utils import now import unittest -from erpnext.assets.doctype.asset.test_asset import create_asset - +from erpnext.stock.doctype.item.test_item import make_item +from frappe.utils import now, nowdate, get_last_day, add_days +from erpnext.assets.doctype.asset.test_asset import create_asset_data +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestAssetMovement(unittest.TestCase): + def setUp(self): + create_asset_data() + make_location() + make_serialized_item() + def test_movement(self): - asset = create_asset() + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "next_depreciation_date": "2020-12-31", + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-06-06" + }) if asset.docstatus == 0: asset.submit() @@ -21,10 +44,12 @@ class TestAssetMovement(unittest.TestCase): 'location_name': 'Test Location 2' }).insert() - movement1 = create_asset_movement(asset, target_location="Test Location 2") + movement1 = create_asset_movement(asset= asset.name, purpose = 'Transfer', + company=asset.company, source_location="Test Location", target_location="Test Location 2") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(asset, target_location="Test Location") + movement2 = create_asset_movement(asset= asset.name, purpose = 'Transfer', + company=asset.company, source_location = "Test Location 2", target_location="Test Location") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") movement1.cancel() @@ -33,23 +58,112 @@ class TestAssetMovement(unittest.TestCase): movement2.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") - asset.load_from_db() - asset.cancel() - frappe.delete_doc("Asset", asset.name) + def test_movement_for_serialized_asset(self): + asset_item = "Test Serialized Asset Item" + pr = make_purchase_receipt(item_code=asset_item, rate = 1000, qty=3, location = "Mumbai") + asset_name = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') -def create_asset_movement(asset, target_location, transaction_date=None): - if not transaction_date: - transaction_date = now() + asset = frappe.get_doc('Asset', asset_name) + month_end_date = get_last_day(nowdate()) + asset.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + + asset.calculate_depreciation = 1 + asset.append("finance_books", { + "expected_value_after_useful_life": 200, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + asset.submit() + serial_nos = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'serial_no') + + mov1 = create_asset_movement(asset=asset_name, purpose = 'Transfer', + company=asset.company, source_location = "Mumbai", target_location="Pune", serial_no=serial_nos) + self.assertEqual(mov1.target_location, "Pune") + + serial_no = frappe.db.get_value('Serial No', {'asset': asset_name}, 'name') + + employee = make_employee("testassetemp@example.com") + create_asset_movement(asset=asset_name, purpose = 'Transfer', + company=asset.company, serial_no=serial_no, to_employee=employee) + + self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), employee) + + create_asset_movement(asset=asset_name, purpose = 'Transfer', company=asset.company, + serial_no=serial_no, from_employee=employee, to_employee="_T-Employee-00001") + + self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Pune") + + mov4 = create_asset_movement(asset=asset_name, purpose = 'Transfer', + company=asset.company, source_location = "Pune", target_location="Nagpur", serial_no=serial_nos) + self.assertEqual(mov4.target_location, "Nagpur") + self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Nagpur") + self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), "_T-Employee-00001") + +def create_asset_movement(**args): + args = frappe._dict(args) + + if not args.transaction_date: + args.transaction_date = now() movement = frappe.new_doc("Asset Movement") movement.update({ - "asset": asset.name, - "transaction_date": transaction_date, - "target_location": target_location, - "company": asset.company + "asset": args.asset, + "transaction_date": args.transaction_date, + "target_location": args.target_location, + "company": args.company, + 'purpose': args.purpose or 'Receipt', + 'serial_no': args.serial_no, + 'quantity': len(get_serial_nos(args.serial_no)) if args.serial_no else 1, + 'from_employee': "_T-Employee-00001" or args.from_employee, + 'to_employee': args.to_employee }) + if args.source_location: + movement.update({ + 'source_location': args.source_location + }) + movement.insert() movement.submit() return movement + +def make_location(): + for location in ['Pune', 'Mumbai', 'Nagpur']: + if not frappe.db.exists('Location', location): + frappe.get_doc({ + 'doctype': 'Location', + 'location_name': location + }).insert(ignore_permissions = True) + +def make_serialized_item(): + asset_item = "Test Serialized Asset Item" + + if not frappe.db.exists('Item', asset_item): + asset_category = frappe.get_all('Asset Category') + + if asset_category: + asset_category = asset_category[0].name + + if not asset_category: + doc = frappe.get_doc({ + 'doctype': 'Asset Category', + 'asset_category_name': 'Test Asset Category', + 'depreciation_method': 'Straight Line', + 'total_number_of_depreciations': 12, + 'frequency_of_depreciation': 1, + 'accounts': [{ + 'company_name': '_Test Company', + 'fixed_asset_account': '_Test Fixed Asset - _TC', + 'accumulated_depreciation_account': 'Depreciation - _TC', + 'depreciation_expense_account': 'Depreciation - _TC' + }] + }).insert() + + asset_category = doc.name + + make_item(asset_item, {'is_stock_item':0, + 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, + 'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js index 73e84b35ac..29909f8bb9 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js @@ -23,9 +23,9 @@ frappe.ui.form.on('Asset Value Adjustment', { }, set_current_asset_value: function(frm) { - if (frm.doc.finance_book && frm.doc.asset) { + if (frm.doc.asset) { frm.call({ - method: "erpnext.assets.doctype.asset_adjustment.asset_adjustment.get_current_asset_value", + method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_current_asset_value", args: { asset: frm.doc.asset, finance_book: frm.doc.finance_book diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json index 7d963eb6a9..b15f8987e3 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json @@ -139,7 +139,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -413,7 +413,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-06-15 11:48:52.880894", + "modified": "2018-06-18 17:44:42.383087", "modified_by": "Administrator", "module": "Assets", "name": "Asset Value Adjustment", diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index cbab6309b8..d248803244 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -28,7 +28,7 @@ class AssetValueAdjustment(Document): self.difference_amount = flt(self.current_asset_value - self.new_asset_value) def set_current_asset_value(self): - if not self.current_asset_value and self.asset and self.finance_book: + if not self.current_asset_value and self.asset: self.current_asset_value = get_current_asset_value(self.asset, self.finance_book) def make_depreciation_entry(self): @@ -101,6 +101,9 @@ class AssetValueAdjustment(Document): asset_data.db_update() @frappe.whitelist() -def get_current_asset_value(asset, finance_book): - return frappe.db.get_value('Asset Finance Book', - {'parent': asset, 'parenttype': 'Asset', 'finance_book': finance_book}, 'value_after_depreciation', debug=1) +def get_current_asset_value(asset, finance_book=None): + cond = {'parent': asset, 'parenttype': 'Asset'} + if finance_book: + cond.update({'finance_book': finance_book}) + + return frappe.db.get_value('Asset Finance Book', cond, 'value_after_depreciation') diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index 26dad48f58..03dc47b0bb 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -5,6 +5,90 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.utils import nowdate, get_last_day, add_days +from erpnext.assets.doctype.asset.test_asset import create_asset_data +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import get_current_asset_value class TestAssetValueAdjustment(unittest.TestCase): - pass + def setUp(self): + create_asset_data() + + def test_current_asset_value(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset_doc = frappe.get_doc('Asset', asset_name) + + month_end_date = get_last_day(nowdate()) + purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + + asset_doc.available_for_use_date = purchase_date + asset_doc.purchase_date = purchase_date + asset_doc.calculate_depreciation = 1 + asset_doc.append("finance_books", { + "expected_value_after_useful_life": 200, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + asset_doc.submit() + + current_value = get_current_asset_value(asset_doc.name) + self.assertEqual(current_value, 100000.0) + + def test_asset_depreciation_value_adjustment(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset_doc = frappe.get_doc('Asset', asset_name) + asset_doc.calculate_depreciation = 1 + + month_end_date = get_last_day(nowdate()) + purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + + asset_doc.available_for_use_date = purchase_date + asset_doc.purchase_date = purchase_date + asset_doc.calculate_depreciation = 1 + asset_doc.append("finance_books", { + "expected_value_after_useful_life": 200, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": month_end_date + }) + asset_doc.submit() + + current_value = get_current_asset_value(asset_doc.name) + adj_doc = make_asset_value_adjustment(asset = asset_doc.name, + current_asset_value = current_value, new_asset_value = 50000.0) + adj_doc.submit() + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 0.0, 50000.0), + ("_Test Depreciations - _TC", 50000.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where voucher_type='Journal Entry' and voucher_no = %s + order by account""", adj_doc.journal_entry) + + self.assertEqual(gle, expected_gle) + +def make_asset_value_adjustment(**args): + args = frappe._dict(args) + + doc = frappe.get_doc({ + "doctype": "Asset Value Adjustment", + "company": args.company or "_Test Company", + "asset": args.asset, + "date": args.date or nowdate(), + "new_asset_value": args.new_asset_value, + "current_asset_value": args.current_asset_value, + "cost_center": args.cost_center or "Main - _TC" + }).insert() + + return doc \ No newline at end of file diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index fc548cdd43..fa8cf2e8db 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -578,7 +578,6 @@ class BuyingController(StockController): 'doctype': 'Asset', 'item_code': row.item_code, 'asset_name': row.item_name, - 'status': 'Receipt', 'naming_series': item_data.get('asset_naming_series') or 'AST', 'asset_category': item_data.get('asset_category'), 'location': row.asset_location, diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index dfde030ae6..f397700f22 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -33,8 +33,6 @@ class TestEmployee(unittest.TestCase): self.assertTrue("Subject: Birthday Reminder for {0}".format(employee.employee_name) \ in email_queue[0].message) - - def make_employee(user): if not frappe.db.get_value("User", user): frappe.get_doc({ diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c199271227..86ff2eea5a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -332,6 +332,10 @@ class TestPurchaseReceipt(unittest.TestCase): serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') self.assertEquals(len(serial_nos), 3) + + location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location') + self.assertEquals(location, "Test Location") + pr.cancel() serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] self.assertEquals(len(serial_nos), 0) @@ -373,7 +377,8 @@ def make_purchase_receipt(**args): "serial_no": args.serial_no, "stock_uom": args.stock_uom or "_Test UOM", "uom": args.uom or "_Test UOM", - "asset_location": "Test Location" if args.item_code == "Test Serialized Asset Item" else "" + "cost_center": args.cost_center or frappe.db.get_value('Company', pr.company, 'cost_center'), + "asset_location": args.location or "Test Location" }) if not args.do_not_save: