diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 095617dbcf1..2eaa33767c9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -479,6 +479,12 @@ cur_frm.set_query("expense_account", "items", function(doc) { } }); +cur_frm.set_query("wip_composite_asset", "items", function() { + return { + filters: {'is_composite_asset': 1, 'docstatus': 0 } + } +}); + cur_frm.cscript.expense_account = function(doc, cdt, cdn){ var d = locals[cdt][cdn]; if(d.idx == 1 && d.expense_account){ diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 3690142aac9..424e942990f 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -77,6 +77,7 @@ "manufacturer_part_no", "accounting", "expense_account", + "wip_composite_asset", "col_break5", "is_fixed_asset", "asset_location", @@ -903,12 +904,18 @@ "no_copy": 1, "options": "Serial and Batch Bundle", "print_hide": 1 + }, + { + "fieldname": "wip_composite_asset", + "fieldtype": "Link", + "label": "WIP Composite Asset", + "options": "Asset" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-26 12:54:53.178156", + "modified": "2023-10-03 21:01:01.824892", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index ba7940c5e27..5395f15e7a1 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -148,6 +148,15 @@ frappe.ui.form.on('Asset', { if (frm.doc.docstatus == 0) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); + + if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) { + $('.primary-action').prop('hidden', true); + $('.form-message').text('Capitalize this asset to confirm'); + + frm.add_custom_button(__("Capitalize Asset"), function() { + frm.trigger("create_asset_capitalization"); + }); + } } }, @@ -169,7 +178,7 @@ frappe.ui.form.on('Asset', { frm.set_df_property('purchase_invoice', 'read_only', 1); frm.set_df_property('purchase_receipt', 'read_only', 1); } - else if (frm.doc.is_existing_asset) { + else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) { frm.toggle_reqd('purchase_receipt', 0); frm.toggle_reqd('purchase_invoice', 0); } @@ -353,7 +362,17 @@ frappe.ui.form.on('Asset', { is_existing_asset: function(frm) { frm.trigger("toggle_reference_doc"); - // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); + }, + + is_composite_asset: function(frm) { + if(frm.doc.is_composite_asset) { + frm.set_value('gross_purchase_amount', 0); + frm.set_df_property('gross_purchase_amount', 'read_only', 1); + } else { + frm.set_df_property('gross_purchase_amount', 'read_only', 0); + } + + frm.trigger("toggle_reference_doc"); }, make_sales_invoice: function(frm) { @@ -403,6 +422,19 @@ frappe.ui.form.on('Asset', { }); }, + create_asset_capitalization: function(frm) { + frappe.call({ + args: { + "asset": frm.doc.name, + }, + method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization", + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }, + split_asset: function(frm) { const title = __('Split Asset'); @@ -466,9 +498,11 @@ frappe.ui.form.on('Asset', { }, gross_purchase_amount: function(frm) { - frm.doc.finance_books.forEach(d => { - frm.events.set_depreciation_rate(frm, d); - }) + if (frm.doc.finance_books) { + frm.doc.finance_books.forEach(d => { + frm.events.set_depreciation_rate(frm, d); + }) + } }, purchase_receipt: (frm) => { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index befb5248d56..c7d08e20413 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -14,6 +14,7 @@ "asset_owner", "asset_owner_company", "is_existing_asset", + "is_composite_asset", "supplier", "customer", "image", @@ -72,7 +73,8 @@ "purchase_receipt_amount", "default_finance_book", "depr_entry_posting_status", - "amended_from" + "amended_from", + "capitalized_in" ], "fields": [ { @@ -199,7 +201,7 @@ "fieldtype": "Date", "label": "Purchase Date", "read_only": 1, - "read_only_depends_on": "eval:!doc.is_existing_asset", + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset", "reqd": 1 }, { @@ -237,10 +239,12 @@ "default": "0", "fieldname": "calculate_depreciation", "fieldtype": "Check", - "label": "Calculate Depreciation" + "label": "Calculate Depreciation", + "read_only_depends_on": "eval:doc.is_composite_asset && !doc.gross_purchase_amount" }, { "default": "0", + "depends_on": "eval:!doc.is_composite_asset", "fieldname": "is_existing_asset", "fieldtype": "Check", "label": "Is Existing Asset" @@ -478,7 +482,7 @@ "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", - "read_only_depends_on": "eval:!doc.is_existing_asset" + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset" }, { "fieldname": "depr_entry_posting_status", @@ -507,6 +511,21 @@ "fieldname": "is_fully_depreciated", "fieldtype": "Check", "label": "Is Fully Depreciated" + }, + { + "default": "0", + "depends_on": "eval:!doc.is_existing_asset", + "fieldname": "is_composite_asset", + "fieldtype": "Check", + "label": "Is Composite Asset" + }, + { + "fieldname": "capitalized_in", + "fieldtype": "Link", + "hidden": 1, + "label": "Capitalized In", + "options": "Asset Capitalization", + "read_only": 1 } ], "idx": 72, @@ -545,7 +564,7 @@ "table_fieldname": "accounts" } ], - "modified": "2023-07-28 20:12:44.819616", + "modified": "2023-10-03 23:28:26.732269", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 2e69fe5d5c0..9d356349330 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -228,7 +228,7 @@ class Asset(AccountsController): if not self.asset_category: self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") - if not flt(self.gross_purchase_amount): + if not flt(self.gross_purchase_amount) and not self.is_composite_asset: frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): @@ -768,6 +768,15 @@ def create_asset_repair(asset, asset_name): return asset_repair +@frappe.whitelist() +def create_asset_capitalization(asset): + asset_capitalization = frappe.new_doc("Asset Capitalization") + asset_capitalization.update( + {"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"} + ) + return asset_capitalization + + @frappe.whitelist() def create_asset_value_adjustment(asset, asset_category, company): asset_value_adjustment = frappe.new_doc("Asset Value Adjustment") diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 39fcb21cdb4..88ef69cddc3 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1744,6 +1744,7 @@ def create_asset(**args): "location": args.location or "Test Location", "asset_owner": args.asset_owner or "Company", "is_existing_asset": args.is_existing_asset or 1, + "is_composite_asset": args.is_composite_asset or 0, "asset_quantity": args.get("asset_quantity") or 1, "depr_entry_posting_status": args.depr_entry_posting_status or "", } diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 6d55d7772b2..be78d9ebdc2 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -16,9 +16,15 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s refresh() { this.show_general_ledger(); + if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) { this.show_stock_ledger(); } + + if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") { + this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset); + this.get_target_asset_details(); + } } setup_queries() { @@ -35,18 +41,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s }); me.frm.set_query("target_asset", function() { - var filters = {}; - - if (me.frm.doc.target_item_code) { - filters['item_code'] = me.frm.doc.target_item_code; - } - - filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]]; - filters['docstatus'] = 1; - return { - filters: filters - }; + filters: {'is_composite_asset': 1, 'docstatus': 0 } + } }); me.frm.set_query("asset", "asset_items", function() { @@ -128,6 +125,39 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s return this.get_target_item_details(); } + target_asset() { + if (this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") { + this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset); + this.get_target_asset_details(); + } + } + + set_consumed_stock_items_tagged_to_wip_composite_asset(asset) { + var me = this; + + if (asset) { + return me.frm.call({ + method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset", + args: { + asset: asset, + }, + callback: function (r) { + if (!r.exc && r.message) { + me.frm.clear_table("stock_items"); + + for (let item of r.message) { + me.frm.add_child("stock_items", item); + } + + refresh_field("stock_items"); + + me.calculate_totals(); + } + } + }); + } + } + item_code(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); if (cdt === "Asset Capitalization Stock Item") { @@ -242,6 +272,26 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } } + get_target_asset_details() { + var me = this; + + if (me.frm.doc.target_asset) { + return me.frm.call({ + method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details", + child: me.frm.doc, + args: { + asset: me.frm.doc.target_asset, + company: me.frm.doc.company, + }, + callback: function (r) { + if (!r.exc) { + me.frm.refresh_fields(); + } + } + }); + } + } + get_consumed_stock_item_details(row) { var me = this; diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index 04b0c4e5132..9ddc44212f6 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -8,24 +8,25 @@ "engine": "InnoDB", "field_order": [ "title", + "company", "naming_series", "entry_type", - "target_item_code", - "target_asset", "target_item_name", "target_is_fixed_asset", "target_has_batch_no", "target_has_serial_no", "column_break_9", - "target_asset_name", + "capitalization_method", + "target_item_code", "target_asset_location", + "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", @@ -57,12 +58,13 @@ "label": "Title" }, { + "depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')", "fieldname": "target_item_code", "fieldtype": "Link", "in_standard_filter": 1, "label": "Target Item Code", - "options": "Item", - "reqd": 1 + "mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'", + "options": "Item" }, { "depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code", @@ -86,16 +88,18 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')", "fieldname": "target_asset", "fieldtype": "Link", "in_standard_filter": 1, "label": "Target Asset", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'", "no_copy": 1, "options": "Asset", - "read_only": 1 + "read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')" }, { - "depends_on": "eval:doc.entry_type=='Capitalization'", + "depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')", "fetch_from": "target_asset.asset_name", "fieldname": "target_asset_name", "fieldtype": "Data", @@ -186,12 +190,14 @@ }, { "default": "1", + "depends_on": "eval:doc.entry_type=='Decapitalization'", "fieldname": "target_qty", "fieldtype": "Float", "label": "Target Qty", "read_only_depends_on": "eval:doc.entry_type=='Capitalization'" }, { + "depends_on": "eval:doc.entry_type=='Decapitalization'", "fetch_from": "target_item_code.stock_uom", "fieldname": "target_stock_uom", "fieldtype": "Link", @@ -331,18 +337,26 @@ "read_only": 1 }, { - "depends_on": "eval:doc.entry_type=='Capitalization'", + "depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'", "fieldname": "target_asset_location", "fieldtype": "Link", "label": "Target Asset Location", - "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'", "options": "Location" + }, + { + "depends_on": "eval:doc.entry_type=='Capitalization'", + "fieldname": "capitalization_method", + "fieldtype": "Select", + "label": "Capitalization Method", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", + "options": "\nCreate a new composite asset\nChoose a WIP composite asset" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-22 14:17:07.995120", + "modified": "2023-10-03 22:55:59.461456", "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 662e4b983b1..0d6f6b4da1f 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -53,6 +53,7 @@ class AssetCapitalization(StockController): self.validate_posting_time() self.set_missing_values(for_validate=True) self.validate_target_item() + self.validate_target_asset() self.validate_consumed_stock_item() self.validate_consumed_asset_item() self.validate_service_item() @@ -67,12 +68,12 @@ class AssetCapitalization(StockController): def before_submit(self): self.validate_source_mandatory() - if self.entry_type == "Capitalization": - self.create_target_asset() + self.create_target_asset() def on_submit(self): self.update_stock_ledger() self.make_gl_entries() + self.update_target_asset() def on_cancel(self): self.ignore_linked_doctypes = ( @@ -94,6 +95,11 @@ class AssetCapitalization(StockController): if self.meta.has_field(k) and (not self.get(k) or k in force_fields): self.set(k, v) + target_asset_details = get_target_asset_details(self.target_asset, self.company) + for k, v in target_asset_details.items(): + if self.meta.has_field(k) and (not self.get(k) or k in force_fields): + self.set(k, v) + for d in self.stock_items: args = self.as_dict() args.update(d.as_dict()) @@ -155,6 +161,33 @@ class AssetCapitalization(StockController): self.validate_item(target_item) + def validate_target_asset(self): + if self.target_asset: + target_asset = self.get_asset_for_validation(self.target_asset) + + if not target_asset.is_composite_asset: + frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name)) + + 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) + ) + + if target_asset.status in ("Scrapped", "Sold", "Capitalized", "Decapitalized"): + frappe.throw( + _("Target Asset {0} cannot be {1}").format(target_asset.name, target_asset.status) + ) + + if target_asset.docstatus == 1: + frappe.throw(_("Target Asset {0} cannot be submitted").format(target_asset.name)) + elif target_asset.docstatus == 2: + frappe.throw(_("Target Asset {0} cannot be cancelled").format(target_asset.name)) + + if target_asset.company != self.company: + frappe.throw( + _("Target Asset {0} does not belong to company {1}").format(target_asset.name, self.company) + ) + def validate_consumed_stock_item(self): for d in self.stock_items: if d.item_code: @@ -179,7 +212,23 @@ class AssetCapitalization(StockController): ) asset = self.get_asset_for_validation(d.asset) - self.validate_asset(asset) + + if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"): + frappe.throw( + _("Row #{0}: Consumed Asset {1} cannot be {2}").format(d.idx, asset.name, asset.status) + ) + + if asset.docstatus == 0: + frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be Draft").format(d.idx, asset.name)) + elif asset.docstatus == 2: + frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be cancelled").format(d.idx, asset.name)) + + if asset.company != self.company: + frappe.throw( + _("Row #{0}: Consumed Asset {1} does not belong to company {2}").format( + d.idx, asset.name, self.company + ) + ) def validate_service_item(self): for d in self.service_items: @@ -214,21 +263,12 @@ class AssetCapitalization(StockController): def get_asset_for_validation(self, asset): return frappe.db.get_value( - "Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1 + "Asset", + asset, + ["name", "item_code", "company", "status", "docstatus", "is_composite_asset"], + as_dict=1, ) - def validate_asset(self, asset): - if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"): - frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status)) - - if asset.docstatus == 0: - frappe.throw(_("Asset {0} is Draft").format(asset.name)) - if asset.docstatus == 2: - frappe.throw(_("Asset {0} is cancelled").format(asset.name)) - - if asset.company != self.company: - frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company)) - @frappe.whitelist() def set_warehouse_details(self): for d in self.get("stock_items"): @@ -495,16 +535,25 @@ class AssetCapitalization(StockController): ) def create_target_asset(self): + if ( + self.entry_type != "Capitalization" + or self.capitalization_method != "Create a new composite asset" + ): + return + total_target_asset_value = flt(self.total_value, self.precision("total_value")) + asset_doc = frappe.new_doc("Asset") asset_doc.company = self.company asset_doc.item_code = self.target_item_code - asset_doc.is_existing_asset = 1 + asset_doc.is_composite_asset = 1 asset_doc.location = self.target_asset_location asset_doc.available_for_use_date = self.posting_date 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.purchase_receipt_amount = total_target_asset_value + asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.flags.asset_created_via_asset_capitalization = True asset_doc.insert() @@ -528,6 +577,28 @@ class AssetCapitalization(StockController): ).format(get_link_to_form("Asset", asset_doc.name)) ) + def update_target_asset(self): + if ( + self.entry_type != "Capitalization" + or self.capitalization_method != "Choose a WIP composite asset" + ): + return + + total_target_asset_value = flt(self.total_value, self.precision("total_value")) + + asset_doc = frappe.get_doc("Asset", self.target_asset) + asset_doc.gross_purchase_amount = total_target_asset_value + asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.capitalized_in = self.name + asset_doc.flags.ignore_validate = True + asset_doc.save() + + frappe.msgprint( + _( + "Asset {0} has been updated. Please set the depreciation details if any and submit it." + ).format(get_link_to_form("Asset", asset_doc.name)) + ) + def restore_consumed_asset_items(self): for item in self.asset_items: asset = frappe.get_doc("Asset", item.asset) @@ -612,6 +683,33 @@ def get_target_item_details(item_code=None, company=None): return out +@frappe.whitelist() +def get_target_asset_details(asset=None, company=None): + out = frappe._dict() + + # Get Asset Details + asset_details = frappe._dict() + if asset: + asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1) + if not asset_details: + frappe.throw(_("Asset {0} does not exist").format(asset)) + + # Re-set item code from Asset + out.target_item_code = asset_details.item_code + + # Set Asset Details + out.asset_name = asset_details.asset_name + + if asset_details.item_code: + out.target_fixed_asset_account = get_asset_category_account( + "fixed_asset_account", item=asset_details.item_code, company=company + ) + else: + out.target_fixed_asset_account = None + + return out + + @frappe.whitelist() def get_consumed_stock_item_details(args): if isinstance(args, str): @@ -760,3 +858,30 @@ def get_service_item_details(args): ) return out + + +@frappe.whitelist() +def get_items_tagged_to_wip_composite_asset(asset): + fields = [ + "item_code", + "item_name", + "batch_no", + "serial_no", + "stock_qty", + "stock_uom", + "warehouse", + "cost_center", + "qty", + "valuation_rate", + "amount", + ] + + pi_items = frappe.get_all( + "Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields + ) + + pr_items = frappe.get_all( + "Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields + ) + + return pi_items + pr_items diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 6e0a6856f56..4a78595cb59 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -58,6 +58,7 @@ class TestAssetCapitalization(unittest.TestCase): # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( entry_type="Capitalization", + capitalization_method="Create a new composite asset", target_item_code="Macbook Pro", target_asset_location="Test Location", stock_qty=stock_qty, @@ -147,6 +148,7 @@ class TestAssetCapitalization(unittest.TestCase): # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( entry_type="Capitalization", + capitalization_method="Create a new composite asset", target_item_code="Macbook Pro", target_asset_location="Test Location", stock_qty=stock_qty, @@ -211,6 +213,77 @@ class TestAssetCapitalization(unittest.TestCase): self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + def test_capitalization_with_wip_composite_asset(self): + company = "_Test Company with perpetual inventory" + set_depreciation_settings_in_company(company=company) + + stock_rate = 1000 + stock_qty = 2 + stock_amount = 2000 + + total_amount = 2000 + + wip_composite_asset = create_asset( + asset_name="Asset Capitalization WIP Composite Asset", + is_composite_asset=1, + warehouse="Stores - TCP1", + company=company, + ) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization( + entry_type="Capitalization", + capitalization_method="Choose a WIP composite asset", + target_asset=wip_composite_asset, + target_asset_location="Test Location", + stock_qty=stock_qty, + stock_rate=stock_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.capitalization_method, "Choose a WIP composite asset") + 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.total_value, total_amount) + self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) + + # Test Target Asset values + target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) + self.assertEqual(target_asset.gross_purchase_amount, total_amount) + self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + + # Test General Ledger Entries + expected_gle = { + "_Test Fixed Asset - TCP1": 2000, + "_Test Warehouse - TCP1": -2000, + } + actual_gle = get_actual_gle_dict(asset_capitalization.name) + + self.assertEqual(actual_gle, expected_gle) + + # Test Stock Ledger Entries + expected_sle = { + ("Capitalization Source Stock Item", "_Test Warehouse - TCP1"): { + "actual_qty": -stock_qty, + "stock_value_difference": -stock_amount, + } + } + actual_sle = get_actual_sle_dict(asset_capitalization.name) + self.assertEqual(actual_sle, expected_sle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.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" @@ -347,6 +420,7 @@ def create_asset_capitalization(**args): asset_capitalization.update( { "entry_type": args.entry_type or "Capitalization", + "capitalization_method": args.capitalization_method or None, "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"), diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 2bb479b8bcd..6552cd7fce3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -37,6 +37,12 @@ frappe.ui.form.on("Purchase Receipt", { } }); + frm.set_query("wip_composite_asset", "items", function() { + return { + filters: {'is_composite_asset': 1, 'docstatus': 0 } + } + }); + frm.set_query("taxes_and_charges", function() { return { filters: {'company': frm.doc.company } diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 5eb3656c006..d7419dcc378 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -117,6 +117,7 @@ "accounting_details_section", "expense_account", "item_tax_rate", + "wip_composite_asset", "column_break_102", "provisional_expense_account", "accounting_dimensions_section", @@ -1056,12 +1057,18 @@ "fieldname": "add_serial_batch_bundle", "fieldtype": "Button", "label": "Add Serial / Batch No" + }, + { + "fieldname": "wip_composite_asset", + "fieldtype": "Link", + "label": "WIP Composite Asset", + "options": "Asset" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-11 16:16:16.504549", + "modified": "2023-10-03 21:11:50.547261", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item",