From 600333875a1e275d799988c35a9d0ccb80893737 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 21 Jun 2021 23:41:35 +0530 Subject: [PATCH 01/14] fix(Sales Invoice): Let item.asset be copied on duplicating the doc --- .../doctype/sales_invoice_item/sales_invoice_item.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 8e6952a93c..6690bdafc3 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -743,7 +743,6 @@ "fieldname": "asset", "fieldtype": "Link", "label": "Asset", - "no_copy": 1, "options": "Asset" }, { @@ -826,7 +825,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 01:05:22.123527", + "modified": "2021-06-21 23:03:11.599901", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", From 5b07e58412ae3b86c67fa90833b603b5dac4b64f Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 21 Jun 2021 23:51:09 +0530 Subject: [PATCH 02/14] fix(Sales Invoice): Let invoice be created for Sold Assets if it's a return invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index a008742390..c835debce6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -149,7 +149,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", "Sold"): + elif asset.status in ("Scrapped", "Cancelled") 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): From d0d5fedd484339038064f803460978eaa38196b1 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 22 Jun 2021 00:22:08 +0530 Subject: [PATCH 03/14] fix(Sales Invoice): Print appropriate message if item.asset is missing when the Item is a Fixed Asset --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c835debce6..ff1e14b30a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -916,7 +916,11 @@ class SalesInvoice(SellingController): for item in self.get("items"): if flt(item.base_net_amount, item.precision("base_net_amount")): if item.is_fixed_asset: - asset = frappe.get_doc("Asset", item.asset) + if item.get('asset'): + asset = frappe.get_doc("Asset", item.asset) + else: + frappe.throw(_("Enter Asset linked with Item {0}: {1} in row {2}.") + .format(item.item_code, item.item_name, item.idx)) if (len(asset.finance_books) > 1 and not item.finance_book and asset.finance_books[0].finance_book): From 8127208774d1db413c3f86b0f728420122fdd419 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 22 Jun 2021 23:12:56 +0530 Subject: [PATCH 04/14] fix(Sales Invoice): Reset Asset status on issuing Credit Note --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4655ea8222..58a2a33473 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -936,7 +936,8 @@ class SalesInvoice(SellingController): gl_entries.append(self.get_gl_dict(gle, item=item)) asset.db_set("disposal_date", self.posting_date) - asset.set_status("Sold" if self.docstatus==1 else None) + self.set_asset_status(asset) + else: # Do not book income for transfer within same company if not self.is_internal_transfer(): @@ -962,6 +963,12 @@ class SalesInvoice(SellingController): erpnext.is_perpetual_inventory_enabled(self.company): gl_entries += super(SalesInvoice, self).get_gl_entries() + def set_asset_status(self, asset): + if self.is_return: + asset.set_status() + else: + asset.set_status("Sold" if self.docstatus==1 else None) + def make_loyalty_point_redemption_gle(self, gl_entries): if cint(self.redeem_loyalty_points): gl_entries.append( From 32d7b1f6ad21ab588a66e2fefd7ca5a413710db3 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 23 Jun 2021 11:24:05 +0530 Subject: [PATCH 05/14] fix(Sales Invoice): Fix GL Entry creation for Return Invoices linked with Assets --- .../doctype/sales_invoice/sales_invoice.py | 10 ++-- erpnext/assets/doctype/asset/depreciation.py | 52 ++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 58a2a33473..347d2f58ab 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.assets.doctype.asset.depreciation \ - import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal + import get_disposal_account_and_cost_center, get_gl_entries_on_asset_movement from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -928,8 +928,12 @@ class SalesInvoice(SellingController): frappe.throw(_("Select finance book for the item {0} at row {1}") .format(item.item_code, item.idx)) - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset, - item.base_net_amount, item.finance_book) + if self.is_return: + fixed_asset_gl_entries = get_gl_entries_on_asset_movement(asset, + item.base_net_amount, item.finance_book, True) + else: + fixed_asset_gl_entries = get_gl_entries_on_asset_movement(asset, + item.base_net_amount, item.finance_book) for gle in fixed_asset_gl_entries: gle["against"] = self.customer diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 8f0afb42b2..a18f4278e1 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -147,7 +147,7 @@ def scrap_asset(asset_name): je.company = asset.company je.remark = "Scrap Entry for asset {0}".format(asset_name) - for entry in get_gl_entries_on_asset_disposal(asset): + for entry in get_gl_entries_on_asset_movement(asset): entry.update({ "reference_type": "Asset", "reference_name": asset_name @@ -177,7 +177,7 @@ def restore_asset(asset_name): asset.set_status() @frappe.whitelist() -def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_movement(asset, selling_amount=0, finance_book=None, is_return = False): fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset) disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company) depreciation_cost_center = asset.cost_center or depreciation_cost_center @@ -193,6 +193,44 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) if asset.calculate_depreciation else asset.value_after_depreciation) accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation) + if is_return: + gl_entries = get_gl_entries_on_asset_regain(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount) + profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount)) + + else: + gl_entries = get_gl_entries_on_asset_disposal(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount) + profit_amount = flt(selling_amount) - flt(value_after_depreciation) + + if profit_amount: + debit_or_credit = "debit" if profit_amount < 0 else "credit" + gl_entries.append({ + "account": disposal_account, + "cost_center": depreciation_cost_center, + debit_or_credit: abs(profit_amount), + debit_or_credit + "_in_account_currency": abs(profit_amount) + }) + + return gl_entries + +def get_gl_entries_on_asset_regain(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount): + gl_entries = [ + { + "account": fixed_asset_account, + "debit_in_account_currency": asset.gross_purchase_amount, + "debit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center + }, + { + "account": accumulated_depr_account, + "credit_in_account_currency": accumulated_depr_amount, + "credit": accumulated_depr_amount, + "cost_center": depreciation_cost_center + } + ] + + return gl_entries + +def get_gl_entries_on_asset_disposal(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount): gl_entries = [ { "account": fixed_asset_account, @@ -208,16 +246,6 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) } ] - profit_amount = flt(selling_amount) - flt(value_after_depreciation) - if profit_amount: - debit_or_credit = "debit" if profit_amount < 0 else "credit" - gl_entries.append({ - "account": disposal_account, - "cost_center": depreciation_cost_center, - debit_or_credit: abs(profit_amount), - debit_or_credit + "_in_account_currency": abs(profit_amount) - }) - return gl_entries @frappe.whitelist() From 88348e2da713603670506aef566c300566daf054 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 23 Jun 2021 11:57:53 +0530 Subject: [PATCH 06/14] fix(Sales Invoice): Print appropriate message if Asset isn't specified when the Item is a Fixed Asset --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 347d2f58ab..494f63f4a2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -920,9 +920,10 @@ class SalesInvoice(SellingController): if item.get('asset'): asset = frappe.get_doc("Asset", item.asset) else: - frappe.throw(_("Enter Asset linked with Item {0}: {1} in row {2}.") - .format(item.item_code, item.item_name, item.idx)) - + frappe.throw(_( + "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), + title=_("Missing 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}") From 4cf3d9ac2064d367877b8f29a0177dab8c61ff86 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 2 Jul 2021 15:25:04 +0530 Subject: [PATCH 07/14] fix(Sales Invoice): Let invoice be created for Sold Assets if it's a return invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 494f63f4a2..bfcf206757 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -149,7 +149,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") 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): From f8ee8058e7cba69882e953d45f512763f81c3518 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 2 Jul 2021 15:45:52 +0530 Subject: [PATCH 08/14] fix(Sales Invoice): Reset disposal_date on returning the Asset --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bfcf206757..dc5282037a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -932,15 +932,16 @@ class SalesInvoice(SellingController): if self.is_return: fixed_asset_gl_entries = get_gl_entries_on_asset_movement(asset, item.base_net_amount, item.finance_book, True) + asset.db_set("disposal_date", None) else: fixed_asset_gl_entries = get_gl_entries_on_asset_movement(asset, item.base_net_amount, item.finance_book) + asset.db_set("disposal_date", self.posting_date) for gle in fixed_asset_gl_entries: gle["against"] = self.customer gl_entries.append(self.get_gl_dict(gle, item=item)) - asset.db_set("disposal_date", self.posting_date) self.set_asset_status(asset) else: From ca87745be1e8ba7dad7ce2e49a30ff6b7d2e0976 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 6 Jul 2021 23:43:08 +0530 Subject: [PATCH 09/14] fix: Rename get_gl_entries_on_asset_movement to get_gl_entries_on_asset_disposal_and_regain --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 +++--- erpnext/assets/doctype/asset/depreciation.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index dc5282037a..6a3db939cf 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.assets.doctype.asset.depreciation \ - import get_disposal_account_and_cost_center, get_gl_entries_on_asset_movement + import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal_and_regain from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -930,11 +930,11 @@ class SalesInvoice(SellingController): .format(item.item_code, item.idx)) if self.is_return: - fixed_asset_gl_entries = get_gl_entries_on_asset_movement(asset, + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal_and_regain(asset, item.base_net_amount, item.finance_book, True) asset.db_set("disposal_date", None) else: - fixed_asset_gl_entries = get_gl_entries_on_asset_movement(asset, + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal_and_regain(asset, item.base_net_amount, item.finance_book) asset.db_set("disposal_date", self.posting_date) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index a18f4278e1..1d877a86f6 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -147,7 +147,7 @@ def scrap_asset(asset_name): je.company = asset.company je.remark = "Scrap Entry for asset {0}".format(asset_name) - for entry in get_gl_entries_on_asset_movement(asset): + for entry in get_gl_entries_on_asset_disposal_and_regain(asset): entry.update({ "reference_type": "Asset", "reference_name": asset_name @@ -177,7 +177,7 @@ def restore_asset(asset_name): asset.set_status() @frappe.whitelist() -def get_gl_entries_on_asset_movement(asset, selling_amount=0, finance_book=None, is_return = False): +def get_gl_entries_on_asset_disposal_and_regain(asset, selling_amount=0, finance_book=None, is_return = False): fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset) disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company) depreciation_cost_center = asset.cost_center or depreciation_cost_center From 3a7f25b218c4522255ee27e37881147cc2900289 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 7 Jul 2021 15:59:52 +0530 Subject: [PATCH 10/14] fix: Make functions more readable --- .../doctype/sales_invoice/sales_invoice.py | 8 +- erpnext/assets/doctype/asset/depreciation.py | 81 ++++++++++--------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 6a3db939cf..8ef5bcb4d7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.assets.doctype.asset.depreciation \ - import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal_and_regain + import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -930,11 +930,11 @@ class SalesInvoice(SellingController): .format(item.item_code, item.idx)) if self.is_return: - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal_and_regain(asset, - item.base_net_amount, item.finance_book, True) + fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset, + item.base_net_amount, item.finance_book) asset.db_set("disposal_date", None) else: - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal_and_regain(asset, + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset, item.base_net_amount, item.finance_book) asset.db_set("disposal_date", self.posting_date) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 1d877a86f6..8fdbbf95d4 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -147,7 +147,7 @@ def scrap_asset(asset_name): je.company = asset.company je.remark = "Scrap Entry for asset {0}".format(asset_name) - for entry in get_gl_entries_on_asset_disposal_and_regain(asset): + for entry in get_gl_entries_on_asset_disposal(asset): entry.update({ "reference_type": "Asset", "reference_name": asset_name @@ -176,43 +176,10 @@ def restore_asset(asset_name): asset.set_status() -@frappe.whitelist() -def get_gl_entries_on_asset_disposal_and_regain(asset, selling_amount=0, finance_book=None, is_return = False): - fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset) - disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company) - depreciation_cost_center = asset.cost_center or depreciation_cost_center +def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): + fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \ + get_asset_details(asset, finance_book) - idx = 1 - if finance_book: - for d in asset.finance_books: - if d.finance_book == finance_book: - idx = d.idx - break - - value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation - if asset.calculate_depreciation else asset.value_after_depreciation) - accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation) - - if is_return: - gl_entries = get_gl_entries_on_asset_regain(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount) - profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount)) - - else: - gl_entries = get_gl_entries_on_asset_disposal(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount) - profit_amount = flt(selling_amount) - flt(value_after_depreciation) - - if profit_amount: - debit_or_credit = "debit" if profit_amount < 0 else "credit" - gl_entries.append({ - "account": disposal_account, - "cost_center": depreciation_cost_center, - debit_or_credit: abs(profit_amount), - debit_or_credit + "_in_account_currency": abs(profit_amount) - }) - - return gl_entries - -def get_gl_entries_on_asset_regain(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount): gl_entries = [ { "account": fixed_asset_account, @@ -228,9 +195,16 @@ def get_gl_entries_on_asset_regain(fixed_asset_account, asset, depreciation_cost } ] + profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount)) + if profit_amount: + get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + return gl_entries -def get_gl_entries_on_asset_disposal(fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount): +def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None): + fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \ + get_asset_details(asset, finance_book) + gl_entries = [ { "account": fixed_asset_account, @@ -246,8 +220,39 @@ def get_gl_entries_on_asset_disposal(fixed_asset_account, asset, depreciation_co } ] + profit_amount = flt(selling_amount) - flt(value_after_depreciation) + if profit_amount: + get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + return gl_entries +def get_asset_details(asset, finance_book=None): + fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset) + disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company) + depreciation_cost_center = asset.cost_center or depreciation_cost_center + + idx = 1 + if finance_book: + for d in asset.finance_books: + if d.finance_book == finance_book: + idx = d.idx + break + + value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation + if asset.calculate_depreciation else asset.value_after_depreciation) + accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation) + + return fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation + +def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center): + debit_or_credit = "debit" if profit_amount < 0 else "credit" + gl_entries.append({ + "account": disposal_account, + "cost_center": depreciation_cost_center, + debit_or_credit: abs(profit_amount), + debit_or_credit + "_in_account_currency": abs(profit_amount) + }) + @frappe.whitelist() def get_disposal_account_and_cost_center(company): disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company, From 298b43c177761052eef9beb1864eed666cae71c6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 7 Jul 2021 20:56:15 +0530 Subject: [PATCH 11/14] fix: Let create_item() make items that are fixed assets --- erpnext/stock/doctype/item/test_item.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index c7467a5a0f..922049f144 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -587,8 +587,8 @@ def make_item_variant(): test_records = frappe.get_test_records('Item') def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC", - is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, - company="_Test Company"): + is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, is_fixed_asset=0, + asset_category=None, company="_Test Company"): if not frappe.db.exists("Item", item_code): item = frappe.new_doc("Item") item.item_code = item_code @@ -596,6 +596,8 @@ def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test W item.description = item_code item.item_group = "All Item Groups" item.is_stock_item = is_stock_item + item.is_fixed_asset = is_fixed_asset + item.asset_category = asset_category item.opening_stock = opening_stock item.valuation_rate = valuation_rate item.is_purchase_item = is_purchase_item From cfff3b87266d92baafeabb082c8c77a3b83efbe3 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 7 Jul 2021 21:04:20 +0530 Subject: [PATCH 12/14] fix: Test GL Entries made when an Asset is returned --- .../sales_invoice/test_sales_invoice.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 114b7d2d35..ee8b057b6d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -10,6 +10,7 @@ from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile +from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_category from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from frappe.model.naming import make_autoname @@ -1069,6 +1070,36 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(si1.outstanding_amount) self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500) + def test_gle_made_when_asset_is_returned(self): + create_item(item_code="_Test Item linked with Asset", is_stock_item = 0, is_fixed_asset=1, asset_category="Computers") + asset = create_asset(item_code="_Test Item linked with Asset") + + si = create_sales_invoice(item_code="_Test Item linked with Asset", asset=asset.name, qty=1, rate=90000) + return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="_Test Item linked with Asset", asset=asset.name, qty=-1, rate=90000) + + disposal_account = frappe.get_cached_value("Company", "_Test Company", "disposal_account") + + # Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000 + loss_for_si = frappe.get_all( + "GL Entry", + filters = { + "voucher_no": si.name, + "account": disposal_account + }, + fields = ["credit", "debit"] + )[0] + + loss_for_return_si = frappe.get_all( + "GL Entry", + filters = { + "voucher_no": return_si.name, + "account": disposal_account + }, + fields = ["credit", "debit"] + )[0] + + self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit']) + self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit']) def test_discount_on_net_total(self): si = frappe.copy_doc(test_records[2]) @@ -2164,6 +2195,7 @@ def create_sales_invoice(**args): "rate": args.rate if args.get("rate") is not None else 100, "income_account": args.income_account or "Sales - _TC", "expense_account": args.expense_account or "Cost of Goods Sold - _TC", + "asset": args.asset or None, "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, "conversion_factor": 1 From ef42e80065228d0150fab79f064632104e418db5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 9 Jul 2021 19:23:13 +0530 Subject: [PATCH 13/14] fix: Sider issues --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b16631b316..31de48216b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -10,7 +10,7 @@ from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile -from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_category +from erpnext.assets.doctype.asset.test_asset import create_asset from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from frappe.model.naming import make_autoname From 6357ffe4a142ff9ae861e26b9728b2cb110a0200 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 13 Jul 2021 22:31:01 +0530 Subject: [PATCH 14/14] fix: Create asset data --- .../doctype/sales_invoice/test_sales_invoice.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 31de48216b..c27a878dd8 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -10,7 +10,7 @@ from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile -from erpnext.assets.doctype.asset.test_asset import create_asset +from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from frappe.model.naming import make_autoname @@ -1071,11 +1071,11 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500) def test_gle_made_when_asset_is_returned(self): - create_item(item_code="_Test Item linked with Asset", is_stock_item = 0, is_fixed_asset=1, asset_category="Computers") - asset = create_asset(item_code="_Test Item linked with Asset") + create_asset_data() + asset = create_asset(item_code="Macbook Pro") - si = create_sales_invoice(item_code="_Test Item linked with Asset", asset=asset.name, qty=1, rate=90000) - return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="_Test Item linked with Asset", asset=asset.name, qty=-1, rate=90000) + si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000) + return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000) disposal_account = frappe.get_cached_value("Company", "_Test Company", "disposal_account")