feat: composite WIP asset (copy #37352) (#37353)
* feat: wip composite asset
(cherry picked from commit 4907e7acd4ff549f34f6c9d0144af83db0ad9cc1)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
# erpnext/assets/doctype/asset/asset.json
# erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
* chore: resolving conflicts
---------
Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 095617d..2eaa337 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("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 3690142..424e942 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 ba7940c..5395f15 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -148,6 +148,15 @@
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 @@
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 @@
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 @@
});
},
+ 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 @@
},
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 befb524..c7d08e2 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 2e69fe5..9d35634 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -228,7 +228,7 @@
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):
@@ -769,6 +769,15 @@
@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")
asset_value_adjustment.update(
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 39fcb21..88ef69c 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -1744,6 +1744,7 @@
"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 6d55d77..be78d9e 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
@@ -16,9 +16,15 @@
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 @@
});
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 @@
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 @@
}
}
+ 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 04b0c4e..9ddc442 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 662e4b9..0d6f6b4 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -53,6 +53,7 @@
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 @@
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 @@
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 @@
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 @@
)
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 @@
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 @@
)
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 @@
).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)
@@ -613,6 +684,33 @@
@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):
args = json.loads(args)
@@ -760,3 +858,30 @@
)
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 6e0a685..4a78595 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 @@
# 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 @@
# 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 @@
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 @@
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 2bb479b..6552cd7 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -37,6 +37,12 @@
}
});
+ 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 5eb3656..d7419dc 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",