mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-22 06:29:20 +00:00
Merge pull request #51198 from aerele/repost-asset-sales-voucher
fix: avoid creating multiple asset depreciations while reposting asset sales invoice
This commit is contained in:
@@ -362,21 +362,34 @@ class SalesInvoice(SellingController):
|
|||||||
validate_docs_for_deferred_accounting([self.name], [])
|
validate_docs_for_deferred_accounting([self.name], [])
|
||||||
|
|
||||||
def validate_fixed_asset(self):
|
def validate_fixed_asset(self):
|
||||||
for d in self.get("items"):
|
if self.doctype != "Sales Invoice":
|
||||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
return
|
||||||
asset = frappe.get_doc("Asset", d.asset)
|
|
||||||
if self.doctype == "Sales Invoice" and self.docstatus == 1:
|
|
||||||
if self.update_stock:
|
|
||||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
|
||||||
|
|
||||||
elif asset.status in ("Scrapped", "Cancelled", "Capitalized") or (
|
for d in self.get("items"):
|
||||||
asset.status == "Sold" and not self.is_return
|
if d.is_fixed_asset:
|
||||||
):
|
if d.asset:
|
||||||
frappe.throw(
|
if not self.is_return:
|
||||||
_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(
|
asset_status = frappe.db.get_value("Asset", d.asset, "status")
|
||||||
d.idx, d.asset, asset.status
|
if self.update_stock:
|
||||||
|
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
||||||
|
|
||||||
|
elif asset_status in ("Scrapped", "Cancelled", "Capitalized"):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Asset {1} cannot be sold, it is already {2}").format(
|
||||||
|
d.idx, d.asset, asset_status
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
elif asset_status == "Sold" and not self.is_return:
|
||||||
|
frappe.throw(_("Row #{0}: Asset {1} is already sold").format(d.idx, d.asset))
|
||||||
|
elif not self.return_against:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Return Against is required for returning asset").format(d.idx)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: You must select an Asset for Item {1}.").format(d.idx, d.item_code),
|
||||||
|
title=_("Missing Asset"),
|
||||||
|
)
|
||||||
|
|
||||||
def validate_item_cost_centers(self):
|
def validate_item_cost_centers(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
@@ -465,6 +478,8 @@ class SalesInvoice(SellingController):
|
|||||||
self.update_stock_reservation_entries()
|
self.update_stock_reservation_entries()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
|
self.process_asset_depreciation()
|
||||||
|
|
||||||
# this sequence because outstanding may get -ve
|
# this sequence because outstanding may get -ve
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
@@ -561,6 +576,8 @@ class SalesInvoice(SellingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
|
self.process_asset_depreciation()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
@@ -1182,6 +1199,91 @@ class SalesInvoice(SellingController):
|
|||||||
):
|
):
|
||||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
||||||
|
|
||||||
|
def process_asset_depreciation(self):
|
||||||
|
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
|
||||||
|
self.depreciate_asset_on_sale()
|
||||||
|
else:
|
||||||
|
self.restore_asset()
|
||||||
|
|
||||||
|
self.update_asset()
|
||||||
|
|
||||||
|
def depreciate_asset_on_sale(self):
|
||||||
|
"""
|
||||||
|
Depreciate asset on sale or cancellation of return sales invoice
|
||||||
|
"""
|
||||||
|
disposal_date = self.get_disposal_date()
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.asset:
|
||||||
|
asset = frappe.get_doc("Asset", d.asset)
|
||||||
|
if asset.calculate_depreciation and asset.status != "Fully Depreciated":
|
||||||
|
depreciate_asset(asset, disposal_date, self.get_note_for_asset_sale(asset))
|
||||||
|
|
||||||
|
def get_note_for_asset_sale(self, asset):
|
||||||
|
return _("This schedule was created when Asset {0} was {1} through Sales Invoice {2}.").format(
|
||||||
|
get_link_to_form(asset.doctype, asset.name),
|
||||||
|
_("returned") if self.is_return else _("sold"),
|
||||||
|
get_link_to_form(self.doctype, self.get("name")),
|
||||||
|
)
|
||||||
|
|
||||||
|
def restore_asset(self):
|
||||||
|
"""
|
||||||
|
Restore asset on return or cancellation of original sales invoice
|
||||||
|
"""
|
||||||
|
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.asset:
|
||||||
|
asset = frappe.get_cached_doc("Asset", d.asset)
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
posting_date = self.get_disposal_date()
|
||||||
|
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||||
|
|
||||||
|
note = self.get_note_for_asset_return(asset)
|
||||||
|
reset_depreciation_schedule(asset, self.posting_date, note)
|
||||||
|
|
||||||
|
def get_note_for_asset_return(self, asset):
|
||||||
|
asset_link = get_link_to_form(asset.doctype, asset.name)
|
||||||
|
invoice_link = get_link_to_form(self.doctype, self.get("name"))
|
||||||
|
if self.is_return:
|
||||||
|
return _(
|
||||||
|
"This schedule was created when Asset {0} was returned through Sales Invoice {1}."
|
||||||
|
).format(asset_link, invoice_link)
|
||||||
|
else:
|
||||||
|
return _(
|
||||||
|
"This schedule was created when Asset {0} was restored due to Sales Invoice {1} cancellation."
|
||||||
|
).format(asset_link, invoice_link)
|
||||||
|
|
||||||
|
def update_asset(self):
|
||||||
|
"""
|
||||||
|
Update asset status, disposal date and asset activity on sale or return sales invoice
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _update_asset(asset, disposal_date, note, asset_status=None):
|
||||||
|
frappe.db.set_value("Asset", d.asset, "disposal_date", disposal_date)
|
||||||
|
add_asset_activity(asset.name, note)
|
||||||
|
asset.set_status(asset_status)
|
||||||
|
|
||||||
|
disposal_date = self.get_disposal_date()
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.asset:
|
||||||
|
asset = frappe.get_cached_doc("Asset", d.asset)
|
||||||
|
|
||||||
|
if (self.is_return and self.docstatus == 1) or (not self.is_return and self.docstatus == 2):
|
||||||
|
note = _("Asset returned") if self.is_return else _("Asset sold")
|
||||||
|
asset_status, disposal_date = None, None
|
||||||
|
else:
|
||||||
|
note = _("Asset sold") if not self.is_return else _("Return invoice of asset cancelled")
|
||||||
|
asset_status = "Sold"
|
||||||
|
|
||||||
|
_update_asset(asset, disposal_date, note, asset_status)
|
||||||
|
|
||||||
|
def get_disposal_date(self):
|
||||||
|
if self.is_return:
|
||||||
|
disposal_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||||
|
else:
|
||||||
|
disposal_date = self.posting_date
|
||||||
|
|
||||||
|
return disposal_date
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||||
|
|
||||||
@@ -1358,68 +1460,8 @@ class SalesInvoice(SellingController):
|
|||||||
if self.is_internal_transfer():
|
if self.is_internal_transfer():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset and item.asset:
|
||||||
asset = self.get_asset(item)
|
self.get_gl_entries_for_fixed_asset(item, gl_entries)
|
||||||
|
|
||||||
if (self.docstatus == 2 and not self.is_return) or (
|
|
||||||
self.docstatus == 1 and self.is_return
|
|
||||||
):
|
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
|
||||||
asset,
|
|
||||||
item.base_net_amount,
|
|
||||||
item.finance_book,
|
|
||||||
self.get("doctype"),
|
|
||||||
self.get("name"),
|
|
||||||
self.get("posting_date"),
|
|
||||||
)
|
|
||||||
asset.db_set("disposal_date", None)
|
|
||||||
add_asset_activity(asset.name, _("Asset returned"))
|
|
||||||
asset_status = asset.get_status()
|
|
||||||
|
|
||||||
if asset.calculate_depreciation and not asset_status == "Fully Depreciated":
|
|
||||||
posting_date = (
|
|
||||||
frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
|
||||||
if self.is_return
|
|
||||||
else self.posting_date
|
|
||||||
)
|
|
||||||
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
|
||||||
notes = _(
|
|
||||||
"This schedule was created when Asset {0} was returned through Sales Invoice {1}."
|
|
||||||
).format(
|
|
||||||
get_link_to_form(asset.doctype, asset.name),
|
|
||||||
get_link_to_form(self.doctype, self.get("name")),
|
|
||||||
)
|
|
||||||
reset_depreciation_schedule(asset, self.posting_date, notes)
|
|
||||||
asset.reload()
|
|
||||||
|
|
||||||
else:
|
|
||||||
if asset.calculate_depreciation:
|
|
||||||
if not asset.status == "Fully Depreciated":
|
|
||||||
notes = _(
|
|
||||||
"This schedule was created when Asset {0} was sold through Sales Invoice {1}."
|
|
||||||
).format(
|
|
||||||
get_link_to_form(asset.doctype, asset.name),
|
|
||||||
get_link_to_form(self.doctype, self.get("name")),
|
|
||||||
)
|
|
||||||
depreciate_asset(asset, self.posting_date, notes)
|
|
||||||
asset.reload()
|
|
||||||
|
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
|
||||||
asset,
|
|
||||||
item.base_net_amount,
|
|
||||||
item.finance_book,
|
|
||||||
self.get("doctype"),
|
|
||||||
self.get("name"),
|
|
||||||
self.get("posting_date"),
|
|
||||||
)
|
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
|
||||||
add_asset_activity(asset.name, _("Asset sold"))
|
|
||||||
|
|
||||||
for gle in fixed_asset_gl_entries:
|
|
||||||
gle["against"] = self.customer
|
|
||||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
|
||||||
|
|
||||||
self.set_asset_status(asset)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
income_account = (
|
income_account = (
|
||||||
@@ -1455,17 +1497,31 @@ class SalesInvoice(SellingController):
|
|||||||
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
|
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
gl_entries += super().get_gl_entries()
|
gl_entries += super().get_gl_entries()
|
||||||
|
|
||||||
def get_asset(self, item):
|
def get_gl_entries_for_fixed_asset(self, item, gl_entries):
|
||||||
if item.get("asset"):
|
asset = frappe.get_cached_doc("Asset", item.asset)
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
|
||||||
|
if self.is_return:
|
||||||
|
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
||||||
|
asset,
|
||||||
|
item.base_net_amount,
|
||||||
|
item.finance_book,
|
||||||
|
self.get("doctype"),
|
||||||
|
self.get("name"),
|
||||||
|
self.get("posting_date"),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
frappe.throw(
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||||
_("Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
|
asset,
|
||||||
title=_("Missing Asset"),
|
item.base_net_amount,
|
||||||
|
item.finance_book,
|
||||||
|
self.get("doctype"),
|
||||||
|
self.get("name"),
|
||||||
|
self.get("posting_date"),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.check_finance_books(item, asset)
|
for gle in fixed_asset_gl_entries:
|
||||||
return asset
|
gle["against"] = self.customer
|
||||||
|
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enable_discount_accounting(self):
|
def enable_discount_accounting(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user