mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-24 07:29:22 +00:00
Merge branch 'develop' into home-onboarding
This commit is contained in:
@@ -80,20 +80,20 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
if (frm.doc.docstatus==1) {
|
||||
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
|
||||
frm.add_custom_button("Transfer Asset", function() {
|
||||
frm.add_custom_button(__("Transfer Asset"), function() {
|
||||
erpnext.asset.transfer_asset(frm);
|
||||
}, __("Manage"));
|
||||
|
||||
frm.add_custom_button("Scrap Asset", function() {
|
||||
frm.add_custom_button(__("Scrap Asset"), function() {
|
||||
erpnext.asset.scrap_asset(frm);
|
||||
}, __("Manage"));
|
||||
|
||||
frm.add_custom_button("Sell Asset", function() {
|
||||
frm.add_custom_button(__("Sell Asset"), function() {
|
||||
frm.trigger("make_sales_invoice");
|
||||
}, __("Manage"));
|
||||
|
||||
} else if (frm.doc.status=='Scrapped') {
|
||||
frm.add_custom_button("Restore Asset", function() {
|
||||
frm.add_custom_button(__("Restore Asset"), function() {
|
||||
erpnext.asset.restore_asset(frm);
|
||||
}, __("Manage"));
|
||||
}
|
||||
@@ -121,7 +121,7 @@ frappe.ui.form.on('Asset', {
|
||||
}
|
||||
|
||||
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
|
||||
frm.add_custom_button("View General Ledger", function() {
|
||||
frm.add_custom_button(__("View General Ledger"), function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": frm.doc.name,
|
||||
"from_date": frm.doc.available_for_use_date,
|
||||
|
||||
@@ -192,8 +192,7 @@ class Asset(AccountsController):
|
||||
|
||||
# value_after_depreciation - current Asset value
|
||||
if self.docstatus == 1 and d.value_after_depreciation:
|
||||
value_after_depreciation = (flt(d.value_after_depreciation) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
value_after_depreciation = flt(d.value_after_depreciation)
|
||||
else:
|
||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
@@ -241,7 +240,7 @@ class Asset(AccountsController):
|
||||
break
|
||||
|
||||
# For first row
|
||||
if has_pro_rata and n==0:
|
||||
if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
|
||||
self.available_for_use_date, d.depreciation_start_date)
|
||||
|
||||
@@ -254,7 +253,7 @@ class Asset(AccountsController):
|
||||
if not self.flags.increase_in_asset_life:
|
||||
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
||||
self.to_date = add_months(self.available_for_use_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
(n + self.number_of_depreciations_booked) * cint(d.frequency_of_depreciation))
|
||||
|
||||
depreciation_amount_without_pro_rata = depreciation_amount
|
||||
|
||||
@@ -354,7 +353,12 @@ class Asset(AccountsController):
|
||||
# if it returns True, depreciation_amount will not be equal for the first and last rows
|
||||
def check_is_pro_rata(self, row):
|
||||
has_pro_rata = False
|
||||
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
|
||||
|
||||
# if not existing asset, from_date = available_for_use_date
|
||||
# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||
# from_date = 01/01/2022
|
||||
from_date = self.get_modified_available_for_use_date(row)
|
||||
days = date_diff(row.depreciation_start_date, from_date) + 1
|
||||
|
||||
# if frequency_of_depreciation is 12 months, total_days = 365
|
||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||
@@ -364,6 +368,9 @@ class Asset(AccountsController):
|
||||
|
||||
return has_pro_rata
|
||||
|
||||
def get_modified_available_for_use_date(self, row):
|
||||
return add_months(self.available_for_use_date, (self.number_of_depreciations_booked * row.frequency_of_depreciation))
|
||||
|
||||
def validate_asset_finance_books(self, row):
|
||||
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
||||
frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
|
||||
@@ -402,10 +409,11 @@ class Asset(AccountsController):
|
||||
|
||||
# to ensure that final accumulated depreciation amount is accurate
|
||||
def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book):
|
||||
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
|
||||
if not self.opening_accumulated_depreciation:
|
||||
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
|
||||
|
||||
if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
|
||||
depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
|
||||
if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
|
||||
depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
|
||||
|
||||
return depreciation_amount_for_last_row
|
||||
|
||||
@@ -850,13 +858,11 @@ def get_total_days(date, frequency):
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_depreciation_amount(asset, depreciable_value, row):
|
||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
if not asset.flags.increase_in_asset_life:
|
||||
depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
depreciation_amount = (flt(asset.gross_purchase_amount) -
|
||||
flt(row.expected_value_after_useful_life)) / flt(row.total_number_of_depreciations)
|
||||
|
||||
# if the Depreciation Schedule is being modified after Asset Repair
|
||||
else:
|
||||
|
||||
@@ -57,8 +57,10 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
je.finance_book = d.finance_book
|
||||
je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
|
||||
|
||||
credit_account, debit_account = get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account)
|
||||
|
||||
credit_entry = {
|
||||
"account": accumulated_depreciation_account,
|
||||
"account": credit_account,
|
||||
"credit_in_account_currency": d.depreciation_amount,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset.name,
|
||||
@@ -66,7 +68,7 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
}
|
||||
|
||||
debit_entry = {
|
||||
"account": depreciation_expense_account,
|
||||
"account": debit_account,
|
||||
"debit_in_account_currency": d.depreciation_amount,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset.name,
|
||||
@@ -132,6 +134,20 @@ def get_depreciation_accounts(asset):
|
||||
|
||||
return fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account
|
||||
|
||||
def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account):
|
||||
root_type = frappe.get_value("Account", depreciation_expense_account, "root_type")
|
||||
|
||||
if root_type == "Expense":
|
||||
credit_account = accumulated_depreciation_account
|
||||
debit_account = depreciation_expense_account
|
||||
elif root_type == "Income":
|
||||
credit_account = depreciation_expense_account
|
||||
debit_account = accumulated_depreciation_account
|
||||
else:
|
||||
frappe.throw(_("Depreciation Expense Account should be an Income or Expense Account."))
|
||||
|
||||
return credit_account, debit_account
|
||||
|
||||
@frappe.whitelist()
|
||||
def scrap_asset(asset_name):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
|
||||
@@ -409,19 +409,18 @@ class TestDepreciationMethods(AssetSetup):
|
||||
calculate_depreciation = 1,
|
||||
available_for_use_date = "2030-06-06",
|
||||
is_existing_asset = 1,
|
||||
number_of_depreciations_booked = 1,
|
||||
opening_accumulated_depreciation = 40000,
|
||||
number_of_depreciations_booked = 2,
|
||||
opening_accumulated_depreciation = 47095.89,
|
||||
expected_value_after_useful_life = 10000,
|
||||
depreciation_start_date = "2030-12-31",
|
||||
depreciation_start_date = "2032-12-31",
|
||||
total_number_of_depreciations = 3,
|
||||
frequency_of_depreciation = 12
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
expected_schedules = [
|
||||
["2030-12-31", 14246.58, 54246.58],
|
||||
["2031-12-31", 25000.00, 79246.58],
|
||||
["2032-06-06", 10753.42, 90000.00]
|
||||
["2032-12-31", 30000.0, 77095.89],
|
||||
["2033-06-06", 12904.11, 90000.0]
|
||||
]
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in asset.get("schedules")]
|
||||
@@ -869,6 +868,72 @@ class TestDepreciationBasics(AssetSetup):
|
||||
self.assertFalse(asset.schedules[1].journal_entry)
|
||||
self.assertFalse(asset.schedules[2].journal_entry)
|
||||
|
||||
def test_depr_entry_posting_when_depr_expense_account_is_an_expense_account(self):
|
||||
"""Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account."""
|
||||
|
||||
asset = create_asset(
|
||||
item_code = "Macbook Pro",
|
||||
calculate_depreciation = 1,
|
||||
available_for_use_date = "2019-12-31",
|
||||
depreciation_start_date = "2020-12-31",
|
||||
frequency_of_depreciation = 12,
|
||||
total_number_of_depreciations = 3,
|
||||
expected_value_after_useful_life = 10000,
|
||||
submit = 1
|
||||
)
|
||||
|
||||
post_depreciation_entries(date="2021-06-01")
|
||||
asset.load_from_db()
|
||||
|
||||
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
|
||||
accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
|
||||
|
||||
for entry in accounting_entries:
|
||||
if entry["account"] == "_Test Depreciations - _TC":
|
||||
self.assertTrue(entry["debit"])
|
||||
self.assertFalse(entry["credit"])
|
||||
else:
|
||||
self.assertTrue(entry["credit"])
|
||||
self.assertFalse(entry["debit"])
|
||||
|
||||
def test_depr_entry_posting_when_depr_expense_account_is_an_income_account(self):
|
||||
"""Tests if the Depreciation Expense Account gets credited and the Accumulated Depreciation Account gets debited when the former's an Income Account."""
|
||||
|
||||
depr_expense_account = frappe.get_doc("Account", "_Test Depreciations - _TC")
|
||||
depr_expense_account.root_type = "Income"
|
||||
depr_expense_account.parent_account = "Income - _TC"
|
||||
depr_expense_account.save()
|
||||
|
||||
asset = create_asset(
|
||||
item_code = "Macbook Pro",
|
||||
calculate_depreciation = 1,
|
||||
available_for_use_date = "2019-12-31",
|
||||
depreciation_start_date = "2020-12-31",
|
||||
frequency_of_depreciation = 12,
|
||||
total_number_of_depreciations = 3,
|
||||
expected_value_after_useful_life = 10000,
|
||||
submit = 1
|
||||
)
|
||||
|
||||
post_depreciation_entries(date="2021-06-01")
|
||||
asset.load_from_db()
|
||||
|
||||
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
|
||||
accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
|
||||
|
||||
for entry in accounting_entries:
|
||||
if entry["account"] == "_Test Depreciations - _TC":
|
||||
self.assertTrue(entry["credit"])
|
||||
self.assertFalse(entry["debit"])
|
||||
else:
|
||||
self.assertTrue(entry["debit"])
|
||||
self.assertFalse(entry["credit"])
|
||||
|
||||
# resetting
|
||||
depr_expense_account.root_type = "Expense"
|
||||
depr_expense_account.parent_account = "Expenses - _TC"
|
||||
depr_expense_account.save()
|
||||
|
||||
def test_clear_depreciation_schedule(self):
|
||||
"""Tests if clear_depreciation_schedule() works as expected."""
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ frappe.ui.form.on('Asset Category', {
|
||||
var d = locals[cdt][cdn];
|
||||
return {
|
||||
"filters": {
|
||||
"root_type": "Expense",
|
||||
"root_type": ["in", ["Expense", "Income"]],
|
||||
"is_group": 0,
|
||||
"company": d.company_name
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ class AssetCategory(Document):
|
||||
|
||||
def validate_account_types(self):
|
||||
account_type_map = {
|
||||
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
|
||||
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },
|
||||
'depreciation_expense_account': { 'root_type': 'Expense' },
|
||||
'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' }
|
||||
'fixed_asset_account': {'account_type': ['Fixed Asset']},
|
||||
'accumulated_depreciation_account': {'account_type': ['Accumulated Depreciation']},
|
||||
'depreciation_expense_account': {'root_type': ['Expense', 'Income']},
|
||||
'capital_work_in_progress_account': {'account_type': ['Capital Work in Progress']}
|
||||
}
|
||||
for d in self.accounts:
|
||||
for fieldname in account_type_map.keys():
|
||||
@@ -53,11 +53,11 @@ class AssetCategory(Document):
|
||||
selected_account = d.get(fieldname)
|
||||
key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
|
||||
selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
|
||||
expected_key_type = account_type_map[fieldname][key_to_match]
|
||||
expected_key_types = account_type_map[fieldname][key_to_match]
|
||||
|
||||
if selected_key_type != expected_key_type:
|
||||
if selected_key_type not in expected_key_types:
|
||||
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_types)),
|
||||
title=_("Invalid Account"))
|
||||
|
||||
def valide_cwip_account(self):
|
||||
|
||||
@@ -60,6 +60,10 @@ frappe.ui.form.on('Asset Repair', {
|
||||
if (frm.doc.repair_status == "Completed") {
|
||||
frm.set_value('completion_date', frappe.datetime.now_datetime());
|
||||
}
|
||||
},
|
||||
|
||||
stock_items_on_form_rendered() {
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -118,9 +118,10 @@ class AssetRepair(AccountsController):
|
||||
for stock_item in self.get('stock_items'):
|
||||
stock_entry.append('items', {
|
||||
"s_warehouse": self.warehouse,
|
||||
"item_code": stock_item.item,
|
||||
"item_code": stock_item.item_code,
|
||||
"qty": stock_item.consumed_quantity,
|
||||
"basic_rate": stock_item.valuation_rate
|
||||
"basic_rate": stock_item.valuation_rate,
|
||||
"serial_no": stock_item.serial_no
|
||||
})
|
||||
|
||||
stock_entry.insert()
|
||||
|
||||
@@ -11,12 +11,15 @@ from erpnext.assets.doctype.asset.test_asset import (
|
||||
create_asset_data,
|
||||
set_depreciation_settings_in_company,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
class TestAssetRepair(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
set_depreciation_settings_in_company()
|
||||
create_asset_data()
|
||||
create_item("_Test Stock Item")
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def test_update_status(self):
|
||||
@@ -70,9 +73,28 @@ class TestAssetRepair(unittest.TestCase):
|
||||
|
||||
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
|
||||
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
|
||||
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item)
|
||||
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code)
|
||||
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
|
||||
|
||||
def test_serialized_item_consumption(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
stock_entry = make_serialized_item()
|
||||
serial_nos = stock_entry.get("items")[0].serial_no
|
||||
serial_no = serial_nos.split("\n")[0]
|
||||
|
||||
# should not raise any error
|
||||
create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code,
|
||||
warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1)
|
||||
|
||||
# should raise error
|
||||
asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC",
|
||||
item_code = stock_entry.get("items")[0].item_code)
|
||||
|
||||
asset_repair.repair_status = "Completed"
|
||||
self.assertRaises(SerialNoRequiredError, asset_repair.submit)
|
||||
|
||||
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
||||
asset = create_asset(calculate_depreciation = 1, submit=1)
|
||||
initial_asset_value = get_asset_value(asset)
|
||||
@@ -137,11 +159,12 @@ def create_asset_repair(**args):
|
||||
|
||||
if args.stock_consumption:
|
||||
asset_repair.stock_consumption = 1
|
||||
asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company)
|
||||
asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company)
|
||||
asset_repair.append("stock_items", {
|
||||
"item": args.item or args.item_code or "_Test Item",
|
||||
"item_code": args.item_code or "_Test Stock Item",
|
||||
"valuation_rate": args.rate if args.get("rate") is not None else 100,
|
||||
"consumed_quantity": args.qty or 1
|
||||
"consumed_quantity": args.qty or 1,
|
||||
"serial_no": args.serial_no
|
||||
})
|
||||
|
||||
asset_repair.insert(ignore_if_duplicate=True)
|
||||
@@ -158,7 +181,7 @@ def create_asset_repair(**args):
|
||||
})
|
||||
stock_entry.append('items', {
|
||||
"t_warehouse": asset_repair.warehouse,
|
||||
"item_code": asset_repair.stock_items[0].item,
|
||||
"item_code": asset_repair.stock_items[0].item_code,
|
||||
"qty": asset_repair.stock_items[0].consumed_quantity
|
||||
})
|
||||
stock_entry.submit()
|
||||
|
||||
@@ -5,19 +5,13 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item",
|
||||
"item_code",
|
||||
"valuation_rate",
|
||||
"consumed_quantity",
|
||||
"total_value"
|
||||
"total_value",
|
||||
"serial_no"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item.valuation_rate",
|
||||
"fieldname": "valuation_rate",
|
||||
@@ -38,12 +32,24 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Total Value",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-12 03:19:55.006300",
|
||||
"modified": "2021-11-11 18:23:00.492483",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair Consumed Item",
|
||||
|
||||
Reference in New Issue
Block a user