Merge branch 'develop' into home-onboarding

This commit is contained in:
Jannat Patel
2021-12-13 17:51:38 +05:30
committed by GitHub
160 changed files with 4874 additions and 1432 deletions

View File

@@ -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,

View File

@@ -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:

View File

@@ -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)

View File

@@ -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."""

View File

@@ -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
}

View File

@@ -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):

View File

@@ -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();
}
});

View File

@@ -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()

View File

@@ -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()

View File

@@ -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",