Merge branch 'SaiFi0102-asset-capitalization' into develop

This commit is contained in:
Nabin Hait 2022-09-13 15:00:21 +05:30
commit e24a4b18c4
23 changed files with 2643 additions and 136 deletions

View File

@ -7,17 +7,7 @@ from frappe import _, msgprint, throw
from frappe.contacts.doctype.address.address import get_address_display from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
from frappe.utils import ( from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
add_days,
add_months,
cint,
cstr,
flt,
formatdate,
get_link_to_form,
getdate,
nowdate,
)
import erpnext import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.deferred_revenue import validate_service_stop_date
@ -35,7 +25,6 @@ from erpnext.assets.doctype.asset.depreciation import (
get_disposal_account_and_cost_center, get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain, get_gl_entries_on_asset_regain,
make_depreciation_entry,
) )
from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
@ -186,7 +175,7 @@ class SalesInvoice(SellingController):
if self.update_stock: if self.update_stock:
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
elif asset.status in ("Scrapped", "Cancelled") or ( elif asset.status in ("Scrapped", "Cancelled", "Capitalized", "Decapitalized") or (
asset.status == "Sold" and not self.is_return asset.status == "Sold" and not self.is_return
): ):
frappe.throw( frappe.throw(
@ -1097,7 +1086,7 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", None) asset.db_set("disposal_date", None)
if asset.calculate_depreciation: if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_sale(asset) self.reverse_depreciation_entry_made_after_disposal(asset)
self.reset_depreciation_schedule(asset) self.reset_depreciation_schedule(asset)
else: else:
@ -1162,101 +1151,6 @@ class SalesInvoice(SellingController):
self.check_finance_books(item, asset) self.check_finance_books(item, asset)
return asset return asset
def check_finance_books(self, item, asset):
if (
len(asset.finance_books) > 1 and not item.finance_book and asset.finance_books[0].finance_book
):
frappe.throw(
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_sale=self.posting_date)
asset.save()
make_depreciation_entry(asset.name, self.posting_date)
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_sale(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == posting_date_of_original_invoice:
if not self.sale_was_made_on_original_schedule_date(
asset, schedule, row, posting_date_of_original_invoice
) or self.sale_happens_in_the_future(posting_date_of_original_invoice):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry)
asset.finance_books[0].value_after_depreciation += depreciation_amount
asset.save()
def get_posting_date_of_sales_invoice(self):
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def sale_was_made_on_original_schedule_date(
self, asset, schedule, row, posting_date_of_original_invoice
):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if orginal_schedule_date == posting_date_of_original_invoice:
return True
return False
def sale_happens_in_the_future(self, posting_date_of_original_invoice):
if posting_date_of_original_invoice > getdate():
return True
return False
def get_depreciation_amount_in_je(self, journal_entry):
if journal_entry.accounts[0].debit_in_account_currency:
return journal_entry.accounts[0].debit_in_account_currency
else:
return journal_entry.accounts[0].credit_in_account_currency
@property @property
def enable_discount_accounting(self): def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"): if not hasattr(self, "_enable_discount_accounting"):

View File

@ -79,12 +79,12 @@ class Asset(AccountsController):
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name) _("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
) )
def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None): def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None):
if self.calculate_depreciation: if self.calculate_depreciation:
self.value_after_depreciation = 0 self.value_after_depreciation = 0
self.set_depreciation_rate() self.set_depreciation_rate()
self.make_depreciation_schedule(date_of_sale) self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_sale, date_of_return) self.set_accumulated_depreciation(date_of_disposal, date_of_return)
else: else:
self.finance_books = [] self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
@ -223,7 +223,7 @@ class Asset(AccountsController):
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation") self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
) )
def make_depreciation_schedule(self, date_of_sale): def make_depreciation_schedule(self, date_of_disposal):
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get( if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
"schedules" "schedules"
): ):
@ -235,9 +235,9 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule() start = self.clear_depreciation_schedule()
for finance_book in self.get("finance_books"): for finance_book in self.get("finance_books"):
self._make_depreciation_schedule(finance_book, start, date_of_sale) self._make_depreciation_schedule(finance_book, start, date_of_disposal)
def _make_depreciation_schedule(self, finance_book, start, date_of_sale): def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
self.validate_asset_finance_books(finance_book) self.validate_asset_finance_books(finance_book)
value_after_depreciation = self._get_value_after_depreciation(finance_book) value_after_depreciation = self._get_value_after_depreciation(finance_book)
@ -274,15 +274,15 @@ class Asset(AccountsController):
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1) monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold # if asset is being sold
if date_of_sale: if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book) from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt( depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_sale finance_book, depreciation_amount, from_date, date_of_disposal
) )
if depreciation_amount > 0: if depreciation_amount > 0:
self._add_depreciation_row( self._add_depreciation_row(
date_of_sale, date_of_disposal,
depreciation_amount, depreciation_amount,
finance_book.depreciation_method, finance_book.depreciation_method,
finance_book.finance_book, finance_book.finance_book,

View File

@ -10,6 +10,9 @@ frappe.listview_settings['Asset'] = {
} else if (doc.status === "Sold") { } else if (doc.status === "Sold") {
return [__("Sold"), "green", "status,=,Sold"]; return [__("Sold"), "green", "status,=,Sold"];
} else if (["Capitalized", "Decapitalized"].includes(doc.status)) {
return [__(doc.status), "grey", "status,=," + doc.status];
} else if (doc.status === "Scrapped") { } else if (doc.status === "Scrapped") {
return [__("Scrapped"), "grey", "status,=,Scrapped"]; return [__("Scrapped"), "grey", "status,=,Scrapped"];

View File

@ -11,7 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
) )
def post_depreciation_entries(date=None): def post_depreciation_entries(date=None, commit=True):
# Return if automatic booking of asset depreciation is disabled # Return if automatic booking of asset depreciation is disabled
if not cint( if not cint(
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically") frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
@ -22,7 +22,8 @@ def post_depreciation_entries(date=None):
date = today() date = today()
for asset in get_depreciable_assets(date): for asset in get_depreciable_assets(date):
make_depreciation_entry(asset, date) make_depreciation_entry(asset, date)
frappe.db.commit() if commit:
frappe.db.commit()
def get_depreciable_assets(date): def get_depreciable_assets(date):
@ -190,7 +191,7 @@ def scrap_asset(asset_name):
if asset.docstatus != 1: if asset.docstatus != 1:
frappe.throw(_("Asset {0} must be submitted").format(asset.name)) frappe.throw(_("Asset {0} must be submitted").format(asset.name))
elif asset.status in ("Cancelled", "Sold", "Scrapped"): elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized", "Decapitalized"):
frappe.throw( frappe.throw(
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status) _("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
) )
@ -358,3 +359,30 @@ def get_disposal_account_and_cost_center(company):
frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company)) frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company))
return disposal_account, depreciation_cost_center return disposal_account, depreciation_cost_center
@frappe.whitelist()
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
asset_doc = frappe.get_doc("Asset", asset)
if asset_doc.calculate_depreciation:
asset_doc.prepare_depreciation_data(getdate(disposal_date))
finance_book_id = 1
if finance_book:
for fb in asset_doc.finance_books:
if fb.finance_book == finance_book:
finance_book_id = fb.idx
break
asset_schedules = [
sch for sch in asset_doc.schedules if cint(sch.finance_book_id) == finance_book_id
]
accumulated_depr_amount = asset_schedules[-1].accumulated_depreciation_amount
return flt(
flt(asset_doc.gross_purchase_amount) - accumulated_depr_amount,
asset_doc.precision("gross_purchase_amount"),
)
else:
return flt(asset_doc.value_after_depreciation)

View File

@ -1425,6 +1425,16 @@ def create_asset_category():
"depreciation_expense_account": "_Test Depreciations - _TC", "depreciation_expense_account": "_Test Depreciations - _TC",
}, },
) )
asset_category.append(
"accounts",
{
"company_name": "_Test Company with perpetual inventory",
"fixed_asset_account": "_Test Fixed Asset - TCP1",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
"depreciation_expense_account": "_Test Depreciations - TCP1",
},
)
asset_category.insert() asset_category.insert()

View File

@ -0,0 +1,417 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide("erpnext.assets");
erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
setup() {
this.setup_posting_date_time_check();
}
onload() {
this.setup_queries();
}
refresh() {
erpnext.hide_company();
this.show_general_ledger();
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
this.show_stock_ledger();
}
}
setup_queries() {
var me = this;
me.setup_warehouse_query();
me.frm.set_query("target_item_code", function() {
if (me.frm.doc.entry_type == "Capitalization") {
return erpnext.queries.item({"is_stock_item": 0, "is_fixed_asset": 1});
} else {
return erpnext.queries.item({"is_stock_item": 1, "is_fixed_asset": 0});
}
});
me.frm.set_query("target_asset", function() {
var filters = {};
if (me.frm.doc.target_item_code) {
filters['item_code'] = me.frm.doc.target_item_code;
}
filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]];
filters['docstatus'] = 1;
return {
filters: filters
};
});
me.frm.set_query("asset", "asset_items", function() {
var filters = {
'status': ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]],
'docstatus': 1
};
if (me.frm.doc.target_asset) {
filters['name'] = ['!=', me.frm.doc.target_asset];
}
return {
filters: filters
};
});
me.frm.set_query("item_code", "stock_items", function() {
return erpnext.queries.item({"is_stock_item": 1});
});
me.frm.set_query("item_code", "service_items", function() {
return erpnext.queries.item({"is_stock_item": 0, "is_fixed_asset": 0});
});
me.frm.set_query('batch_no', 'stock_items', function(doc, cdt, cdn) {
var item = locals[cdt][cdn];
if (!item.item_code) {
frappe.throw(__("Please enter Item Code to get Batch Number"));
} else {
var filters = {
'item_code': item.item_code,
'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(),
'warehouse': item.warehouse
};
return {
query: "erpnext.controllers.queries.get_batch_no",
filters: filters
};
}
});
me.frm.set_query('expense_account', 'service_items', function() {
return {
filters: {
"account_type": ['in', ["Tax", "Expense Account", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]],
"is_group": 0,
"company": me.frm.doc.company
}
};
});
}
target_item_code() {
return this.get_target_item_details();
}
target_asset() {
return this.get_target_asset_details();
}
item_code(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Stock Item") {
this.get_consumed_stock_item_details(row);
} else if (cdt == "Asset Capitalization Service Item") {
this.get_service_item_details(row);
}
}
warehouse(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Stock Item") {
this.get_warehouse_details(row);
}
}
asset(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
if (cdt === "Asset Capitalization Asset Item") {
this.get_consumed_asset_details(row);
}
}
posting_date() {
if (this.frm.doc.posting_date) {
frappe.run_serially([
() => this.get_all_item_warehouse_details(),
() => this.get_all_asset_values()
]);
}
}
posting_time() {
if (this.frm.doc.posting_time) {
this.get_all_item_warehouse_details();
}
}
finance_book(doc, cdt, cdn) {
if (cdt === "Asset Capitalization Asset Item") {
var row = frappe.get_doc(cdt, cdn);
this.get_consumed_asset_details(row);
} else {
this.get_all_asset_values();
}
}
stock_qty() {
this.calculate_totals();
}
qty() {
this.calculate_totals();
}
target_qty() {
this.calculate_totals();
}
rate() {
this.calculate_totals();
}
company() {
var me = this;
if (me.frm.doc.company) {
frappe.model.set_value(me.frm.doc.doctype, me.frm.doc.name, "cost_center", null);
$.each(me.frm.doc.stock_items || [], function (i, d) {
frappe.model.set_value(d.doctype, d.name, "cost_center", null);
});
$.each(me.frm.doc.asset_items || [], function (i, d) {
frappe.model.set_value(d.doctype, d.name, "cost_center", null);
});
$.each(me.frm.doc.service_items || [], function (i, d) {
frappe.model.set_value(d.doctype, d.name, "cost_center", null);
});
}
erpnext.accounts.dimensions.update_dimension(me.frm, me.frm.doctype);
}
stock_items_add(doc, cdt, cdn) {
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'stock_items');
}
asset_items_add(doc, cdt, cdn) {
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'asset_items');
}
serivce_items_add(doc, cdt, cdn) {
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'service_items');
}
get_target_item_details() {
var me = this;
if (me.frm.doc.target_item_code) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_item_details",
child: me.frm.doc,
args: {
item_code: me.frm.doc.target_item_code,
company: me.frm.doc.company,
},
callback: function (r) {
if (!r.exc) {
me.frm.refresh_fields();
}
}
});
}
}
get_target_asset_details() {
var me = this;
if (me.frm.doc.target_asset) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
child: me.frm.doc,
args: {
asset: me.frm.doc.target_asset,
company: me.frm.doc.company,
},
callback: function (r) {
if (!r.exc) {
me.frm.refresh_fields();
}
}
});
}
}
get_consumed_stock_item_details(row) {
var me = this;
if (row && row.item_code) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_consumed_stock_item_details",
child: row,
args: {
args: {
item_code: row.item_code,
warehouse: row.warehouse,
stock_qty: flt(row.stock_qty),
doctype: me.frm.doc.doctype,
name: me.frm.doc.name,
company: me.frm.doc.company,
posting_date: me.frm.doc.posting_date,
posting_time: me.frm.doc.posting_time,
}
},
callback: function (r) {
if (!r.exc) {
me.calculate_totals();
}
}
});
}
}
get_consumed_asset_details(row) {
var me = this;
if (row && row.asset) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_consumed_asset_details",
child: row,
args: {
args: {
asset: row.asset,
doctype: me.frm.doc.doctype,
name: me.frm.doc.name,
company: me.frm.doc.company,
finance_book: row.finance_book || me.frm.doc.finance_book,
posting_date: me.frm.doc.posting_date,
posting_time: me.frm.doc.posting_time,
}
},
callback: function (r) {
if (!r.exc) {
me.calculate_totals();
}
}
});
}
}
get_service_item_details(row) {
var me = this;
if (row && row.item_code) {
return me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_service_item_details",
child: row,
args: {
args: {
item_code: row.item_code,
qty: flt(row.qty),
expense_account: row.expense_account,
company: me.frm.doc.company,
}
},
callback: function (r) {
if (!r.exc) {
me.calculate_totals();
}
}
});
}
}
get_warehouse_details(item) {
var me = this;
if (item.item_code && item.warehouse) {
me.frm.call({
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_warehouse_details",
child: item,
args: {
args: {
'item_code': item.item_code,
'warehouse': cstr(item.warehouse),
'qty': flt(item.stock_qty),
'serial_no': item.serial_no,
'posting_date': me.frm.doc.posting_date,
'posting_time': me.frm.doc.posting_time,
'company': me.frm.doc.company,
'voucher_type': me.frm.doc.doctype,
'voucher_no': me.frm.doc.name,
'allow_zero_valuation': 1
}
},
callback: function(r) {
if (!r.exc) {
me.calculate_totals();
}
}
});
}
}
get_all_item_warehouse_details() {
var me = this;
return me.frm.call({
method: "set_warehouse_details",
doc: me.frm.doc,
callback: function(r) {
if (!r.exc) {
me.calculate_totals();
}
}
});
}
get_all_asset_values() {
var me = this;
return me.frm.call({
method: "set_asset_values",
doc: me.frm.doc,
callback: function(r) {
if (!r.exc) {
me.calculate_totals();
}
}
});
}
calculate_totals() {
var me = this;
me.frm.doc.stock_items_total = 0;
me.frm.doc.asset_items_total = 0;
me.frm.doc.service_items_total = 0;
$.each(me.frm.doc.stock_items || [], function (i, d) {
d.amount = flt(flt(d.stock_qty) * flt(d.valuation_rate), precision('amount', d));
me.frm.doc.stock_items_total += d.amount;
});
$.each(me.frm.doc.asset_items || [], function (i, d) {
d.asset_value = flt(flt(d.asset_value), precision('asset_value', d));
me.frm.doc.asset_items_total += d.asset_value;
});
$.each(me.frm.doc.service_items || [], function (i, d) {
d.amount = flt(flt(d.qty) * flt(d.rate), precision('amount', d));
me.frm.doc.service_items_total += d.amount;
});
me.frm.doc.stock_items_total = flt(me.frm.doc.stock_items_total, precision('stock_items_total'));
me.frm.doc.asset_items_total = flt(me.frm.doc.asset_items_total, precision('asset_items_total'));
me.frm.doc.service_items_total = flt(me.frm.doc.service_items_total, precision('service_items_total'));
me.frm.doc.total_value = me.frm.doc.stock_items_total + me.frm.doc.asset_items_total + me.frm.doc.service_items_total;
me.frm.doc.total_value = flt(me.frm.doc.total_value, precision('total_value'));
me.frm.doc.target_qty = flt(me.frm.doc.target_qty, precision('target_qty'));
me.frm.doc.target_incoming_rate = me.frm.doc.target_qty ? me.frm.doc.total_value / flt(me.frm.doc.target_qty)
: me.frm.doc.total_value;
me.frm.refresh_fields();
}
};
cur_frm.cscript = new erpnext.assets.AssetCapitalization({frm: cur_frm});

View File

@ -0,0 +1,381 @@
{
"actions": [],
"autoname": "naming_series:",
"creation": "2021-09-04 13:38:04.217187",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"naming_series",
"entry_type",
"target_item_code",
"target_item_name",
"target_is_fixed_asset",
"target_has_batch_no",
"target_has_serial_no",
"column_break_9",
"target_asset",
"target_asset_name",
"target_warehouse",
"target_qty",
"target_stock_uom",
"target_batch_no",
"target_serial_no",
"column_break_5",
"company",
"finance_book",
"posting_date",
"posting_time",
"set_posting_time",
"amended_from",
"section_break_16",
"stock_items",
"stock_items_total",
"section_break_26",
"asset_items",
"asset_items_total",
"service_expenses_section",
"service_items",
"service_items_total",
"totals_section",
"total_value",
"column_break_36",
"target_incoming_rate",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"target_fixed_asset_account"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"label": "Title"
},
{
"fieldname": "target_item_code",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Item Code",
"options": "Item",
"reqd": 1
},
{
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
"fetch_from": "target_item_code.item_name",
"fieldname": "target_item_name",
"fieldtype": "Data",
"label": "Target Item Name",
"read_only": 1
},
{
"default": "0",
"fetch_from": "target_item_code.is_fixed_asset",
"fieldname": "target_is_fixed_asset",
"fieldtype": "Check",
"hidden": 1,
"label": "Target Is Fixed Asset",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"fieldname": "target_asset",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Target Asset",
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
"no_copy": 1,
"options": "Asset"
},
{
"depends_on": "eval:doc.entry_type=='Capitalization'",
"fetch_from": "target_asset.asset_name",
"fieldname": "target_asset_name",
"fieldtype": "Data",
"label": "Asset Name",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fetch_from": "asset.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Posting Date",
"no_copy": 1,
"reqd": 1,
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus==0",
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "ACC-ASC-.YYYY.-",
"reqd": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Asset Capitalization",
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"label": "Consumed Stock Items"
},
{
"fieldname": "stock_items",
"fieldtype": "Table",
"label": "Stock Items",
"options": "Asset Capitalization Stock Item"
},
{
"depends_on": "eval:doc.entry_type=='Decapitalization'",
"fieldname": "target_warehouse",
"fieldtype": "Link",
"label": "Target Warehouse",
"mandatory_depends_on": "eval:doc.entry_type=='Decapitalization'",
"options": "Warehouse"
},
{
"depends_on": "target_has_batch_no",
"fieldname": "target_batch_no",
"fieldtype": "Link",
"label": "Target Batch No",
"options": "Batch"
},
{
"default": "1",
"fieldname": "target_qty",
"fieldtype": "Float",
"label": "Target Qty",
"read_only_depends_on": "target_is_fixed_asset"
},
{
"fetch_from": "target_item_code.stock_uom",
"fieldname": "target_stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
},
{
"default": "0",
"fetch_from": "target_item_code.has_batch_no",
"fieldname": "target_has_batch_no",
"fieldtype": "Check",
"hidden": 1,
"label": "Target Has Batch No",
"read_only": 1
},
{
"default": "0",
"fetch_from": "target_item_code.has_serial_no",
"fieldname": "target_has_serial_no",
"fieldtype": "Check",
"hidden": 1,
"label": "Target Has Serial No",
"read_only": 1
},
{
"depends_on": "target_has_serial_no",
"fieldname": "target_serial_no",
"fieldtype": "Small Text",
"label": "Target Serial No"
},
{
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"label": "Consumed Asset Items"
},
{
"fieldname": "asset_items",
"fieldtype": "Table",
"label": "Assets",
"options": "Asset Capitalization Asset Item"
},
{
"default": "Capitalization",
"fieldname": "entry_type",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Entry Type",
"options": "Capitalization\nDecapitalization",
"reqd": 1
},
{
"fieldname": "stock_items_total",
"fieldtype": "Currency",
"label": "Consumed Stock Total Value",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "asset_items_total",
"fieldtype": "Currency",
"label": "Consumed Asset Total Value",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
"fieldname": "service_expenses_section",
"fieldtype": "Section Break",
"label": "Service Expenses"
},
{
"fieldname": "service_items",
"fieldtype": "Table",
"label": "Services",
"options": "Asset Capitalization Service Item"
},
{
"fieldname": "service_items_total",
"fieldtype": "Currency",
"label": "Service Expense Total Amount",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "totals_section",
"fieldtype": "Section Break",
"label": "Totals"
},
{
"fieldname": "total_value",
"fieldtype": "Currency",
"label": "Total Value",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "column_break_36",
"fieldtype": "Column Break"
},
{
"fieldname": "target_incoming_rate",
"fieldtype": "Currency",
"label": "Target Incoming Rate",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "target_fixed_asset_account",
"fieldtype": "Link",
"label": "Target Fixed Asset Account",
"options": "Account",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-09-12 15:09:40.771332",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Quality Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}

View File

@ -0,0 +1,749 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
# import erpnext
from frappe import _
from frappe.utils import cint, flt
from six import string_types
import erpnext
from erpnext.assets.doctype.asset.depreciation import (
get_gl_entries_on_asset_disposal,
get_value_after_depreciation_on_disposal_date,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value,
)
from erpnext.controllers.stock_controller import StockController
from erpnext.setup.doctype.brand.brand import get_brand_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.stock.get_item_details import (
get_default_cost_center,
get_default_expense_account,
get_item_warehouse,
)
from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.stock.utils import get_incoming_rate
force_fields = [
"target_item_name",
"target_asset_name",
"item_name",
"asset_name",
"target_is_fixed_asset",
"target_has_serial_no",
"target_has_batch_no",
"target_stock_uom",
"stock_uom",
"target_fixed_asset_account",
"fixed_asset_account",
"valuation_rate",
]
class AssetCapitalization(StockController):
def validate(self):
self.validate_posting_time()
self.set_missing_values(for_validate=True)
self.validate_target_item()
self.validate_target_asset()
self.validate_consumed_stock_item()
self.validate_consumed_asset_item()
self.validate_service_item()
self.set_warehouse_details()
self.set_asset_values()
self.calculate_totals()
self.set_title()
def before_submit(self):
self.validate_source_mandatory()
def on_submit(self):
self.update_stock_ledger()
self.make_gl_entries()
self.update_target_asset()
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.update_stock_ledger()
self.make_gl_entries()
self.update_target_asset()
def set_title(self):
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
def set_missing_values(self, for_validate=False):
target_item_details = get_target_item_details(self.target_item_code, self.company)
for k, v in target_item_details.items():
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
self.set(k, v)
# Remove asset if item not a fixed asset
if not self.target_is_fixed_asset:
self.target_asset = None
target_asset_details = get_target_asset_details(self.target_asset, self.company)
for k, v in target_asset_details.items():
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
self.set(k, v)
for d in self.stock_items:
args = self.as_dict()
args.update(d.as_dict())
args.doctype = self.doctype
args.name = self.name
consumed_stock_item_details = get_consumed_stock_item_details(args)
for k, v in consumed_stock_item_details.items():
if d.meta.has_field(k) and (not d.get(k) or k in force_fields):
d.set(k, v)
for d in self.asset_items:
args = self.as_dict()
args.update(d.as_dict())
args.doctype = self.doctype
args.name = self.name
args.finance_book = d.get("finance_book") or self.get("finance_book")
consumed_asset_details = get_consumed_asset_details(args)
for k, v in consumed_asset_details.items():
if d.meta.has_field(k) and (not d.get(k) or k in force_fields):
d.set(k, v)
for d in self.service_items:
args = self.as_dict()
args.update(d.as_dict())
args.doctype = self.doctype
args.name = self.name
service_item_details = get_service_item_details(args)
for k, v in service_item_details.items():
if d.meta.has_field(k) and (not d.get(k) or k in force_fields):
d.set(k, v)
def validate_target_item(self):
target_item = frappe.get_cached_doc("Item", self.target_item_code)
if not target_item.is_fixed_asset and not target_item.is_stock_item:
frappe.throw(
_("Target Item {0} is neither a Fixed Asset nor a Stock Item").format(target_item.name)
)
if self.entry_type == "Capitalization" and not target_item.is_fixed_asset:
frappe.throw(_("Target Item {0} must be a Fixed Asset item").format(target_item.name))
elif self.entry_type == "Decapitalization" and not target_item.is_stock_item:
frappe.throw(_("Target Item {0} must be a Stock Item").format(target_item.name))
if target_item.is_fixed_asset:
self.target_qty = 1
if flt(self.target_qty) <= 0:
frappe.throw(_("Target Qty must be a positive number"))
if not target_item.is_stock_item:
self.target_warehouse = None
if not target_item.is_fixed_asset:
self.target_asset = None
self.target_fixed_asset_account = None
if not target_item.has_batch_no:
self.target_batch_no = None
if not target_item.has_serial_no:
self.target_serial_no = ""
if target_item.is_stock_item and not self.target_warehouse:
frappe.throw(_("Target Warehouse is mandatory for Decapitalization"))
self.validate_item(target_item)
def validate_target_asset(self):
if self.target_asset:
target_asset = self.get_asset_for_validation(self.target_asset)
if target_asset.item_code != self.target_item_code:
frappe.throw(
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
)
self.validate_asset(target_asset)
def validate_consumed_stock_item(self):
for d in self.stock_items:
if d.item_code:
item = frappe.get_cached_doc("Item", d.item_code)
if not item.is_stock_item:
frappe.throw(_("Row #{0}: Item {1} is not a stock item").format(d.idx, d.item_code))
if flt(d.stock_qty) <= 0:
frappe.throw(_("Row #{0}: Qty must be a positive number").format(d.idx))
self.validate_item(item)
def validate_consumed_asset_item(self):
for d in self.asset_items:
if d.asset:
if d.asset == self.target_asset:
frappe.throw(
_("Row #{0}: Consumed Asset {1} cannot be the same as the Target Asset").format(
d.idx, d.asset
)
)
asset = self.get_asset_for_validation(d.asset)
self.validate_asset(asset)
def validate_service_item(self):
for d in self.service_items:
if d.item_code:
item = frappe.get_cached_doc("Item", d.item_code)
if item.is_stock_item or item.is_fixed_asset:
frappe.throw(_("Row #{0}: Item {1} is not a service item").format(d.idx, d.item_code))
if flt(d.qty) <= 0:
frappe.throw(_("Row #{0}: Qty must be a positive number").format(d.idx))
if flt(d.rate) <= 0:
frappe.throw(_("Row #{0}: Amount must be a positive number").format(d.idx))
self.validate_item(item)
if not d.cost_center:
d.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
def validate_source_mandatory(self):
if not self.target_is_fixed_asset and not self.get("asset_items"):
frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization"))
if not self.get("stock_items") and not self.get("asset_items"):
frappe.throw(_("Consumed Stock Items or Consumed Asset Items is mandatory for Capitalization"))
def validate_item(self, item):
from erpnext.stock.doctype.item.item import validate_end_of_life
validate_end_of_life(item.name, item.end_of_life, item.disabled)
def get_asset_for_validation(self, asset):
return frappe.db.get_value(
"Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1
)
def validate_asset(self, asset):
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status))
if asset.docstatus == 0:
frappe.throw(_("Asset {0} is Draft").format(asset.name))
if asset.docstatus == 2:
frappe.throw(_("Asset {0} is cancelled").format(asset.name))
if asset.company != self.company:
frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company))
@frappe.whitelist()
def set_warehouse_details(self):
for d in self.get("stock_items"):
if d.item_code and d.warehouse:
args = self.get_args_for_incoming_rate(d)
warehouse_details = get_warehouse_details(args)
d.update(warehouse_details)
@frappe.whitelist()
def set_asset_values(self):
for d in self.get("asset_items"):
if d.asset:
finance_book = d.get("finance_book") or self.get("finance_book")
d.current_asset_value = flt(get_current_asset_value(d.asset, finance_book=finance_book))
d.asset_value = get_value_after_depreciation_on_disposal_date(
d.asset, self.posting_date, finance_book=finance_book
)
def get_args_for_incoming_rate(self, item):
return frappe._dict(
{
"item_code": item.item_code,
"warehouse": item.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * flt(item.stock_qty),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company,
"allow_zero_valuation": cint(item.get("allow_zero_valuation_rate")),
}
)
def calculate_totals(self):
self.stock_items_total = 0
self.asset_items_total = 0
self.service_items_total = 0
for d in self.stock_items:
d.amount = flt(flt(d.stock_qty) * flt(d.valuation_rate), d.precision("amount"))
self.stock_items_total += d.amount
for d in self.asset_items:
d.asset_value = flt(flt(d.asset_value), d.precision("asset_value"))
self.asset_items_total += d.asset_value
for d in self.service_items:
d.amount = flt(flt(d.qty) * flt(d.rate), d.precision("amount"))
self.service_items_total += d.amount
self.stock_items_total = flt(self.stock_items_total, self.precision("stock_items_total"))
self.asset_items_total = flt(self.asset_items_total, self.precision("asset_items_total"))
self.service_items_total = flt(self.service_items_total, self.precision("service_items_total"))
self.total_value = self.stock_items_total + self.asset_items_total + self.service_items_total
self.total_value = flt(self.total_value, self.precision("total_value"))
self.target_qty = flt(self.target_qty, self.precision("target_qty"))
self.target_incoming_rate = self.total_value / self.target_qty
def update_stock_ledger(self):
sl_entries = []
for d in self.stock_items:
sle = self.get_sl_entries(
d,
{
"actual_qty": -flt(d.stock_qty),
},
)
sl_entries.append(sle)
if self.entry_type == "Decapitalization" and not self.target_is_fixed_asset:
sle = self.get_sl_entries(
self,
{
"item_code": self.target_item_code,
"warehouse": self.target_warehouse,
"batch_no": self.target_batch_no,
"serial_no": self.target_serial_no,
"actual_qty": flt(self.target_qty),
"incoming_rate": flt(self.target_incoming_rate),
},
)
sl_entries.append(sle)
# reverse sl entries if cancel
if self.docstatus == 2:
sl_entries.reverse()
if sl_entries:
self.make_sl_entries(sl_entries)
def make_gl_entries(self, gl_entries=None, from_repost=False):
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
if self.docstatus == 1:
if not gl_entries:
gl_entries = self.get_gl_entries()
if gl_entries:
make_gl_entries(gl_entries, from_repost=from_repost)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def get_gl_entries(
self, warehouse_account=None, default_expense_account=None, default_cost_center=None
):
# Stock GL Entries
gl_entries = []
self.warehouse_account = warehouse_account
if not self.warehouse_account:
self.warehouse_account = get_warehouse_account_map(self.company)
precision = self.get_debit_field_precision()
self.sle_map = self.get_stock_ledger_details()
target_account = self.get_target_account()
target_against = set()
self.get_gl_entries_for_consumed_stock_items(
gl_entries, target_account, target_against, precision
)
self.get_gl_entries_for_consumed_asset_items(
gl_entries, target_account, target_against, precision
)
self.get_gl_entries_for_consumed_service_items(
gl_entries, target_account, target_against, precision
)
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
return gl_entries
def get_target_account(self):
if self.target_is_fixed_asset:
return self.target_fixed_asset_account
else:
return self.warehouse_account[self.target_warehouse]["account"]
def get_gl_entries_for_consumed_stock_items(
self, gl_entries, target_account, target_against, precision
):
# Consumed Stock Items
for item_row in self.stock_items:
sle_list = self.sle_map.get(item_row.name)
if sle_list:
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, precision)
if erpnext.is_perpetual_inventory_enabled(self.company):
account = self.warehouse_account[sle.warehouse]["account"]
else:
account = self.get_company_default("default_expense_account")
target_against.add(account)
gl_entries.append(
self.get_gl_dict(
{
"account": account,
"against": target_account,
"cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": -1 * stock_value_difference,
},
self.warehouse_account[sle.warehouse]["account_currency"],
item=item_row,
)
)
def get_gl_entries_for_consumed_asset_items(
self, gl_entries, target_account, target_against, precision
):
# Consumed Assets
for item in self.asset_items:
asset = self.get_asset(item)
if asset.calculate_depreciation:
self.depreciate_asset(asset)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.asset_value, item.get("finance_book") or self.get("finance_book")
)
asset.db_set("disposal_date", self.posting_date)
self.set_consumed_asset_status(asset)
for gle in fixed_asset_gl_entries:
gle["against"] = target_account
gl_entries.append(self.get_gl_dict(gle, item=item))
target_against.add(gle["account"])
def get_gl_entries_for_consumed_service_items(
self, gl_entries, target_account, target_against, precision
):
# Service Expenses
for item_row in self.service_items:
expense_amount = flt(item_row.amount, precision)
target_against.add(item_row.expense_account)
gl_entries.append(
self.get_gl_dict(
{
"account": item_row.expense_account,
"against": target_account,
"cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": expense_amount,
},
item=item_row,
)
)
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
if self.target_is_fixed_asset:
# Capitalization
gl_entries.append(
self.get_gl_dict(
{
"account": self.target_fixed_asset_account,
"against": ", ".join(target_against),
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": flt(self.total_value, precision),
"cost_center": self.get("cost_center"),
},
item=self,
)
)
else:
# Target Stock Item
sle_list = self.sle_map.get(self.name)
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, precision)
account = self.warehouse_account[sle.warehouse]["account"]
gl_entries.append(
self.get_gl_dict(
{
"account": account,
"against": ", ".join(target_against),
"cost_center": self.cost_center,
"project": self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": stock_value_difference,
},
self.warehouse_account[sle.warehouse]["account_currency"],
item=self,
)
)
def update_target_asset(self):
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
if self.docstatus == 1 and self.entry_type == "Capitalization":
asset_doc = frappe.get_doc("Asset", self.target_asset)
asset_doc.purchase_date = self.posting_date
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.prepare_depreciation_data()
asset_doc.flags.ignore_validate_update_after_submit = True
asset_doc.save()
elif self.docstatus == 2:
for item in self.asset_items:
asset = self.get_asset(item)
asset.db_set("disposal_date", None)
self.set_consumed_asset_status(asset)
if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_disposal(asset)
self.reset_depreciation_schedule(asset)
def get_asset(self, item):
asset = frappe.get_doc("Asset", item.asset)
self.check_finance_books(item, asset)
return asset
def set_consumed_asset_status(self, asset):
if self.docstatus == 1:
asset.set_status("Capitalized" if self.target_is_fixed_asset else "Decapitalized")
else:
asset.set_status()
@frappe.whitelist()
def get_target_item_details(item_code=None, company=None):
out = frappe._dict()
# Get Item Details
item = frappe._dict()
if item_code:
item = frappe.get_cached_doc("Item", item_code)
# Set Item Details
out.target_item_name = item.item_name
out.target_stock_uom = item.stock_uom
out.target_is_fixed_asset = cint(item.is_fixed_asset)
out.target_has_batch_no = cint(item.has_batch_no)
out.target_has_serial_no = cint(item.has_serial_no)
if out.target_is_fixed_asset:
out.target_qty = 1
out.target_warehouse = None
else:
out.target_asset = None
if not out.target_has_batch_no:
out.target_batch_no = None
if not out.target_has_serial_no:
out.target_serial_no = ""
# Cost Center
item_defaults = get_item_defaults(item.name, company)
item_group_defaults = get_item_group_defaults(item.name, company)
brand_defaults = get_brand_defaults(item.name, company)
out.cost_center = get_default_cost_center(
frappe._dict({"item_code": item.name, "company": company}),
item_defaults,
item_group_defaults,
brand_defaults,
)
return out
@frappe.whitelist()
def get_target_asset_details(asset=None, company=None):
out = frappe._dict()
# Get Asset Details
asset_details = frappe._dict()
if asset:
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
if not asset_details:
frappe.throw(_("Asset {0} does not exist").format(asset))
# Re-set item code from Asset
out.target_item_code = asset_details.item_code
# Set Asset Details
out.asset_name = asset_details.asset_name
if asset_details.item_code:
out.target_fixed_asset_account = get_asset_category_account(
"fixed_asset_account", item=asset_details.item_code, company=company
)
else:
out.target_fixed_asset_account = None
return out
@frappe.whitelist()
def get_consumed_stock_item_details(args):
if isinstance(args, string_types):
args = json.loads(args)
args = frappe._dict(args)
out = frappe._dict()
item = frappe._dict()
if args.item_code:
item = frappe.get_cached_doc("Item", args.item_code)
out.item_name = item.item_name
out.batch_no = None
out.serial_no = ""
out.stock_qty = flt(args.stock_qty) or 1
out.stock_uom = item.stock_uom
out.warehouse = get_item_warehouse(item, args, overwrite_warehouse=True) if item else None
# Cost Center
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
out.cost_center = get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults
)
if args.item_code and out.warehouse:
incoming_rate_args = frappe._dict(
{
"item_code": args.item_code,
"warehouse": out.warehouse,
"posting_date": args.posting_date,
"posting_time": args.posting_time,
"qty": -1 * flt(out.stock_qty),
"voucher_type": args.doctype,
"voucher_no": args.name,
"company": args.company,
"serial_no": args.serial_no,
"batch_no": args.batch_no,
}
)
out.update(get_warehouse_details(incoming_rate_args))
else:
out.valuation_rate = 0
out.actual_qty = 0
return out
@frappe.whitelist()
def get_warehouse_details(args):
if isinstance(args, string_types):
args = json.loads(args)
args = frappe._dict(args)
out = {}
if args.warehouse and args.item_code:
out = {
"actual_qty": get_previous_sle(args).get("qty_after_transaction") or 0,
"valuation_rate": get_incoming_rate(args, raise_error_if_no_rate=False),
}
return out
@frappe.whitelist()
def get_consumed_asset_details(args):
if isinstance(args, string_types):
args = json.loads(args)
args = frappe._dict(args)
out = frappe._dict()
asset_details = frappe._dict()
if args.asset:
asset_details = frappe.db.get_value(
"Asset", args.asset, ["asset_name", "item_code", "item_name"], as_dict=1
)
if not asset_details:
frappe.throw(_("Asset {0} does not exist").format(args.asset))
out.item_code = asset_details.item_code
out.asset_name = asset_details.asset_name
out.item_name = asset_details.item_name
if args.asset:
out.current_asset_value = flt(
get_current_asset_value(args.asset, finance_book=args.finance_book)
)
out.asset_value = get_value_after_depreciation_on_disposal_date(
args.asset, args.posting_date, finance_book=args.finance_book
)
else:
out.current_asset_value = 0
out.asset_value = 0
# Account
if asset_details.item_code:
out.fixed_asset_account = get_asset_category_account(
"fixed_asset_account", item=asset_details.item_code, company=args.company
)
else:
out.fixed_asset_account = None
# Cost Center
if asset_details.item_code:
item = frappe.get_cached_doc("Item", asset_details.item_code)
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
out.cost_center = get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults
)
return out
@frappe.whitelist()
def get_service_item_details(args):
if isinstance(args, string_types):
args = json.loads(args)
args = frappe._dict(args)
out = frappe._dict()
item = frappe._dict()
if args.item_code:
item = frappe.get_cached_doc("Item", args.item_code)
out.item_name = item.item_name
out.qty = flt(args.qty) or 1
out.uom = item.purchase_uom or item.stock_uom
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
out.expense_account = get_default_expense_account(
args, item_defaults, item_group_defaults, brand_defaults
)
out.cost_center = get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults
)
return out

View File

@ -0,0 +1,494 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
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):
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_with_perpetual_inventory(self):
company = "_Test Company with perpetual inventory"
set_depreciation_settings_in_company(company=company)
# Variables
consumed_asset_value = 100000
stock_rate = 1000
stock_qty = 2
stock_amount = 2000
service_rate = 500
service_qty = 2
service_amount = 1000
total_amount = 103000
# Create assets
target_asset = create_asset(
asset_name="Asset Capitalization Target Asset",
submit=1,
warehouse="Stores - TCP1",
company=company,
)
consumed_asset = create_asset(
asset_name="Asset Capitalization Consumable Asset",
asset_value=consumed_asset_value,
submit=1,
warehouse="Stores - TCP1",
company=company,
)
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="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 - TCP1",
company=company,
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 - TCP1": 3000,
"Expenses Included In Asset Valuation - TCP1": -1000,
"_Test Warehouse - TCP1": -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 - TCP1"): {
"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_capitalization_with_periodical_inventory(self):
company = "_Test Company"
# Variables
consumed_asset_value = 100000
stock_rate = 1000
stock_qty = 2
stock_amount = 2000
service_rate = 500
service_qty = 2
service_amount = 1000
total_amount = 103000
# Create assets
target_asset = create_asset(
asset_name="Asset Capitalization Target Asset",
submit=1,
warehouse="Stores - _TC",
company=company,
)
consumed_asset = create_asset(
asset_name="Asset Capitalization Consumable Asset",
asset_value=consumed_asset_value,
submit=1,
warehouse="Stores - _TC",
company=company,
)
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="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",
company=company,
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
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
expected_gle = {
"_Test Fixed Asset - _TC": 3000,
"Expenses Included In Asset Valuation - _TC": -1000,
default_expense_account: -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 minor difference
consumed_asset_value_before_disposal = 55_123.29
target_incoming_rate = 5512.329
depreciation_before_disposal_amount = 14_876.71
accumulated_depreciation = 44_876.71
# 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,
company="_Test Company with perpetual inventory",
submit=1,
)
# Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization(
entry_type="Decapitalization",
posting_date=capitalization_date, # half a year
target_item_code="Capitalization Target Stock Item",
target_qty=target_qty,
consumed_asset=consumed_asset.name,
company="_Test Company with perpetual inventory",
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 = {
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal,
"_Test Accumulated Depreciations - TCP1": accumulated_depreciation,
"_Test Fixed Asset - TCP1": -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.reload()
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(
{
"entry_type": args.entry_type or "Capitalization",
"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 (
EmptyStockReconciliationItemsError,
create_stock_reconciliation,
)
if not asset_capitalization.get("stock_items"):
return
try:
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,
)
except EmptyStockReconciliationItemsError:
pass
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

View File

@ -0,0 +1,128 @@
{
"actions": [],
"creation": "2021-09-05 15:52:10.124538",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"asset",
"asset_name",
"finance_book",
"column_break_3",
"item_code",
"item_name",
"section_break_6",
"current_asset_value",
"asset_value",
"column_break_9",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"fixed_asset_account"
],
"fields": [
{
"fieldname": "asset",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Asset",
"options": "Asset",
"reqd": 1
},
{
"fetch_from": "asset.asset_name",
"fieldname": "asset_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Asset Name",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fetch_from": "asset.item_code",
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"label": "Value"
},
{
"default": "0",
"fieldname": "asset_value",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Asset Value",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "fixed_asset_account",
"fieldtype": "Link",
"label": "Fixed Asset Account",
"options": "Account",
"read_only": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "current_asset_value",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Current Asset Value",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-12 14:30:02.915132",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Asset Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AssetCapitalizationAssetItem(Document):
pass

View File

@ -0,0 +1,122 @@
{
"actions": [],
"creation": "2021-09-06 13:32:08.642060",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_3",
"expense_account",
"section_break_6",
"qty",
"uom",
"column_break_9",
"rate",
"amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
],
"fields": [
{
"bold": 1,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item"
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "expense_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Expense Account",
"options": "Account",
"reqd": 1
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"label": "Qty and Rate"
},
{
"columns": 1,
"default": "1",
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty",
"non_negative": 1
},
{
"columns": 1,
"fetch_from": "stock_item_code.stock_uom",
"fieldname": "uom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "UOM",
"options": "UOM"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"options": "Company:company:default_currency"
},
{
"default": "0",
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-08 15:52:08.598100",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Service Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AssetCapitalizationServiceItem(Document):
pass

View File

@ -0,0 +1,156 @@
{
"actions": [],
"creation": "2021-09-05 15:23:23.492310",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_3",
"warehouse",
"section_break_6",
"stock_qty",
"stock_uom",
"actual_qty",
"column_break_9",
"valuation_rate",
"amount",
"batch_and_serial_no_section",
"batch_no",
"column_break_13",
"serial_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
],
"fields": [
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Warehouse",
"options": "Warehouse",
"reqd": 1
},
{
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"label": "Qty and Rate"
},
{
"columns": 1,
"fieldname": "stock_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty",
"non_negative": 1
},
{
"columns": 1,
"fetch_from": "stock_item_code.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Stock UOM",
"options": "UOM",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Valuation Rate",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"default": "0",
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "batch_and_serial_no_section",
"fieldtype": "Section Break",
"label": "Batch and Serial No"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No"
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"reqd": 1
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fieldname": "actual_qty",
"fieldtype": "Float",
"label": "Actual Qty in Warehouse",
"no_copy": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-08 15:56:20.230548",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AssetCapitalizationStockItem(Document):
pass

View File

@ -129,18 +129,6 @@ class TestAssetRepair(unittest.TestCase):
def test_gl_entries_with_perpetual_inventory(self): def test_gl_entries_with_perpetual_inventory(self):
set_depreciation_settings_in_company(company="_Test Company with perpetual inventory") set_depreciation_settings_in_company(company="_Test Company with perpetual inventory")
asset_category = frappe.get_doc("Asset Category", "Computers")
asset_category.append(
"accounts",
{
"company_name": "_Test Company with perpetual inventory",
"fixed_asset_account": "_Test Fixed Asset - TCP1",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
"depreciation_expense_account": "_Test Depreciations - TCP1",
},
)
asset_category.save()
asset_repair = create_asset_repair( asset_repair = create_asset_repair(
capitalize_repair_cost=1, capitalize_repair_cost=1,
stock_consumption=1, stock_consumption=1,

View File

@ -130,6 +130,17 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"dependencies": "Asset",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Capitalization",
"link_count": 0,
"link_to": "Asset Capitalization",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -172,7 +183,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2022-01-13 17:25:41.730628", "modified": "2022-01-13 18:25:41.730628",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Assets", "name": "Assets",
@ -205,4 +216,4 @@
} }
], ],
"title": "Assets" "title": "Assets"
} }

View File

@ -38,6 +38,7 @@ from erpnext.accounts.party import (
validate_party_frozen_disabled, validate_party_frozen_disabled,
) )
from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry
from erpnext.buying.utils import update_last_purchase_rate from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.print_settings import ( from erpnext.controllers.print_settings import (
set_print_templates_for_item_table, set_print_templates_for_item_table,
@ -1874,6 +1875,99 @@ class AccountsController(TransactionBase):
): ):
throw(_("Conversion rate cannot be 0 or 1")) throw(_("Conversion rate cannot be 0 or 1"))
def check_finance_books(self, item, asset):
if (
len(asset.finance_books) > 1
and not item.get("finance_book")
and not self.get("finance_book")
and asset.finance_books[0].finance_book
):
frappe.throw(
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=self.posting_date)
asset.save()
make_depreciation_entry(asset.name, self.posting_date)
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_disposal(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry()
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == posting_date_of_original_disposal:
if not self.disposal_was_made_on_original_schedule_date(
asset, schedule, row, posting_date_of_original_disposal
) or self.disposal_happens_in_the_future(posting_date_of_original_disposal):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
asset.save()
def get_posting_date_of_disposal_entry(self):
if self.doctype == "Sales Invoice" and self.return_against:
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
else:
return self.posting_date
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def disposal_was_made_on_original_schedule_date(
self, asset, schedule, row, posting_date_of_disposal
):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if orginal_schedule_date == posting_date_of_disposal:
return True
return False
def disposal_happens_in_the_future(self, posting_date_of_disposal):
if posting_date_of_disposal > getdate():
return True
return False
@frappe.whitelist() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):

View File

@ -727,7 +727,12 @@ def create_stock_reconciliation(**args):
sr.set_posting_time = 1 sr.set_posting_time = 1
sr.company = args.company or "_Test Company" sr.company = args.company or "_Test Company"
sr.expense_account = args.expense_account or ( sr.expense_account = args.expense_account or (
"Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC" (
frappe.get_cached_value("Company", sr.company, "stock_adjustment_account")
or "Stock Adjustment - _TC"
)
if frappe.get_all("Stock Ledger Entry", {"company": sr.company})
else "Temporary Opening - _TC"
) )
sr.cost_center = ( sr.cost_center = (
args.cost_center args.cost_center