Merge pull request #26159 from GangaManoj/asset-credit-note
This commit is contained in:
commit
bb493a0602
@ -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.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.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||||
from erpnext.assets.doctype.asset.depreciation \
|
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_disposal, get_gl_entries_on_asset_regain
|
||||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
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.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
|
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||||
@ -149,7 +149,7 @@ class SalesInvoice(SellingController):
|
|||||||
if self.update_stock:
|
if self.update_stock:
|
||||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
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))
|
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):
|
def validate_item_cost_centers(self):
|
||||||
@ -918,22 +918,33 @@ class SalesInvoice(SellingController):
|
|||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset:
|
||||||
|
if item.get('asset'):
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
|
else:
|
||||||
|
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
|
if (len(asset.finance_books) > 1 and not item.finance_book
|
||||||
and asset.finance_books[0].finance_book):
|
and asset.finance_books[0].finance_book):
|
||||||
frappe.throw(_("Select finance book for the item {0} at row {1}")
|
frappe.throw(_("Select finance book for the item {0} at row {1}")
|
||||||
.format(item.item_code, item.idx))
|
.format(item.item_code, item.idx))
|
||||||
|
|
||||||
|
if self.is_return:
|
||||||
|
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(asset,
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
|
||||||
item.base_net_amount, item.finance_book)
|
item.base_net_amount, item.finance_book)
|
||||||
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
|
|
||||||
for gle in fixed_asset_gl_entries:
|
for gle in fixed_asset_gl_entries:
|
||||||
gle["against"] = self.customer
|
gle["against"] = self.customer
|
||||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||||
|
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
self.set_asset_status(asset)
|
||||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
|
||||||
else:
|
else:
|
||||||
# Do not book income for transfer within same company
|
# Do not book income for transfer within same company
|
||||||
if not self.is_internal_transfer():
|
if not self.is_internal_transfer():
|
||||||
@ -959,6 +970,12 @@ class SalesInvoice(SellingController):
|
|||||||
erpnext.is_perpetual_inventory_enabled(self.company):
|
erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
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):
|
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||||
if cint(self.redeem_loyalty_points):
|
if cint(self.redeem_loyalty_points):
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
|
|||||||
@ -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.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.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.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
||||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
@ -1069,6 +1070,36 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertFalse(si1.outstanding_amount)
|
self.assertFalse(si1.outstanding_amount)
|
||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
||||||
|
|
||||||
|
def test_gle_made_when_asset_is_returned(self):
|
||||||
|
create_asset_data()
|
||||||
|
asset = create_asset(item_code="Macbook Pro")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
# 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):
|
def test_discount_on_net_total(self):
|
||||||
si = frappe.copy_doc(test_records[2])
|
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,
|
"rate": args.rate if args.get("rate") is not None else 100,
|
||||||
"income_account": args.income_account or "Sales - _TC",
|
"income_account": args.income_account or "Sales - _TC",
|
||||||
"expense_account": args.expense_account or "Cost of Goods Sold - _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",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
"conversion_factor": 1
|
"conversion_factor": 1
|
||||||
|
|||||||
@ -743,7 +743,6 @@
|
|||||||
"fieldname": "asset",
|
"fieldname": "asset",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Asset",
|
"label": "Asset",
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Asset"
|
"options": "Asset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -826,7 +825,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-23 01:05:22.123527",
|
"modified": "2021-06-21 23:03:11.599901",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@ -176,22 +176,34 @@ def restore_asset(asset_name):
|
|||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
@frappe.whitelist()
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
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(asset, selling_amount=0, finance_book=None):
|
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
||||||
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
|
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
|
||||||
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
get_asset_details(asset, finance_book)
|
||||||
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)
|
|
||||||
|
|
||||||
gl_entries = [
|
gl_entries = [
|
||||||
{
|
{
|
||||||
@ -210,6 +222,29 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
|||||||
|
|
||||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||||
if profit_amount:
|
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"
|
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
||||||
gl_entries.append({
|
gl_entries.append({
|
||||||
"account": disposal_account,
|
"account": disposal_account,
|
||||||
@ -218,8 +253,6 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
|||||||
debit_or_credit + "_in_account_currency": abs(profit_amount)
|
debit_or_credit + "_in_account_currency": abs(profit_amount)
|
||||||
})
|
})
|
||||||
|
|
||||||
return gl_entries
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_disposal_account_and_cost_center(company):
|
def get_disposal_account_and_cost_center(company):
|
||||||
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
|
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
|
||||||
|
|||||||
@ -587,8 +587,8 @@ def make_item_variant():
|
|||||||
test_records = frappe.get_test_records('Item')
|
test_records = frappe.get_test_records('Item')
|
||||||
|
|
||||||
def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC",
|
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,
|
is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, is_fixed_asset=0,
|
||||||
company="_Test Company"):
|
asset_category=None, company="_Test Company"):
|
||||||
if not frappe.db.exists("Item", item_code):
|
if not frappe.db.exists("Item", item_code):
|
||||||
item = frappe.new_doc("Item")
|
item = frappe.new_doc("Item")
|
||||||
item.item_code = item_code
|
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.description = item_code
|
||||||
item.item_group = "All Item Groups"
|
item.item_group = "All Item Groups"
|
||||||
item.is_stock_item = is_stock_item
|
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.opening_stock = opening_stock
|
||||||
item.valuation_rate = valuation_rate
|
item.valuation_rate = valuation_rate
|
||||||
item.is_purchase_item = is_purchase_item
|
item.is_purchase_item = is_purchase_item
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user