From 6c748966e732387383399389176afeb2fcc1ab7d Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Mon, 6 Sep 2021 17:27:47 +0500 Subject: [PATCH 01/19] feat: Asset Capitalization Form --- .../doctype/asset_capitalization/__init__.py | 0 .../asset_capitalization.js | 389 +++++++++++++++++ .../asset_capitalization.json | 340 +++++++++++++++ .../asset_capitalization.py | 402 ++++++++++++++++++ .../test_asset_capitalization.py | 8 + .../__init__.py | 0 .../asset_capitalization_asset_item.json | 85 ++++ .../asset_capitalization_asset_item.py | 8 + .../__init__.py | 0 .../asset_capitalization_service_item.json | 129 ++++++ .../asset_capitalization_service_item.py | 8 + .../__init__.py | 0 .../asset_capitalization_stock_item.json | 137 ++++++ .../asset_capitalization_stock_item.py | 8 + erpnext/assets/workspace/assets/assets.json | 13 +- 15 files changed, 1526 insertions(+), 1 deletion(-) create mode 100644 erpnext/assets/doctype/asset_capitalization/__init__.py create mode 100644 erpnext/assets/doctype/asset_capitalization/asset_capitalization.js create mode 100644 erpnext/assets/doctype/asset_capitalization/asset_capitalization.json create mode 100644 erpnext/assets/doctype/asset_capitalization/asset_capitalization.py create mode 100644 erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py create mode 100644 erpnext/assets/doctype/asset_capitalization_asset_item/__init__.py create mode 100644 erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json create mode 100644 erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py create mode 100644 erpnext/assets/doctype/asset_capitalization_service_item/__init__.py create mode 100644 erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json create mode 100644 erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py create mode 100644 erpnext/assets/doctype/asset_capitalization_stock_item/__init__.py create mode 100644 erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json create mode 100644 erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py diff --git a/erpnext/assets/doctype/asset_capitalization/__init__.py b/erpnext/assets/doctype/asset_capitalization/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js new file mode 100644 index 0000000000..9276d00c05 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -0,0 +1,389 @@ +// 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(); + } + + setup_queries() { + var me = this; + + me.setup_warehouse_query(); + + me.frm.set_query("target_item_code", function() { + return erpnext.queries.item(); + }); + + 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"]] + filters['docstatus'] = 1; + + return { + filters: filters + } + }); + + me.frm.set_query("asset", "asset_items", function() { + var filters = { + 'status': ["not in", ["Draft", "Scrapped", "Sold"]], + '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) { + this.get_all_item_warehouse_details(); + } + } + + posting_time() { + if (this.frm.doc.posting_time) { + this.get_all_item_warehouse_details(); + } + } + + finance_book() { + this.get_all_asset_values(); + } + + stock_qty() { + this.calculate_totals(); + } + + qty() { + this.calculate_totals(); + } + + rate() { + this.calculate_totals(); + } + + company() { + var me = this; + + if (me.frm.doc.company) { + frappe.call({ + method: "frappe.client.get_value", + args: { + doctype: "Company", + filters: {"name": me.frm.doc.company}, + fieldname: "cost_center" + }, + callback: function (r) { + if (r.message) { + $.each(me.frm.doc.service_items || [], function (i, d) { + frappe.model.set_value(d.doctype, d.name, "cost_center", r.message.cost_center); + }); + } + } + }); + } + + erpnext.accounts.dimensions.update_dimension(me.frm, me.frm.doctype); + } + + 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, + }, + 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 + }, + 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: 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; + 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; + 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.refresh_fields(); + } +}; + +//$.extend(cur_frm.cscript, new erpnext.assets.AssetCapitalization({frm: cur_frm})); +cur_frm.cscript = new erpnext.assets.AssetCapitalization({frm: cur_frm}); diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json new file mode 100644 index 0000000000..b697c206bf --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -0,0 +1,340 @@ +{ + "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", + "target_item_code", + "target_item_name", + "target_is_fixed_asset", + "target_has_batch_no", + "target_has_serial_no", + "entry_type", + "finance_book", + "naming_series", + "column_break_9", + "company", + "posting_date", + "posting_time", + "set_posting_time", + "amended_from", + "target_item_details_section", + "target_asset", + "target_asset_name", + "target_warehouse", + "target_batch_no", + "target_serial_no", + "column_break_5", + "target_qty", + "target_stock_uom", + "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" + ], + "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.target_item_code || doc.target_is_fixed_asset", + "fieldname": "target_asset", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Target Asset", + "no_copy": 1, + "options": "Asset" + }, + { + "depends_on": "target_asset", + "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 + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break", + "label": "Consumed Stock Items" + }, + { + "fieldname": "stock_items", + "fieldtype": "Table", + "label": "Stock Items", + "options": "Asset Capitalization Stock Item" + }, + { + "fieldname": "target_item_details_section", + "fieldtype": "Section Break", + "label": "Target Item Details" + }, + { + "depends_on": "eval:!doc.target_is_fixed_asset", + "fieldname": "target_warehouse", + "fieldtype": "Link", + "label": "Target Warehouse", + "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" + }, + { + "fieldname": "section_break_26", + "fieldtype": "Section Break", + "label": "Consumed Asset Items" + }, + { + "fieldname": "asset_items", + "fieldtype": "Table", + "label": "Assets", + "options": "Asset Capitalization Asset Item" + }, + { + "fieldname": "entry_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Entry Type", + "options": "\nCapitalization\nDecapitalization", + "read_only": 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" + }, + { + "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 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-09-06 17:18:31.881006", + "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", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py new file mode 100644 index 0000000000..586710a635 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -0,0 +1,402 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from erpnext.controllers.accounts_controller import AccountsController +from frappe.utils import cint, flt +from erpnext.stock.get_item_details import get_item_warehouse, get_default_expense_account, get_default_cost_center +from erpnext.stock.doctype.item.item import get_item_defaults +from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.setup.doctype.brand.brand import get_brand_defaults +from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import get_current_asset_value +from six import string_types +import json + +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'] + + +class AssetCapitalization(AccountsController): + def validate(self): + self.validate_posting_time() + self.set_missing_values(for_validate=True) + self.set_entry_type() + 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 set_entry_type(self): + self.entry_type = "Capitalization" if self.target_is_fixed_asset else "Decapitalization" + + 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) + 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) + 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, get_valuation_rate=False) + 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 + consumed_asset_details = get_consumed_asset_details(args, get_asset_value=False) + 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 target_item.is_fixed_asset: + self.target_qty = 1 + + if not target_item.is_stock_item: + self.target_warehouse = None + if not target_item.is_fixed_asset: + self.target_asset = None + if not target_item.has_batch_no: + self.target_batch_no = None + if not target_item.has_serial_no: + self.target_serial_no = "" + + self.validate_item(target_item) + + def validate_target_asset(self): + if self.target_is_fixed_asset and not self.target_asset: + frappe.throw(_("Target Asset is mandatory for Capitalization")) + + 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.amount) <= 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_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"): + 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(self.target_asset, self.company)) + + @frappe.whitelist() + def set_warehouse_details(self): + for d in self.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.asset_items: + if d.asset: + d.asset_value = flt(get_current_asset_value(d.asset, self.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')) + + +@frappe.whitelist() +def get_target_item_details(item_code=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 = "" + + # Set Entry Type + if not item_code: + out.entry_type = "" + elif out.target_is_fixed_asset: + out.entry_type = "Capitalization" + else: + out.entry_type = "Decapitalization" + + return out + + +@frappe.whitelist() +def get_target_asset_details(asset=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 + + return out + + +@frappe.whitelist() +def get_consumed_stock_item_details(args, get_valuation_rate=True): + 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 + + if get_valuation_rate: + 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, + }) + 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, get_asset_value=True): + 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 get_asset_value: + if args.asset: + out.asset_value = flt(get_current_asset_value(args.asset, finance_book=args.finance_book)) + else: + out.asset_value = 0 + + 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 diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py new file mode 100644 index 0000000000..d8e22c5101 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestAssetCapitalization(unittest.TestCase): + pass diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/__init__.py b/erpnext/assets/doctype/asset_capitalization_asset_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json new file mode 100644 index 0000000000..a0040338c0 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json @@ -0,0 +1,85 @@ +{ + "actions": [], + "creation": "2021-09-05 15:52:10.124538", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "asset", + "asset_name", + "column_break_3", + "item_code", + "item_name", + "section_break_6", + "asset_value", + "column_break_9" + ], + "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" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-06 13:46:04.892863", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Capitalization Asset Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py new file mode 100644 index 0000000000..8817317e70 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py @@ -0,0 +1,8 @@ +# 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 diff --git a/erpnext/assets/doctype/asset_capitalization_service_item/__init__.py b/erpnext/assets/doctype/asset_capitalization_service_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json new file mode 100644 index 0000000000..2d3584dce4 --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json @@ -0,0 +1,129 @@ +{ + "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", + "project" + ], + "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" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-06 14:06:34.768152", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Capitalization Service Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py new file mode 100644 index 0000000000..fa158295ae --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py @@ -0,0 +1,8 @@ +# 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 diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/__init__.py b/erpnext/assets/doctype/asset_capitalization_stock_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json new file mode 100644 index 0000000000..19c455894a --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json @@ -0,0 +1,137 @@ +{ + "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" + ], + "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 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-06 13:46:13.579140", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Capitalization Stock Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py new file mode 100644 index 0000000000..4449538d8e --- /dev/null +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py @@ -0,0 +1,8 @@ +# 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 diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json index dfbf1a378e..cf437a8375 100644 --- a/erpnext/assets/workspace/assets/assets.json +++ b/erpnext/assets/workspace/assets/assets.json @@ -137,6 +137,17 @@ "onboard": 0, "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, "is_query_report": 0, @@ -179,7 +190,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:54.839452", + "modified": "2021-09-06 16:59:02.668813", "modified_by": "Administrator", "module": "Assets", "name": "Assets", From 702b5c32c1e4c1e30bf9e84ac738df30a3b4a435 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 8 Sep 2021 16:36:07 +0500 Subject: [PATCH 02/19] feat(Asset Capitalization): Accounting Fields --- .../asset_capitalization.js | 43 ++++++++++------ .../asset_capitalization.json | 43 +++++++++++++++- .../asset_capitalization.py | 50 +++++++++++++++++-- .../asset_capitalization_asset_item.json | 31 +++++++++++- .../asset_capitalization_service_item.json | 11 +--- .../asset_capitalization_stock_item.json | 23 ++++++++- 6 files changed, 166 insertions(+), 35 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 9276d00c05..b42634a509 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -148,6 +148,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s this.calculate_totals(); } + target_qty() { + this.calculate_totals(); + } + rate() { this.calculate_totals(); } @@ -156,26 +160,29 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s var me = this; if (me.frm.doc.company) { - frappe.call({ - method: "frappe.client.get_value", - args: { - doctype: "Company", - filters: {"name": me.frm.doc.company}, - fieldname: "cost_center" - }, - callback: function (r) { - if (r.message) { - $.each(me.frm.doc.service_items || [], function (i, d) { - frappe.model.set_value(d.doctype, d.name, "cost_center", r.message.cost_center); - }); - } - } + 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'); } @@ -189,6 +196,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s child: me.frm.doc, args: { item_code: me.frm.doc.target_item_code, + company: me.frm.doc.company, }, callback: function (r) { if (!r.exc) { @@ -207,7 +215,8 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details", child: me.frm.doc, args: { - asset: me.frm.doc.target_asset + asset: me.frm.doc.target_asset, + company: me.frm.doc.company, }, callback: function (r) { if (!r.exc) { @@ -381,6 +390,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s 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(); } }; diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index b697c206bf..0582b1ebc1 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -41,7 +41,13 @@ "service_items", "service_items_total", "totals_section", - "total_value" + "total_value", + "column_break_36", + "target_incoming_rate", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "target_fixed_asset_account" ], "fields": [ { @@ -289,12 +295,45 @@ "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" + }, + { + "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": "2021-09-06 17:18:31.881006", + "modified": "2021-09-08 15:58:40.417579", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization", diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 586710a635..64f13887c2 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -11,13 +11,14 @@ from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.stock.utils import get_incoming_rate from erpnext.stock.stock_ledger import get_previous_sle +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 six import string_types import json 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_stock_uom', 'stock_uom', 'target_fixed_asset_account', 'fixed_asset_account'] class AssetCapitalization(AccountsController): @@ -42,7 +43,7 @@ class AssetCapitalization(AccountsController): 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) + 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) @@ -51,7 +52,7 @@ class AssetCapitalization(AccountsController): if not self.target_is_fixed_asset: self.target_asset = None - target_asset_details = get_target_asset_details(self.target_asset) + 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) @@ -95,6 +96,8 @@ class AssetCapitalization(AccountsController): 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 @@ -233,9 +236,12 @@ class AssetCapitalization(AccountsController): 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 + @frappe.whitelist() -def get_target_item_details(item_code=None): +def get_target_item_details(item_code=None, company=None): out = frappe._dict() # Get Item Details @@ -261,6 +267,13 @@ def get_target_item_details(item_code=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) + # Set Entry Type if not item_code: out.entry_type = "" @@ -273,7 +286,7 @@ def get_target_item_details(item_code=None): @frappe.whitelist() -def get_target_asset_details(asset=None): +def get_target_asset_details(asset=None, company=None): out = frappe._dict() # Get Asset Details @@ -289,6 +302,12 @@ def get_target_asset_details(asset=None): # 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 @@ -313,6 +332,12 @@ def get_consumed_stock_item_details(args, get_valuation_rate=True): 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 get_valuation_rate: if args.item_code and out.warehouse: incoming_rate_args = frappe._dict({ @@ -373,6 +398,21 @@ def get_consumed_asset_details(args, get_asset_value=True): else: 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 diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json index a0040338c0..93ec336b15 100644 --- a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json +++ b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json @@ -12,7 +12,11 @@ "item_name", "section_break_6", "asset_value", - "column_break_9" + "column_break_9", + "accounting_dimensions_section", + "fixed_asset_account", + "dimension_col_break", + "cost_center" ], "fields": [ { @@ -68,12 +72,35 @@ { "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" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-06 13:46:04.892863", + "modified": "2021-09-08 15:54:24.885547", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Asset Item", diff --git a/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json index 2d3584dce4..0ae1c1428e 100644 --- a/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json +++ b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json @@ -17,8 +17,7 @@ "amount", "accounting_dimensions_section", "cost_center", - "dimension_col_break", - "project" + "dimension_col_break" ], "fields": [ { @@ -106,18 +105,12 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-06 14:06:34.768152", + "modified": "2021-09-08 15:52:08.598100", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Service Item", diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json index 19c455894a..14eb0f6ef2 100644 --- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json @@ -19,7 +19,10 @@ "batch_and_serial_no_section", "batch_no", "column_break_13", - "serial_no" + "serial_no", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break" ], "fields": [ { @@ -120,12 +123,28 @@ "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-06 13:46:13.579140", + "modified": "2021-09-08 15:56:20.230548", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Stock Item", From 3b9bc8e4effcd46d53c30169ea24df2ae849ad5c Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Sun, 12 Sep 2021 14:28:14 +0500 Subject: [PATCH 03/19] feat(Asset Capitalization): Finance Book field in Asset Row --- .../asset_capitalization/asset_capitalization.js | 11 ++++++++--- .../asset_capitalization/asset_capitalization.py | 3 ++- .../asset_capitalization_asset_item.json | 13 ++++++++++--- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index b42634a509..b0f7712d6e 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -136,8 +136,13 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } } - finance_book() { - this.get_all_asset_values(); + 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() { @@ -268,7 +273,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s doctype: me.frm.doc.doctype, name: me.frm.doc.name, company: me.frm.doc.company, - finance_book: me.frm.doc.finance_book, + finance_book: row.finance_book || me.frm.doc.finance_book, posting_date: me.frm.doc.posting_date, posting_time: me.frm.doc.posting_time, } diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 64f13887c2..b29decb2d9 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -72,6 +72,7 @@ class AssetCapitalization(AccountsController): 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, get_asset_value=False) for k, v in consumed_asset_details.items(): if d.meta.has_field(k) and (not d.get(k) or k in force_fields): @@ -195,7 +196,7 @@ class AssetCapitalization(AccountsController): def set_asset_values(self): for d in self.asset_items: if d.asset: - d.asset_value = flt(get_current_asset_value(d.asset, self.finance_book)) + d.asset_value = flt(get_current_asset_value(d.asset, d.get('finance_book') or self.finance_book)) def get_args_for_incoming_rate(self, item): return frappe._dict({ diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json index 93ec336b15..a5f820299b 100644 --- a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json +++ b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json @@ -7,6 +7,7 @@ "field_order": [ "asset", "asset_name", + "finance_book", "column_break_3", "item_code", "item_name", @@ -14,9 +15,9 @@ "asset_value", "column_break_9", "accounting_dimensions_section", - "fixed_asset_account", + "cost_center", "dimension_col_break", - "cost_center" + "fixed_asset_account" ], "fields": [ { @@ -95,12 +96,18 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "finance_book", + "fieldtype": "Link", + "label": "Finance Book", + "options": "Finance Book" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-08 15:54:24.885547", + "modified": "2021-09-08 23:42:25.143272", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Asset Item", From 7a5d75b68d278cf100c9b73ab4ae0475b820eb67 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Mon, 13 Sep 2021 23:01:52 +0500 Subject: [PATCH 04/19] feat(Asset Capitalization): Submission and Cancellation --- .../doctype/sales_invoice/sales_invoice.py | 74 +----- erpnext/assets/doctype/asset/asset_list.js | 3 + erpnext/assets/doctype/asset/depreciation.py | 29 ++- .../asset_capitalization.js | 9 +- .../asset_capitalization.py | 217 +++++++++++++++++- .../asset_capitalization_asset_item.json | 11 +- erpnext/controllers/accounts_controller.py | 81 +++++++ 7 files changed, 340 insertions(+), 84 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ec249c2419..3af7b24b07 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -37,7 +37,6 @@ from erpnext.assets.doctype.asset.depreciation import ( get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - post_depreciation_entries, ) from erpnext.controllers.selling_controller import SellingController from erpnext.healthcare.utils import manage_invoice_submit_cancel @@ -166,7 +165,7 @@ class SalesInvoice(SellingController): if self.update_stock: frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) - elif asset.status in ("Scrapped", "Cancelled") or (asset.status == "Sold" and not self.is_return): + elif asset.status in ("Scrapped", "Cancelled", "Capitalized", "Decapitalized") or (asset.status == "Sold" and not self.is_return): frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status)) def validate_item_cost_centers(self): @@ -1007,77 +1006,6 @@ class SalesInvoice(SellingController): self.check_finance_books(item, 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(self.posting_date) - asset.save() - - post_depreciation_entries(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() - - self.modify_depreciation_schedule_for_asset_repairs(asset) - asset.save() - - self.delete_depreciation_entry_made_after_sale(asset) - - 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 delete_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): - reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) - reverse_journal_entry.posting_date = nowdate() - reverse_journal_entry.submit() - - 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 - @property def enable_discount_accounting(self): if not hasattr(self, "_enable_discount_accounting"): diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js index 4302cb2c51..3d00eb74aa 100644 --- a/erpnext/assets/doctype/asset/asset_list.js +++ b/erpnext/assets/doctype/asset/asset_list.js @@ -10,6 +10,9 @@ frappe.listview_settings['Asset'] = { } else if (doc.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") { return [__("Scrapped"), "grey", "status,=,Scrapped"]; diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 609791012a..58d4bb5ebb 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -13,7 +13,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 if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")): return @@ -22,7 +22,8 @@ def post_depreciation_entries(date=None): date = today() for asset in get_depreciable_assets(date): make_depreciation_entry(asset, date) - frappe.db.commit() + if commit: + frappe.db.commit() def get_depreciable_assets(date): return frappe.db.sql_list("""select a.name @@ -140,7 +141,7 @@ def scrap_asset(asset_name): if asset.docstatus != 1: 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(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)) depreciation_series = frappe.get_cached_value('Company', asset.company, "series_for_depreciation_entry") @@ -269,3 +270,25 @@ def get_disposal_account_and_cost_center(company): frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company)) 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) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index b0f7712d6e..4f8c95e9a0 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -15,6 +15,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s refresh() { erpnext.hide_company(); + this.show_general_ledger(); + if (this.frm.doc.stock_items || !this.frm.doc.target_is_fixed_asset) { + this.show_stock_ledger(); + } } setup_queries() { @@ -33,7 +37,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s filters['item_code'] = me.frm.doc.target_item_code; } - filters['status'] = ["not in", ["Draft", "Scrapped", "Sold"]] + filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]] filters['docstatus'] = 1; return { @@ -43,7 +47,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s me.frm.set_query("asset", "asset_items", function() { var filters = { - 'status': ["not in", ["Draft", "Scrapped", "Sold"]], + 'status': ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]], 'docstatus': 1 } @@ -127,6 +131,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s posting_date() { if (this.frm.doc.posting_date) { this.get_all_item_warehouse_details(); + this.get_all_asset_values(); } } diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index b29decb2d9..e50ddfaba8 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -2,8 +2,9 @@ # For license information, please see license.txt import frappe +# import erpnext from frappe import _ -from erpnext.controllers.accounts_controller import AccountsController +from erpnext.controllers.stock_controller import StockController from frappe.utils import cint, flt from erpnext.stock.get_item_details import get_item_warehouse, get_default_expense_account, get_default_cost_center from erpnext.stock.doctype.item.item import get_item_defaults @@ -13,6 +14,9 @@ from erpnext.stock.utils import get_incoming_rate from erpnext.stock.stock_ledger import get_previous_sle 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.stock import get_warehouse_account_map +from erpnext.assets.doctype.asset.depreciation import get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain,\ + get_value_after_depreciation_on_disposal_date from six import string_types import json @@ -21,7 +25,7 @@ force_fields = ['target_item_name', 'target_asset_name', 'item_name', 'asset_nam 'target_stock_uom', 'stock_uom', 'target_fixed_asset_account', 'fixed_asset_account'] -class AssetCapitalization(AccountsController): +class AssetCapitalization(StockController): def validate(self): self.validate_posting_time() self.set_missing_values(for_validate=True) @@ -36,6 +40,18 @@ class AssetCapitalization(AccountsController): 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() + + def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') + self.update_stock_ledger() + self.make_gl_entries() + def set_entry_type(self): self.entry_type = "Capitalization" if self.target_is_fixed_asset else "Decapitalization" @@ -104,11 +120,15 @@ class AssetCapitalization(AccountsController): 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): @@ -165,6 +185,13 @@ class AssetCapitalization(AccountsController): 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) @@ -173,7 +200,7 @@ class AssetCapitalization(AccountsController): 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"): + if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"): frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status)) if asset.docstatus == 0: @@ -196,7 +223,10 @@ class AssetCapitalization(AccountsController): def set_asset_values(self): for d in self.asset_items: if d.asset: - d.asset_value = flt(get_current_asset_value(d.asset, d.get('finance_book') or self.finance_book)) + 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({ @@ -240,6 +270,180 @@ class AssetCapitalization(AccountsController): 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 not frappe.db.get_value("Item", self.target_item_code, "is_fixed_asset", cache=1): + 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 not gl_entries: + gl_entries = self.get_gl_entries() + + if self.docstatus == 1: + 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 = [] + + if not warehouse_account: + warehouse_account = get_warehouse_account_map(self.company) + + precision = self.get_debit_field_precision() + sle_map = self.get_stock_ledger_details() + + if self.target_is_fixed_asset: + target_account = self.target_fixed_asset_account + else: + target_account = warehouse_account[self.target_warehouse]["account"] + + target_against = set() + + # Consumed Stock Items + total_consumed_stock_value = 0 + for item_row in self.stock_items: + sle_list = sle_map.get(item_row.name) + if sle_list: + for sle in sle_list: + stock_value_difference = flt(sle.stock_value_difference, precision) + total_consumed_stock_value += -1 * sle.stock_value_difference + + account = warehouse_account[sle.warehouse]["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, + }, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) + + # Consumed Assets + for item in self.asset_items: + asset = self.get_asset(item) + + if self.docstatus == 2: + fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset, + item.asset_value, item.get('finance_book') or self.get('finance_book')) + asset.db_set("disposal_date", None) + + self.set_consumed_asset_status(asset) + + if asset.calculate_depreciation: + self.reset_depreciation_schedule(asset) + else: + 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)) + + # Service Expenses + total_service_expenses = 0 + for item_row in self.service_items: + expense_amount = flt(item_row.amount, precision) + total_service_expenses += expense_amount + 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)) + + target_against = ", ".join(target_against) + total_target_stock_value = 0 + total_target_asset_value = 0 + + if self.target_is_fixed_asset: + # Target Asset Item + total_target_asset_value = flt(self.total_value, precision) + gl_entries.append(self.get_gl_dict({ + "account": self.target_fixed_asset_account, + "against": target_against, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": total_target_asset_value, + "cost_center": self.get('cost_center') + }, item=self)) + + if self.docstatus == 1: + 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() + else: + # Target Stock Item + sle_list = sle_map.get(self.name) + for sle in sle_list: + stock_value_difference = flt(sle.stock_value_difference, precision) + total_target_stock_value += sle.stock_value_difference + account = warehouse_account[sle.warehouse]["account"] + + gl_entries.append(self.get_gl_dict({ + "account": account, + "against": target_against, + "cost_center": self.cost_center, + "project": self.get('project'), + "remarks": self.get("remarks") or "Accounting Entry for Stock", + "debit": stock_value_difference, + }, warehouse_account[sle.warehouse]["account_currency"], item=self)) + + return gl_entries + + 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): @@ -395,8 +599,11 @@ def get_consumed_asset_details(args, get_asset_value=True): if get_asset_value: if args.asset: - out.asset_value = flt(get_current_asset_value(args.asset, finance_book=args.finance_book)) + 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 diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json index a5f820299b..ebaaffbad1 100644 --- a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json +++ b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json @@ -12,6 +12,7 @@ "item_code", "item_name", "section_break_6", + "current_asset_value", "asset_value", "column_break_9", "accounting_dimensions_section", @@ -102,12 +103,20 @@ "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-08 23:42:25.143272", + "modified": "2021-09-12 14:30:02.915132", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization Asset Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b5..930dca8245 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -54,6 +54,7 @@ from erpnext.stock.get_item_details import ( get_item_tax_map, get_item_warehouse, ) +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.utilities.transaction_base import TransactionBase @@ -1457,6 +1458,86 @@ class AccountsController(TransactionBase): jv.save() jv.submit() + 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(self.posting_date) + asset.save() + + post_depreciation_entries(self.posting_date, commit=False) + + 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() + + self.modify_depreciation_schedule_for_asset_repairs(asset) + asset.save() + + self.delete_depreciation_entry_made_after_disposal(asset) + + 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 delete_depreciation_entry_made_after_disposal(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_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_invoice: + if not self.disposal_was_made_on_original_schedule_date(asset, schedule, row, + posting_date_of_original_invoice): + reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) + reverse_journal_entry.posting_date = nowdate() + + for d in reverse_journal_entry.accounts: + d.reference_type = "Asset" + d.reference_name = asset.name + + reverse_journal_entry.submit() + + 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_original_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_original_disposal: + return True + return False + @frappe.whitelist() def get_tax_rate(account_head): return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) From 8c54be7e999e2c5e7a61ae69a2bae8e624a939b0 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 14 Sep 2021 12:30:40 +0500 Subject: [PATCH 05/19] chore(Asset Capitalization): linting --- .../asset_capitalization.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 4f8c95e9a0..892f8c7d4a 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -37,27 +37,27 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s filters['item_code'] = me.frm.doc.target_item_code; } - filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]] + 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] + filters['name'] = ['!=', me.frm.doc.target_asset]; } return { filters: filters - } + }; }); me.frm.set_query("item_code", "stock_items", function() { @@ -70,19 +70,19 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s me.frm.set_query('batch_no', 'stock_items', function(doc, cdt, cdn) { var item = locals[cdt][cdn]; - if(!item.item_code) { + 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", + query: "erpnext.controllers.queries.get_batch_no", filters: filters - } + }; } }); @@ -318,7 +318,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s get_warehouse_details(item) { var me = this; - if(item.item_code && item.warehouse) { + if (item.item_code && item.warehouse) { me.frm.call({ method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_warehouse_details", child: item, From dc24a657fd2c71f03c0f4f6d5a0c1460152725ca Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 14 Sep 2021 12:40:17 +0500 Subject: [PATCH 06/19] chore(Asset Capitalization): linting --- .../doctype/sales_invoice/sales_invoice.py | 1 - .../asset_capitalization/asset_capitalization.py | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3af7b24b07..b2188e1e5e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -10,7 +10,6 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.utils import ( add_days, - add_months, cint, cstr, flt, diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index e50ddfaba8..856ace2ee5 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -5,8 +5,15 @@ import frappe # import erpnext from frappe import _ from erpnext.controllers.stock_controller import StockController -from frappe.utils import cint, flt -from erpnext.stock.get_item_details import get_item_warehouse, get_default_expense_account, get_default_cost_center +from frappe.utils import ( + cint, + flt +) +from erpnext.stock.get_item_details import ( + get_item_warehouse, + get_default_expense_account, + get_default_cost_center +) from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.brand.brand import get_brand_defaults @@ -15,8 +22,11 @@ from erpnext.stock.stock_ledger import get_previous_sle 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.stock import get_warehouse_account_map -from erpnext.assets.doctype.asset.depreciation import get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain,\ +from erpnext.assets.doctype.asset.depreciation import ( + get_gl_entries_on_asset_disposal, + get_gl_entries_on_asset_regain, get_value_after_depreciation_on_disposal_date +) from six import string_types import json From 8873ef7b675965f920d0534caa981091910d9d4b Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 14 Sep 2021 15:05:39 +0500 Subject: [PATCH 07/19] chore(Asset Capitalization): isort linting --- .../asset_capitalization.py | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 856ace2ee5..129b1aa4e1 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -1,34 +1,36 @@ # 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 erpnext.controllers.stock_controller import StockController -from frappe.utils import ( - cint, - flt -) -from erpnext.stock.get_item_details import ( - get_item_warehouse, - get_default_expense_account, - get_default_cost_center -) -from erpnext.stock.doctype.item.item import get_item_defaults -from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults -from erpnext.setup.doctype.brand.brand import get_brand_defaults -from erpnext.stock.utils import get_incoming_rate -from erpnext.stock.stock_ledger import get_previous_sle -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.stock import get_warehouse_account_map +from frappe.utils import cint, flt +from six import string_types + from erpnext.assets.doctype.asset.depreciation import ( get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - get_value_after_depreciation_on_disposal_date + get_value_after_depreciation_on_disposal_date, ) -from six import string_types -import json +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', From 9ae0380a96ebe7521e28262c5d9e10914d45de18 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 14 Sep 2021 15:09:58 +0500 Subject: [PATCH 08/19] chore(Asset Capitalization): isort linting --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +---------- erpnext/controllers/accounts_controller.py | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b2188e1e5e..14f4787cda 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -8,16 +8,7 @@ from frappe import _, msgprint, throw from frappe.contacts.doctype.address.address import get_address_display from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values -from frappe.utils import ( - add_days, - cint, - cstr, - flt, - formatdate, - get_link_to_form, - getdate, - nowdate, -) +from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate from six import iteritems import erpnext diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 930dca8245..31af7f3744 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -38,6 +38,7 @@ from erpnext.accounts.party import ( validate_party_frozen_disabled, ) from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.print_settings import ( set_print_templates_for_item_table, @@ -54,7 +55,6 @@ from erpnext.stock.get_item_details import ( get_item_tax_map, get_item_warehouse, ) -from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.utilities.transaction_base import TransactionBase From d173e06e69feb60f59e7c43b7daa6752a3236db8 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 14 Sep 2021 15:13:35 +0500 Subject: [PATCH 09/19] chore(Asset Capitalization): isort linting --- .../doctype/asset_capitalization/test_asset_capitalization.py | 1 + .../asset_capitalization_asset_item.py | 1 + .../asset_capitalization_service_item.py | 1 + .../asset_capitalization_stock_item.py | 1 + 4 files changed, 4 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index d8e22c5101..da128467fe 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestAssetCapitalization(unittest.TestCase): pass diff --git a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py index 8817317e70..ba356d6b9f 100644 --- a/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py +++ b/erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class AssetCapitalizationAssetItem(Document): pass diff --git a/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py index fa158295ae..28d018ee39 100644 --- a/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py +++ b/erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class AssetCapitalizationServiceItem(Document): pass diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py index 4449538d8e..5d6f98d5cf 100644 --- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py +++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class AssetCapitalizationStockItem(Document): pass From 132b517584352a8a6c895fd3256bdc08b03c1df5 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 16 Sep 2021 23:20:36 +0500 Subject: [PATCH 10/19] fix(Asset Captalization): run_serially on posting_date changed --- .../asset_capitalization/asset_capitalization.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 892f8c7d4a..d135e60ae7 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -16,7 +16,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s refresh() { erpnext.hide_company(); this.show_general_ledger(); - if (this.frm.doc.stock_items || !this.frm.doc.target_is_fixed_asset) { + if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) { this.show_stock_ledger(); } } @@ -130,8 +130,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s posting_date() { if (this.frm.doc.posting_date) { - this.get_all_item_warehouse_details(); - this.get_all_asset_values(); + frappe.run_serially([ + () => this.get_all_item_warehouse_details(), + () => this.get_all_asset_values() + ]); } } @@ -347,7 +349,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s get_all_item_warehouse_details() { var me = this; - me.frm.call({ + return me.frm.call({ method: "set_warehouse_details", doc: me.frm.doc, callback: function(r) { @@ -360,7 +362,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s get_all_asset_values() { var me = this; - me.frm.call({ + return me.frm.call({ method: "set_asset_values", doc: me.frm.doc, callback: function(r) { From 003cfe27172f1f1eecf1e663b7c0559c1a009be6 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 16 Sep 2021 23:21:09 +0500 Subject: [PATCH 11/19] fix(Asset Capitalization): Hide source items section if table is empty --- .../asset_capitalization/asset_capitalization.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index 0582b1ebc1..d7e6b54716 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -158,6 +158,7 @@ "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" @@ -227,6 +228,7 @@ "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" @@ -267,6 +269,7 @@ "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" @@ -304,7 +307,8 @@ "fieldname": "target_incoming_rate", "fieldtype": "Currency", "label": "Target Incoming Rate", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 }, { "collapsible": 1, @@ -333,7 +337,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-09-08 15:58:40.417579", + "modified": "2021-09-15 15:41:27.917458", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization", From e832944dfede4e979691ec7a4dd22c2fd89de4f7 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 16 Sep 2021 23:22:31 +0500 Subject: [PATCH 12/19] fix(Asset): On Depreciation reversal, remove Journal Entry reference --- erpnext/controllers/accounts_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 31af7f3744..ba0c7a8a8b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1475,13 +1475,12 @@ class AccountsController(TransactionBase): asset.flags.ignore_validate_update_after_submit = True # recreate original depreciation schedule of the asset + self.delete_depreciation_entry_made_after_disposal(asset) asset.prepare_depreciation_data() self.modify_depreciation_schedule_for_asset_repairs(asset) asset.save() - self.delete_depreciation_entry_made_after_disposal(asset) - def modify_depreciation_schedule_for_asset_repairs(self, asset): asset_repairs = frappe.get_all( 'Asset Repair', @@ -1511,7 +1510,7 @@ class AccountsController(TransactionBase): if schedule.schedule_date == posting_date_of_original_invoice: if not self.disposal_was_made_on_original_schedule_date(asset, schedule, row, - posting_date_of_original_invoice): + posting_date_of_original_invoice) or getdate(schedule.schedule_date) > getdate(today()): reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() @@ -1520,6 +1519,8 @@ class AccountsController(TransactionBase): d.reference_name = asset.name reverse_journal_entry.submit() + schedule.db_set('journal_entry', None) + def get_posting_date_of_disposal_entry(self): if self.doctype == "Sales Invoice" and self.return_against: From c311b8ea4f5c51c18462f671d26ede1c18f7f5b6 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 16 Sep 2021 23:23:22 +0500 Subject: [PATCH 13/19] fix(Asset Capitalization): validation edge cases --- .../doctype/asset_capitalization/asset_capitalization.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 129b1aa4e1..5a2398650b 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -189,7 +189,7 @@ class AssetCapitalization(StockController): if flt(d.qty) <= 0: frappe.throw(_("Row #{0}: Qty must be a positive number").format(d.idx)) - if flt(d.amount) <= 0: + if flt(d.rate) <= 0: frappe.throw(_("Row #{0}: Amount must be a positive number").format(d.idx)) self.validate_item(item) @@ -221,11 +221,11 @@ class AssetCapitalization(StockController): 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(self.target_asset, 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.stock_items: + 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) @@ -233,7 +233,7 @@ class AssetCapitalization(StockController): @frappe.whitelist() def set_asset_values(self): - for d in self.asset_items: + 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)) From 86a6293e6226fbadbdb28ce681d2b39a77e3acc2 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 16 Sep 2021 23:24:46 +0500 Subject: [PATCH 14/19] test(Asset Capitalization): unit tests --- erpnext/assets/doctype/asset/test_asset.py | 6 +- .../test_asset_capitalization.py | 329 +++++++++++++++++- 2 files changed, 330 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4cc9be5b05..2bb5a09908 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -702,9 +702,9 @@ def create_asset(**args): "company": args.company or"_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, - "gross_purchase_amount": 100000, - "purchase_receipt_amount": 100000, - "expected_value_after_useful_life": 10000, + "gross_purchase_amount": args.asset_value or 100000, + "purchase_receipt_amount": args.asset_value or 100000, + "expected_value_after_useful_life": args.asset_value or 10000, "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": "2020-06-06", "location": "Test Location", diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index da128467fe..9bfc88b28c 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -1,9 +1,334 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe import unittest +import frappe +from frappe.utils import cint, flt, getdate, now_datetime + +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries +from erpnext.assets.doctype.asset.test_asset import ( + create_asset, + create_asset_data, + set_depreciation_settings_in_company, +) +from erpnext.stock.doctype.item.test_item import create_item + class TestAssetCapitalization(unittest.TestCase): - pass + def setUp(self): + set_depreciation_settings_in_company() + create_asset_data() + create_asset_capitalization_data() + frappe.db.sql("delete from `tabTax Rule`") + + def test_capitalization(self): + # Variables + consumed_asset_value = 100_000 + + stock_rate = 1000 + stock_qty = 2 + stock_amount = 2000 + + service_rate = 500 + service_qty = 2 + service_amount = 1000 + + total_amount = 103_000 + + # Create assets + target_asset = create_asset(asset_name='Asset Capitalization Target Asset', submit=1) + consumed_asset = create_asset(asset_name='Asset Capitalization Consumable Asset', asset_value=consumed_asset_value, + submit=1) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization(target_asset=target_asset.name, + stock_qty=stock_qty, stock_rate=stock_rate, + consumed_asset=consumed_asset.name, + service_qty=service_qty, service_rate=service_rate, + service_expense_account='Expenses Included In Asset Valuation - _TC', + submit=1) + + # Test Asset Capitalization values + self.assertEqual(asset_capitalization.entry_type, 'Capitalization') + self.assertEqual(asset_capitalization.target_qty, 1) + + self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate) + self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount) + self.assertEqual(asset_capitalization.stock_items_total, stock_amount) + + self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value) + self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value) + + self.assertEqual(asset_capitalization.service_items[0].amount, service_amount) + self.assertEqual(asset_capitalization.service_items_total, service_amount) + + self.assertEqual(asset_capitalization.total_value, total_amount) + self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) + + # Test Target Asset values + target_asset.reload() + self.assertEqual(target_asset.gross_purchase_amount, total_amount) + self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + + # Test Consumed Asset values + self.assertEqual(consumed_asset.db_get('status'), 'Capitalized') + + # Test General Ledger Entries + expected_gle = { + '_Test Fixed Asset - _TC': 3000, + 'Expenses Included In Asset Valuation - _TC': -1000, + 'Stock In Hand - _TC' : -2000 + } + actual_gle = get_actual_gle_dict(asset_capitalization.name) + + self.assertEqual(actual_gle, expected_gle) + + # Test Stock Ledger Entries + expected_sle = { + ('Capitalization Source Stock Item', '_Test Warehouse - _TC'): { + 'actual_qty': -stock_qty, 'stock_value_difference': -stock_amount + } + } + actual_sle = get_actual_sle_dict(asset_capitalization.name) + + self.assertEqual(actual_sle, expected_sle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.assertEqual(consumed_asset.db_get('status'), 'Submitted') + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + + def test_decapitalization_with_depreciation(self): + # Variables + purchase_date = '2020-01-01' + depreciation_start_date = '2020-12-31' + capitalization_date = '2021-06-30' + + total_number_of_depreciations = 3 + expected_value_after_useful_life = 10_000 + consumed_asset_purchase_value = 100_000 + consumed_asset_current_value = 70_000 + consumed_asset_value_before_disposal = 55_000 + + target_qty = 10 + target_incoming_rate = 5500 + + depreciation_before_disposal_amount = 15_000 + accumulated_depreciation = 45_000 + + # to accomodate for depreciation on disposal calculation bugs TODO remove this when bug is fixed + consumed_asset_value_before_disposal = 60_082.19 + target_incoming_rate = 6008.219 + depreciation_before_disposal_amount = 9917.81 + accumulated_depreciation = 39_917.81 + + # Create assets + consumed_asset = create_depreciation_asset( + asset_name='Asset Capitalization Consumable Asset', + asset_value=consumed_asset_purchase_value, + purchase_date=purchase_date, + depreciation_start_date=depreciation_start_date, + depreciation_method='Straight Line', + total_number_of_depreciations=total_number_of_depreciations, + frequency_of_depreciation=12, + expected_value_after_useful_life=expected_value_after_useful_life, + submit=1) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization( + posting_date=capitalization_date, # half a year + target_item_code="Capitalization Target Stock Item", + target_qty=target_qty, + consumed_asset=consumed_asset.name, + submit=1) + + # Test Asset Capitalization values + self.assertEqual(asset_capitalization.entry_type, 'Decapitalization') + + self.assertEqual(asset_capitalization.asset_items[0].current_asset_value, consumed_asset_current_value) + self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value_before_disposal) + self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value_before_disposal) + + self.assertEqual(asset_capitalization.total_value, consumed_asset_value_before_disposal) + self.assertEqual(asset_capitalization.target_incoming_rate, target_incoming_rate) + + # Test Consumed Asset values + consumed_asset.reload() + self.assertEqual(consumed_asset.status, 'Decapitalized') + + consumed_depreciation_schedule = [d for d in consumed_asset.schedules + if getdate(d.schedule_date) == getdate(capitalization_date)] + self.assertTrue(consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry) + self.assertEqual(consumed_depreciation_schedule[0].depreciation_amount, depreciation_before_disposal_amount) + + # Test General Ledger Entries + expected_gle = { + 'Stock In Hand - _TC': consumed_asset_value_before_disposal, + '_Test Accumulated Depreciations - _TC': accumulated_depreciation, + '_Test Fixed Asset - _TC': -consumed_asset_purchase_value, + } + actual_gle = get_actual_gle_dict(asset_capitalization.name) + + self.assertEqual(actual_gle, expected_gle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.assertEqual(consumed_asset.db_get('status'), 'Partially Depreciated') + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + + +def create_asset_capitalization_data(): + create_item("Capitalization Target Stock Item", + is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) + create_item("Capitalization Source Stock Item", + is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) + create_item("Capitalization Source Service Item", + is_stock_item=0, is_fixed_asset=0, is_purchase_item=0) + + +def create_asset_capitalization(**args): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + args = frappe._dict(args) + + now = now_datetime() + target_asset = frappe.get_doc("Asset", args.target_asset) if args.target_asset else frappe._dict() + target_item_code = target_asset.item_code or args.target_item_code + company = target_asset.company or args.company or "_Test Company" + warehouse = args.warehouse or create_warehouse("_Test Warehouse", company=company) + target_warehouse = args.target_warehouse or warehouse + source_warehouse = args.source_warehouse or warehouse + + asset_capitalization = frappe.new_doc("Asset Capitalization") + asset_capitalization.update({ + "company": company, + "posting_date": args.posting_date or now.strftime('%Y-%m-%d'), + "posting_time": args.posting_time or now.strftime('%H:%M:%S.%f'), + "target_item_code": target_item_code, + "target_asset": target_asset.name, + "target_warehouse": target_warehouse, + "target_qty": flt(args.target_qty) or 1, + "target_batch_no": args.target_batch_no, + "target_serial_no": args.target_serial_no, + "finance_book": args.finance_book + }) + + if args.posting_date or args.posting_time: + asset_capitalization.set_posting_time = 1 + + if flt(args.stock_rate): + asset_capitalization.append("stock_items", { + "item_code": args.stock_item or "Capitalization Source Stock Item", + "warehouse": source_warehouse, + "stock_qty": flt(args.stock_qty) or 1, + "batch_no": args.stock_batch_no, + "serial_no": args.stock_serial_no, + }) + + if args.consumed_asset: + asset_capitalization.append("asset_items", { + "asset": args.consumed_asset, + }) + + if flt(args.service_rate): + asset_capitalization.append("service_items", { + "item_code": args.service_item or "Capitalization Source Service Item", + "expense_account": args.service_expense_account, + "qty": flt(args.service_qty) or 1, + "rate": flt(args.service_rate) + }) + + if args.submit: + create_stock_reconciliation(asset_capitalization, stock_rate=args.stock_rate) + + asset_capitalization.insert() + + if args.submit: + asset_capitalization.submit() + + return asset_capitalization + + +def create_stock_reconciliation(asset_capitalization, stock_rate=0): + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, + ) + if not asset_capitalization.get('stock_items'): + return + + return create_stock_reconciliation( + item_code=asset_capitalization.stock_items[0].item_code, + warehouse=asset_capitalization.stock_items[0].warehouse, + qty=flt(asset_capitalization.stock_items[0].stock_qty), + rate=flt(stock_rate), + company=asset_capitalization.company) + + +def create_depreciation_asset(**args): + args = frappe._dict(args) + + asset = frappe.new_doc("Asset") + asset.is_existing_asset = 1 + asset.calculate_depreciation = 1 + asset.asset_owner = "Company" + + asset.company = args.company or "_Test Company" + asset.item_code = args.item_code or "Macbook Pro" + asset.asset_name = args.asset_name or asset.item_code + asset.location = args.location or "Test Location" + + asset.purchase_date = args.purchase_date or '2020-01-01' + asset.available_for_use_date = args.available_for_use_date or asset.purchase_date + + asset.gross_purchase_amount = args.asset_value or 100000 + asset.purchase_receipt_amount = asset.gross_purchase_amount + + finance_book = asset.append('finance_books') + finance_book.depreciation_start_date = args.depreciation_start_date or '2020-12-31' + finance_book.depreciation_method = args.depreciation_method or 'Straight Line' + finance_book.total_number_of_depreciations = cint(args.total_number_of_depreciations) or 3 + finance_book.frequency_of_depreciation = cint(args.frequency_of_depreciation) or 12 + finance_book.expected_value_after_useful_life = flt(args.expected_value_after_useful_life) + + if args.submit: + asset.submit() + + frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") + post_depreciation_entries(date=finance_book.depreciation_start_date) + asset.load_from_db() + + return asset + + +def get_actual_gle_dict(name): + return dict(frappe.db.sql(""" + select account, sum(debit-credit) as diff + from `tabGL Entry` + where voucher_type = 'Asset Capitalization' and voucher_no = %s + group by account + having diff != 0 + """, name)) + + +def get_actual_sle_dict(name): + sles = frappe.db.sql(""" + select + item_code, warehouse, + sum(actual_qty) as actual_qty, + sum(stock_value_difference) as stock_value_difference + from `tabStock Ledger Entry` + where voucher_type = 'Asset Capitalization' and voucher_no = %s + group by item_code, warehouse + having actual_qty != 0 + """, name, as_dict=1) + + sle_dict = {} + for d in sles: + sle_dict[(d.item_code, d.warehouse)] = { + 'actual_qty': d.actual_qty, 'stock_value_difference': d.stock_value_difference + } + + return sle_dict From dc3c27fd1b6382c1f1e7a6cdbe4af3826859b247 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 4 Nov 2021 13:47:33 +0500 Subject: [PATCH 15/19] fix(Asset Capitalization): update code for changes in depreciation logic --- .../doctype/sales_invoice/sales_invoice.py | 86 +------------------ erpnext/assets/doctype/asset/asset.py | 14 +-- .../asset_capitalization.py | 2 +- .../test_asset_capitalization.py | 10 +-- erpnext/controllers/accounts_controller.py | 41 +++++---- 5 files changed, 37 insertions(+), 116 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 2b3850e513..7ed45ce26c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -27,7 +27,6 @@ from erpnext.assets.doctype.asset.depreciation import ( get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - make_depreciation_entry, ) from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data @@ -924,7 +923,7 @@ class SalesInvoice(SellingController): asset.db_set("disposal_date", None) 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) else: @@ -980,89 +979,6 @@ class SalesInvoice(SellingController): self.check_finance_books(item, 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 - 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 - @property def enable_discount_accounting(self): if not hasattr(self, "_enable_discount_accounting"): diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index cf62f496ea..7c05488db5 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -75,12 +75,12 @@ class Asset(AccountsController): if self.is_existing_asset and self.purchase_invoice: frappe.throw(_("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: self.value_after_depreciation = 0 self.set_depreciation_rate() - self.make_depreciation_schedule(date_of_sale) - self.set_accumulated_depreciation(date_of_sale, date_of_return) + self.make_depreciation_schedule(date_of_disposal) + self.set_accumulated_depreciation(date_of_disposal, date_of_return) else: self.finance_books = [] self.value_after_depreciation = (flt(self.gross_purchase_amount) - @@ -181,7 +181,7 @@ class Asset(AccountsController): d.rate_of_depreciation = flt(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('schedules'): self.schedules = [] @@ -227,14 +227,14 @@ class Asset(AccountsController): monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1) # if asset is being sold - if date_of_sale: + if date_of_disposal: from_date = self.get_from_date(d.finance_book) depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, - from_date, date_of_sale) + from_date, date_of_disposal) if depreciation_amount > 0: self.append("schedules", { - "schedule_date": date_of_sale, + "schedule_date": date_of_disposal, "depreciation_amount": depreciation_amount, "depreciation_method": d.depreciation_method, "finance_book": d.finance_book, diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 5a2398650b..7d08581cbe 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -375,8 +375,8 @@ class AssetCapitalization(StockController): else: if asset.calculate_depreciation: self.depreciate_asset(asset) + asset.reload() - 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) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 9bfc88b28c..5a342f7d2f 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -118,11 +118,11 @@ class TestAssetCapitalization(unittest.TestCase): depreciation_before_disposal_amount = 15_000 accumulated_depreciation = 45_000 - # to accomodate for depreciation on disposal calculation bugs TODO remove this when bug is fixed - consumed_asset_value_before_disposal = 60_082.19 - target_incoming_rate = 6008.219 - depreciation_before_disposal_amount = 9917.81 - accumulated_depreciation = 39_917.81 + # 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( diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9d19639020..6b681ffee5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -38,7 +38,7 @@ from erpnext.accounts.party import ( validate_party_frozen_disabled, ) from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year -from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries +from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.print_settings import ( set_print_templates_for_item_table, @@ -1516,17 +1516,16 @@ class AccountsController(TransactionBase): def depreciate_asset(self, asset): asset.flags.ignore_validate_update_after_submit = True - asset.prepare_depreciation_data(self.posting_date) + asset.prepare_depreciation_data(date_of_disposal=self.posting_date) asset.save() - post_depreciation_entries(self.posting_date, commit=False) + 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 - self.delete_depreciation_entry_made_after_disposal(asset) - asset.prepare_depreciation_data() + asset.prepare_depreciation_data(date_of_return=self.posting_date) self.modify_depreciation_schedule_for_asset_repairs(asset) asset.save() @@ -1544,10 +1543,10 @@ class AccountsController(TransactionBase): asset_repair.modify_depreciation_schedule() asset.prepare_depreciation_data() - def delete_depreciation_entry_made_after_disposal(self, asset): + 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_invoice = self.get_posting_date_of_disposal_entry() + posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry() row = -1 finance_book = asset.get('schedules')[0].get('finance_book') @@ -1558,19 +1557,19 @@ class AccountsController(TransactionBase): else: row += 1 - if schedule.schedule_date == posting_date_of_original_invoice: - if not self.disposal_was_made_on_original_schedule_date(asset, schedule, row, - posting_date_of_original_invoice) or getdate(schedule.schedule_date) > getdate(today()): + 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() - - for d in reverse_journal_entry.accounts: - d.reference_type = "Asset" - d.reference_name = asset.name - + frappe.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() - schedule.db_set('journal_entry', None) + 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: @@ -1579,16 +1578,22 @@ class AccountsController(TransactionBase): 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_original_disposal): + 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_original_disposal: + 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() def get_tax_rate(account_head): return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) From 85d1a237ce13d857ebbecd6a7e196c06d9aa3735 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 4 Nov 2021 14:15:47 +0500 Subject: [PATCH 16/19] fix(Asset Capitalization): Reverse depreciation on cancel --- .../assets/doctype/asset_capitalization/asset_capitalization.py | 1 + .../doctype/asset_capitalization/test_asset_capitalization.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 7d08581cbe..a8f2d79c27 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -371,6 +371,7 @@ class AssetCapitalization(StockController): self.set_consumed_asset_status(asset) if asset.calculate_depreciation: + self.reverse_depreciation_entry_made_after_disposal(asset) self.reset_depreciation_schedule(asset) else: if asset.calculate_depreciation: diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 5a342f7d2f..7046de6f83 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -174,6 +174,7 @@ class TestAssetCapitalization(unittest.TestCase): 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)) From cdb18000871931def61dda3f1b4a45b4c38d444b Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 9 Nov 2021 12:35:01 +0500 Subject: [PATCH 17/19] chore: remove unused import --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 86b7aaac87..e83873fe6c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -9,7 +9,6 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.utils import ( add_days, - add_months, cint, cstr, flt, From 06aead0470d0c1659e7c15db642cb9a10f2999f6 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 10 Nov 2021 13:45:40 +0500 Subject: [PATCH 18/19] chore: isort --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e83873fe6c..5297cc9502 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -7,16 +7,7 @@ from frappe import _, msgprint, throw from frappe.contacts.doctype.address.address import get_address_display from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values -from frappe.utils import ( - add_days, - cint, - cstr, - flt, - formatdate, - get_link_to_form, - getdate, - nowdate, -) +from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate import erpnext from erpnext.accounts.deferred_revenue import validate_service_stop_date From 58d430fe3ee62e93ad8d16a08bb42156a25b7d41 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 13 Sep 2022 14:56:21 +0530 Subject: [PATCH 19/19] feat: Asset Capitalization - manual selection of entry type - GLE cleanup with smaller functions - GLE considering periodical inventory - test cases --- erpnext/assets/doctype/asset/test_asset.py | 10 + .../asset_capitalization.js | 7 +- .../asset_capitalization.json | 44 +- .../asset_capitalization.py | 498 ++++++++++-------- .../test_asset_capitalization.py | 339 ++++++++---- .../doctype/asset_repair/test_asset_repair.py | 12 - .../test_stock_reconciliation.py | 7 +- 7 files changed, 583 insertions(+), 334 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 132840e38c..e7af9bd5bc 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1425,6 +1425,16 @@ def create_asset_category(): "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() diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index d135e60ae7..9c7f70b0e5 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -27,7 +27,11 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s me.setup_warehouse_query(); me.frm.set_query("target_item_code", function() { - return erpnext.queries.item(); + 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() { @@ -410,5 +414,4 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } }; -//$.extend(cur_frm.cscript, new erpnext.assets.AssetCapitalization({frm: cur_frm})); cur_frm.cscript = new erpnext.assets.AssetCapitalization({frm: cur_frm}); diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index d7e6b54716..d1be5752d6 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -8,29 +8,28 @@ "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", - "entry_type", - "finance_book", - "naming_series", "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", - "target_item_details_section", - "target_asset", - "target_asset_name", - "target_warehouse", - "target_batch_no", - "target_serial_no", - "column_break_5", - "target_qty", - "target_stock_uom", "section_break_16", "stock_items", "stock_items_total", @@ -86,16 +85,17 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:!doc.target_item_code || doc.target_is_fixed_asset", + "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": "target_asset", + "depends_on": "eval:doc.entry_type=='Capitalization'", "fetch_from": "target_asset.asset_name", "fieldname": "target_asset_name", "fieldtype": "Data", @@ -170,15 +170,11 @@ "options": "Asset Capitalization Stock Item" }, { - "fieldname": "target_item_details_section", - "fieldtype": "Section Break", - "label": "Target Item Details" - }, - { - "depends_on": "eval:!doc.target_is_fixed_asset", + "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" }, { @@ -240,13 +236,14 @@ "options": "Asset Capitalization Asset Item" }, { + "default": "Capitalization", "fieldname": "entry_type", "fieldtype": "Select", "in_list_view": 1, "in_standard_filter": 1, "label": "Entry Type", - "options": "\nCapitalization\nDecapitalization", - "read_only": 1 + "options": "Capitalization\nDecapitalization", + "reqd": 1 }, { "fieldname": "stock_items_total", @@ -337,7 +334,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-09-15 15:41:27.917458", + "modified": "2022-09-12 15:09:40.771332", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization", @@ -377,6 +374,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title", "track_changes": 1, "track_seen": 1 diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index a8f2d79c27..2e6f0ad7b0 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -10,9 +10,9 @@ 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_gl_entries_on_asset_regain, get_value_after_depreciation_on_disposal_date, ) from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account @@ -32,16 +32,26 @@ from erpnext.stock.get_item_details import ( 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'] +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.set_entry_type() self.validate_target_item() self.validate_target_asset() self.validate_consumed_stock_item() @@ -58,14 +68,13 @@ class AssetCapitalization(StockController): 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.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.update_stock_ledger() self.make_gl_entries() - - def set_entry_type(self): - self.entry_type = "Capitalization" if self.target_is_fixed_asset else "Decapitalization" + self.update_target_asset() def set_title(self): self.title = self.target_asset_name or self.target_item_name or self.target_item_code @@ -90,7 +99,7 @@ class AssetCapitalization(StockController): args.update(d.as_dict()) args.doctype = self.doctype args.name = self.name - consumed_stock_item_details = get_consumed_stock_item_details(args, get_valuation_rate=False) + 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) @@ -100,8 +109,8 @@ class AssetCapitalization(StockController): 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, get_asset_value=False) + 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) @@ -120,8 +129,14 @@ class AssetCapitalization(StockController): 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)) + 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 @@ -144,14 +159,13 @@ class AssetCapitalization(StockController): self.validate_item(target_item) def validate_target_asset(self): - if self.target_is_fixed_asset and not self.target_asset: - frappe.throw(_("Target Asset is mandatory for Capitalization")) - 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)) + frappe.throw( + _("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code) + ) self.validate_asset(target_asset) @@ -172,8 +186,11 @@ class AssetCapitalization(StockController): 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)) + 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) @@ -198,18 +215,21 @@ class AssetCapitalization(StockController): 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'): + 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'): + 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) + 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"): @@ -225,7 +245,7 @@ class AssetCapitalization(StockController): @frappe.whitelist() def set_warehouse_details(self): - for d in self.get('stock_items'): + 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) @@ -233,27 +253,30 @@ class AssetCapitalization(StockController): @frappe.whitelist() def set_asset_values(self): - for d in self.get('asset_items'): + for d in self.get("asset_items"): if d.asset: - finance_book = d.get('finance_book') or self.get('finance_book') + 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) + 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')), - }) + 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 @@ -261,45 +284,51 @@ class AssetCapitalization(StockController): 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')) + 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')) + 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')) + 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.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.total_value = flt(self.total_value, self.precision("total_value")) - self.target_qty = flt(self.target_qty, self.precision('target_qty')) + 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), - }) + sle = self.get_sl_entries( + d, + { + "actual_qty": -flt(d.stock_qty), + }, + ) sl_entries.append(sle) - if not frappe.db.get_value("Item", self.target_item_code, "is_fixed_asset", cache=1): - 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) - }) + 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 @@ -312,139 +341,183 @@ class AssetCapitalization(StockController): 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 not gl_entries: - gl_entries = self.get_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): + def get_gl_entries( + self, warehouse_account=None, default_expense_account=None, default_cost_center=None + ): # Stock GL Entries gl_entries = [] - if not warehouse_account: - warehouse_account = get_warehouse_account_map(self.company) + 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() - sle_map = self.get_stock_ledger_details() - - if self.target_is_fixed_asset: - target_account = self.target_fixed_asset_account - else: - target_account = warehouse_account[self.target_warehouse]["account"] + 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 - total_consumed_stock_value = 0 for item_row in self.stock_items: - sle_list = sle_map.get(item_row.name) + 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) - total_consumed_stock_value += -1 * sle.stock_value_difference - account = warehouse_account[sle.warehouse]["account"] + 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, + ) + ) - 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, - }, 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 self.docstatus == 2: - fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset, - item.asset_value, item.get('finance_book') or self.get('finance_book')) - asset.db_set("disposal_date", None) + 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) - else: - 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)) - - # Service Expenses - total_service_expenses = 0 - for item_row in self.service_items: - expense_amount = flt(item_row.amount, precision) - total_service_expenses += expense_amount - 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)) - - target_against = ", ".join(target_against) - total_target_stock_value = 0 - total_target_asset_value = 0 - - if self.target_is_fixed_asset: - # Target Asset Item - total_target_asset_value = flt(self.total_value, precision) - gl_entries.append(self.get_gl_dict({ - "account": self.target_fixed_asset_account, - "against": target_against, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": total_target_asset_value, - "cost_center": self.get('cost_center') - }, item=self)) - - if self.docstatus == 1: - 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() - else: - # Target Stock Item - sle_list = sle_map.get(self.name) - for sle in sle_list: - stock_value_difference = flt(sle.stock_value_difference, precision) - total_target_stock_value += sle.stock_value_difference - account = warehouse_account[sle.warehouse]["account"] - - gl_entries.append(self.get_gl_dict({ - "account": account, - "against": target_against, - "cost_center": self.cost_center, - "project": self.get('project'), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "debit": stock_value_difference, - }, warehouse_account[sle.warehouse]["account_currency"], item=self)) - - return gl_entries def get_asset(self, item): asset = frappe.get_doc("Asset", item.asset) @@ -489,16 +562,12 @@ def get_target_item_details(item_code=None, company=None): 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) - - # Set Entry Type - if not item_code: - out.entry_type = "" - elif out.target_is_fixed_asset: - out.entry_type = "Capitalization" - else: - out.entry_type = "Decapitalization" + out.cost_center = get_default_cost_center( + frappe._dict({"item_code": item.name, "company": company}), + item_defaults, + item_group_defaults, + brand_defaults, + ) return out @@ -510,7 +579,7 @@ def get_target_asset_details(asset=None, company=None): # Get Asset Details asset_details = frappe._dict() if asset: - asset_details = frappe.db.get_value("Asset", asset, ['asset_name', 'item_code'], as_dict=1) + 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)) @@ -521,8 +590,9 @@ def get_target_asset_details(asset=None, company=None): 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) + 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 @@ -530,7 +600,7 @@ def get_target_asset_details(asset=None, company=None): @frappe.whitelist() -def get_consumed_stock_item_details(args, get_valuation_rate=True): +def get_consumed_stock_item_details(args): if isinstance(args, string_types): args = json.loads(args) @@ -554,24 +624,29 @@ def get_consumed_stock_item_details(args, get_valuation_rate=True): 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) + out.cost_center = get_default_cost_center( + args, item_defaults, item_group_defaults, brand_defaults + ) - if get_valuation_rate: - 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), + 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, - }) - out.update(get_warehouse_details(incoming_rate_args)) - else: - out.valuation_rate = 0 - out.actual_qty = 0 + "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 @@ -587,13 +662,13 @@ def get_warehouse_details(args): 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) + "valuation_rate": get_incoming_rate(args, raise_error_if_no_rate=False), } return out @frappe.whitelist() -def get_consumed_asset_details(args, get_asset_value=True): +def get_consumed_asset_details(args): if isinstance(args, string_types): args = json.loads(args) @@ -602,7 +677,9 @@ def get_consumed_asset_details(args, get_asset_value=True): 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) + 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)) @@ -610,19 +687,22 @@ def get_consumed_asset_details(args, get_asset_value=True): out.asset_name = asset_details.asset_name out.item_name = asset_details.item_name - if get_asset_value: - 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 + 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) + 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 @@ -632,7 +712,9 @@ def get_consumed_asset_details(args, get_asset_value=True): 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) + out.cost_center = get_default_cost_center( + args, item_defaults, item_group_defaults, brand_defaults + ) return out @@ -657,7 +739,11 @@ def get_service_item_details(args): 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) + 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 diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 7046de6f83..86861f0b16 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -22,9 +22,12 @@ class TestAssetCapitalization(unittest.TestCase): create_asset_capitalization_data() frappe.db.sql("delete from `tabTax Rule`") - def test_capitalization(self): + def test_capitalization_with_perpetual_inventory(self): + company = "_Test Company with perpetual inventory" + set_depreciation_settings_in_company(company=company) + # Variables - consumed_asset_value = 100_000 + consumed_asset_value = 100000 stock_rate = 1000 stock_qty = 2 @@ -34,23 +37,39 @@ class TestAssetCapitalization(unittest.TestCase): service_qty = 2 service_amount = 1000 - total_amount = 103_000 + total_amount = 103000 # Create assets - target_asset = create_asset(asset_name='Asset Capitalization Target Asset', submit=1) - consumed_asset = create_asset(asset_name='Asset Capitalization Consumable Asset', asset_value=consumed_asset_value, - submit=1) + 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(target_asset=target_asset.name, - stock_qty=stock_qty, stock_rate=stock_rate, + 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', - submit=1) + 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.entry_type, "Capitalization") self.assertEqual(asset_capitalization.target_qty, 1) self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate) @@ -72,13 +91,13 @@ class TestAssetCapitalization(unittest.TestCase): self.assertEqual(target_asset.purchase_receipt_amount, total_amount) # Test Consumed Asset values - self.assertEqual(consumed_asset.db_get('status'), 'Capitalized') + self.assertEqual(consumed_asset.db_get("status"), "Capitalized") # Test General Ledger Entries expected_gle = { - '_Test Fixed Asset - _TC': 3000, - 'Expenses Included In Asset Valuation - _TC': -1000, - 'Stock In Hand - _TC' : -2000 + "_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) @@ -86,25 +105,121 @@ class TestAssetCapitalization(unittest.TestCase): # Test Stock Ledger Entries expected_sle = { - ('Capitalization Source Stock Item', '_Test Warehouse - _TC'): { - 'actual_qty': -stock_qty, 'stock_value_difference': -stock_amount + ("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.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' + 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 @@ -126,29 +241,38 @@ class TestAssetCapitalization(unittest.TestCase): # Create assets consumed_asset = create_depreciation_asset( - asset_name='Asset Capitalization Consumable 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', + depreciation_method="Straight Line", total_number_of_depreciations=total_number_of_depreciations, frequency_of_depreciation=12, expected_value_after_useful_life=expected_value_after_useful_life, - submit=1) + 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, - submit=1) + company="_Test Company with perpetual inventory", + submit=1, + ) # Test Asset Capitalization values - self.assertEqual(asset_capitalization.entry_type, 'Decapitalization') + 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[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) @@ -156,38 +280,45 @@ class TestAssetCapitalization(unittest.TestCase): # Test Consumed Asset values consumed_asset.reload() - self.assertEqual(consumed_asset.status, 'Decapitalized') + 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) + consumed_depreciation_schedule = [ + d for d in consumed_asset.schedules if getdate(d.schedule_date) == getdate(capitalization_date) + ] + self.assertTrue( + consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry + ) + self.assertEqual( + consumed_depreciation_schedule[0].depreciation_amount, depreciation_before_disposal_amount + ) # Test General Ledger Entries expected_gle = { - 'Stock In Hand - _TC': consumed_asset_value_before_disposal, - '_Test Accumulated Depreciations - _TC': accumulated_depreciation, - '_Test Fixed Asset - _TC': -consumed_asset_purchase_value, + "_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.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) + 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): @@ -204,43 +335,55 @@ def create_asset_capitalization(**args): source_warehouse = args.source_warehouse or warehouse asset_capitalization = frappe.new_doc("Asset Capitalization") - asset_capitalization.update({ - "company": company, - "posting_date": args.posting_date or now.strftime('%Y-%m-%d'), - "posting_time": args.posting_time or now.strftime('%H:%M:%S.%f'), - "target_item_code": target_item_code, - "target_asset": target_asset.name, - "target_warehouse": target_warehouse, - "target_qty": flt(args.target_qty) or 1, - "target_batch_no": args.target_batch_no, - "target_serial_no": args.target_serial_no, - "finance_book": args.finance_book - }) + 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, - }) + 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, - }) + 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) - }) + 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) @@ -255,17 +398,23 @@ def create_asset_capitalization(**args): 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'): + + if not asset_capitalization.get("stock_items"): return - return create_stock_reconciliation( - item_code=asset_capitalization.stock_items[0].item_code, - warehouse=asset_capitalization.stock_items[0].warehouse, - qty=flt(asset_capitalization.stock_items[0].stock_qty), - rate=flt(stock_rate), - company=asset_capitalization.company) + 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): @@ -281,15 +430,15 @@ def create_depreciation_asset(**args): 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.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 = 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) @@ -305,17 +454,23 @@ def create_depreciation_asset(**args): def get_actual_gle_dict(name): - return dict(frappe.db.sql(""" + 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)) + """, + name, + ) + ) def get_actual_sle_dict(name): - sles = frappe.db.sql(""" + sles = frappe.db.sql( + """ select item_code, warehouse, sum(actual_qty) as actual_qty, @@ -324,12 +479,16 @@ def get_actual_sle_dict(name): where voucher_type = 'Asset Capitalization' and voucher_no = %s group by item_code, warehouse having actual_qty != 0 - """, name, as_dict=1) + """, + 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 + "actual_qty": d.actual_qty, + "stock_value_difference": d.stock_value_difference, } return sle_dict diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 6e06f52ac6..2786349f7b 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -129,18 +129,6 @@ class TestAssetRepair(unittest.TestCase): def test_gl_entries_with_perpetual_inventory(self): 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( capitalize_repair_cost=1, stock_consumption=1, diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 191c03f5f1..4e76ae781f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -727,7 +727,12 @@ def create_stock_reconciliation(**args): sr.set_posting_time = 1 sr.company = args.company or "_Test Company" 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 = ( args.cost_center