mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-14 04:15:10 +00:00
feat: multiple purchase invoice in asset repair doctype (#43460)
Co-authored-by: “rahulgupta8848” <“rahul.gupta@8848digital.com”>
This commit is contained in:
@@ -29,8 +29,9 @@ frappe.ui.form.on("Asset Repair", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("purchase_invoice", function () {
|
||||
frm.set_query("purchase_invoice", "invoices", function () {
|
||||
return {
|
||||
query: "erpnext.assets.doctype.asset_repair.asset_repair.get_purchase_invoice",
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
docstatus: 1,
|
||||
@@ -58,6 +59,16 @@ frappe.ui.form.on("Asset Repair", {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("expense_account", "invoices", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: ["=", 0],
|
||||
report_type: ["=", "Profit and Loss"],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"column_break_14",
|
||||
"project",
|
||||
"accounting_details",
|
||||
"purchase_invoice",
|
||||
"invoices",
|
||||
"section_break_y7cc",
|
||||
"capitalize_repair_cost",
|
||||
"stock_consumption",
|
||||
"column_break_8",
|
||||
@@ -229,26 +230,30 @@
|
||||
"label": "Increase In Asset Life(Months)",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
"mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0",
|
||||
"no_copy": 1,
|
||||
"options": "Purchase Invoice"
|
||||
},
|
||||
{
|
||||
"fetch_from": "asset.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "invoices",
|
||||
"fieldtype": "Table",
|
||||
"label": "Asset Repair Purchase Invoices",
|
||||
"mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0;",
|
||||
"no_copy": 1,
|
||||
"options": "Asset Repair Purchase Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_y7cc",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-13 16:14:14.398356",
|
||||
"modified": "2024-09-30 13:02:06.931188",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_diff_in_hours
|
||||
|
||||
import erpnext
|
||||
@@ -28,6 +29,9 @@ class AssetRepair(AccountsController):
|
||||
from erpnext.assets.doctype.asset_repair_consumed_item.asset_repair_consumed_item import (
|
||||
AssetRepairConsumedItem,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_repair_purchase_invoice.asset_repair_purchase_invoice import (
|
||||
AssetRepairPurchaseInvoice,
|
||||
)
|
||||
|
||||
actions_performed: DF.LongText | None
|
||||
amended_from: DF.Link | None
|
||||
@@ -41,9 +45,9 @@ class AssetRepair(AccountsController):
|
||||
downtime: DF.Data | None
|
||||
failure_date: DF.Datetime
|
||||
increase_in_asset_life: DF.Int
|
||||
invoices: DF.Table[AssetRepairPurchaseInvoice]
|
||||
naming_series: DF.Literal["ACC-ASR-.YYYY.-"]
|
||||
project: DF.Link | None
|
||||
purchase_invoice: DF.Link | None
|
||||
repair_cost: DF.Currency
|
||||
repair_status: DF.Literal["Pending", "Completed", "Cancelled"]
|
||||
stock_consumption: DF.Check
|
||||
@@ -54,10 +58,15 @@ class AssetRepair(AccountsController):
|
||||
def validate(self):
|
||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
||||
self.validate_dates()
|
||||
self.validate_purchase_invoice()
|
||||
self.validate_purchase_invoice_repair_cost()
|
||||
self.validate_purchase_invoice_expense_account()
|
||||
self.update_status()
|
||||
|
||||
if self.get("stock_items"):
|
||||
self.set_stock_items_cost()
|
||||
|
||||
self.calculate_repair_cost()
|
||||
self.calculate_total_repair_cost()
|
||||
|
||||
def validate_dates(self):
|
||||
@@ -66,6 +75,31 @@ class AssetRepair(AccountsController):
|
||||
_("Completion Date can not be before Failure Date. Please adjust the dates accordingly.")
|
||||
)
|
||||
|
||||
def validate_purchase_invoice(self):
|
||||
query = expense_item_pi_query(self.company)
|
||||
purchase_invoice_list = [item[0] for item in query.run()]
|
||||
for pi in self.invoices:
|
||||
if pi.purchase_invoice not in purchase_invoice_list:
|
||||
frappe.throw(_("Expense item not present in Purchase Invoice"))
|
||||
|
||||
def validate_purchase_invoice_repair_cost(self):
|
||||
for pi in self.invoices:
|
||||
if flt(pi.repair_cost) > frappe.db.get_value(
|
||||
"Purchase Invoice", pi.purchase_invoice, "base_net_total"
|
||||
):
|
||||
frappe.throw(_("Repair cost cannot be greater than purchase invoice base net total"))
|
||||
|
||||
def validate_purchase_invoice_expense_account(self):
|
||||
for pi in self.invoices:
|
||||
if pi.expense_account not in frappe.db.get_all(
|
||||
"Purchase Invoice Item", {"parent": pi.purchase_invoice}, pluck="expense_account"
|
||||
):
|
||||
frappe.throw(
|
||||
_("Expense account not present in Purchase Invoice {0}").format(
|
||||
get_link_to_form("Purchase Invoice", pi.purchase_invoice)
|
||||
)
|
||||
)
|
||||
|
||||
def update_status(self):
|
||||
if self.repair_status == "Pending" and self.asset_doc.status != "Out of Order":
|
||||
frappe.db.set_value("Asset", self.asset, "status", "Out of Order")
|
||||
@@ -82,6 +116,9 @@ class AssetRepair(AccountsController):
|
||||
for item in self.get("stock_items"):
|
||||
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
|
||||
|
||||
def calculate_repair_cost(self):
|
||||
self.repair_cost = sum(flt(pi.repair_cost) for pi in self.invoices)
|
||||
|
||||
def calculate_total_repair_cost(self):
|
||||
self.total_repair_cost = flt(self.repair_cost)
|
||||
|
||||
@@ -267,40 +304,39 @@ class AssetRepair(AccountsController):
|
||||
if flt(self.repair_cost) <= 0:
|
||||
return
|
||||
|
||||
pi_expense_account = (
|
||||
frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account
|
||||
)
|
||||
debit_against_account = set()
|
||||
|
||||
for pi in self.invoices:
|
||||
debit_against_account.add(pi.expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": pi.expense_account,
|
||||
"credit": pi.repair_cost,
|
||||
"credit_in_account_currency": pi.repair_cost,
|
||||
"against": fixed_asset_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
debit_against_account = ", ".join(debit_against_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit": self.repair_cost,
|
||||
"debit_in_account_currency": self.repair_cost,
|
||||
"against": pi_expense_account,
|
||||
"against": debit_against_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"against_voucher_type": "Purchase Invoice",
|
||||
"against_voucher": self.purchase_invoice,
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": pi_expense_account,
|
||||
"credit": self.repair_cost,
|
||||
"credit_in_account_currency": self.repair_cost,
|
||||
"against": fixed_asset_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
@@ -432,3 +468,31 @@ class AssetRepair(AccountsController):
|
||||
def get_downtime(failure_date, completion_date):
|
||||
downtime = time_diff_in_hours(completion_date, failure_date)
|
||||
return round(downtime, 2)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_purchase_invoice(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = expense_item_pi_query(filters.get("company"))
|
||||
return query.run(as_list=1)
|
||||
|
||||
|
||||
def expense_item_pi_query(company):
|
||||
PurchaseInvoice = DocType("Purchase Invoice")
|
||||
PurchaseInvoiceItem = DocType("Purchase Invoice Item")
|
||||
Item = DocType("Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(PurchaseInvoice)
|
||||
.join(PurchaseInvoiceItem)
|
||||
.on(PurchaseInvoiceItem.parent == PurchaseInvoice.name)
|
||||
.join(Item)
|
||||
.on(Item.name == PurchaseInvoiceItem.item_code)
|
||||
.select(PurchaseInvoice.name)
|
||||
.where(
|
||||
(Item.is_stock_item == 0)
|
||||
& (Item.is_fixed_asset == 0)
|
||||
& (PurchaseInvoice.company == company)
|
||||
& (PurchaseInvoice.docstatus == 1)
|
||||
)
|
||||
)
|
||||
return query
|
||||
|
||||
@@ -126,15 +126,17 @@ class TestAssetRepair(unittest.TestCase):
|
||||
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
|
||||
asset = create_asset(calculate_depreciation=1, submit=1)
|
||||
initial_asset_value = get_asset_value_after_depreciation(asset.name)
|
||||
asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
|
||||
asset_repair = create_asset_repair(
|
||||
asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1
|
||||
)
|
||||
asset.reload()
|
||||
|
||||
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
|
||||
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
|
||||
|
||||
def test_purchase_invoice(self):
|
||||
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
|
||||
self.assertTrue(asset_repair.purchase_invoice)
|
||||
asset_repair = create_asset_repair(capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1)
|
||||
self.assertTrue(asset_repair.invoices)
|
||||
|
||||
def test_gl_entries_with_perpetual_inventory(self):
|
||||
set_depreciation_settings_in_company(company="_Test Company with perpetual inventory")
|
||||
@@ -147,6 +149,7 @@ class TestAssetRepair(unittest.TestCase):
|
||||
"fixed_asset_account": "_Test Fixed Asset - TCP1",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
|
||||
"depreciation_expense_account": "_Test Depreciations - TCP1",
|
||||
"capital_work_in_progress_account": "CWIP Account - TCP1",
|
||||
},
|
||||
)
|
||||
asset_category.save()
|
||||
@@ -156,6 +159,9 @@ class TestAssetRepair(unittest.TestCase):
|
||||
stock_consumption=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
pi_expense_account1="Administrative Expenses - TCP1",
|
||||
pi_expense_account2="Legal Expenses - TCP1",
|
||||
item="_Test Non Stock Item",
|
||||
submit=1,
|
||||
)
|
||||
|
||||
@@ -181,16 +187,16 @@ class TestAssetRepair(unittest.TestCase):
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
|
||||
)
|
||||
pi_expense_account = (
|
||||
frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account
|
||||
)
|
||||
pi_expense_accounts = [pi.expense_account for pi in asset_repair.invoices]
|
||||
pi_repair_costs = [pi.repair_cost for pi in asset_repair.invoices]
|
||||
stock_entry_expense_account = (
|
||||
frappe.get_doc("Stock Entry", {"asset_repair": asset_repair.name}).get("items")[0].expense_account
|
||||
)
|
||||
|
||||
expected_values = {
|
||||
fixed_asset_account: [asset_repair.total_repair_cost, 0],
|
||||
pi_expense_account: [0, asset_repair.repair_cost],
|
||||
pi_expense_accounts[0]: [0, pi_repair_costs[0]],
|
||||
pi_expense_accounts[1]: [0, pi_repair_costs[1]],
|
||||
stock_entry_expense_account: [0, 100],
|
||||
}
|
||||
|
||||
@@ -203,6 +209,7 @@ class TestAssetRepair(unittest.TestCase):
|
||||
asset_repair = create_asset_repair(
|
||||
capitalize_repair_cost=1,
|
||||
stock_consumption=1,
|
||||
item="_Test Non Stock Item",
|
||||
submit=1,
|
||||
)
|
||||
|
||||
@@ -231,8 +238,14 @@ class TestAssetRepair(unittest.TestCase):
|
||||
default_expense_account = frappe.get_cached_value(
|
||||
"Company", asset_repair.company, "default_expense_account"
|
||||
)
|
||||
pi_expense_accounts = [pi.expense_account for pi in asset_repair.invoices]
|
||||
|
||||
expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]}
|
||||
expected_values = {
|
||||
fixed_asset_account: [650, 0],
|
||||
pi_expense_accounts[0]: [0, 250],
|
||||
default_expense_account: [0, 100],
|
||||
pi_expense_accounts[1]: [0, 300],
|
||||
}
|
||||
|
||||
for d in gl_entries:
|
||||
self.assertEqual(expected_values[d.account][0], d.debit)
|
||||
@@ -245,7 +258,7 @@ class TestAssetRepair(unittest.TestCase):
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Active")
|
||||
|
||||
initial_num_of_depreciations = num_of_depreciations(asset)
|
||||
create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
|
||||
create_asset_repair(asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1)
|
||||
|
||||
asset.reload()
|
||||
first_asset_depr_schedule.load_from_db()
|
||||
@@ -288,7 +301,6 @@ def create_asset_repair(**args):
|
||||
"asset_name": asset.asset_name,
|
||||
"failure_date": nowdate(),
|
||||
"description": "Test Description",
|
||||
"repair_cost": 0,
|
||||
"company": asset.company,
|
||||
}
|
||||
)
|
||||
@@ -351,16 +363,38 @@ def create_asset_repair(**args):
|
||||
|
||||
if args.capitalize_repair_cost:
|
||||
asset_repair.capitalize_repair_cost = 1
|
||||
asset_repair.repair_cost = 1000
|
||||
if asset.calculate_depreciation:
|
||||
asset_repair.increase_in_asset_life = 12
|
||||
pi = make_purchase_invoice(
|
||||
pi1 = make_purchase_invoice(
|
||||
company=asset.company,
|
||||
expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"),
|
||||
item=args.item or "_Test Item",
|
||||
expense_account=args.pi_expense_account1 or "Administrative Expenses - _TC",
|
||||
cost_center=asset_repair.cost_center,
|
||||
warehouse=args.warehouse or create_warehouse("Test Warehouse", company=asset.company),
|
||||
rate="50",
|
||||
)
|
||||
asset_repair.purchase_invoice = pi.name
|
||||
pi2 = make_purchase_invoice(
|
||||
company=asset.company,
|
||||
item=args.item or "_Test Item",
|
||||
expense_account=args.pi_expense_account2 or "Legal Expenses - _TC",
|
||||
cost_center=asset_repair.cost_center,
|
||||
warehouse=args.warehouse or create_warehouse("Test Warehouse", company=asset.company),
|
||||
rate="60",
|
||||
)
|
||||
invoices = [
|
||||
{
|
||||
"purchase_invoice": pi1.name,
|
||||
"expense_account": args.pi_expense_account1 or "Administrative Expenses - _TC",
|
||||
"repair_cost": args.pi_repair_cost1 or 250,
|
||||
},
|
||||
{
|
||||
"purchase_invoice": pi2.name,
|
||||
"expense_account": args.pi_expense_account2 or "Legal Expenses - _TC",
|
||||
"repair_cost": args.pi_repair_cost2 or 300,
|
||||
},
|
||||
]
|
||||
|
||||
for invoice in invoices:
|
||||
asset_repair.append("invoices", invoice)
|
||||
asset_repair.submit()
|
||||
return asset_repair
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-09-30 12:52:08.085813",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"purchase_invoice",
|
||||
"expense_account",
|
||||
"repair_cost"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Purchase Invoice",
|
||||
"options": "Purchase Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Expense Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "repair_cost",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Repair Cost",
|
||||
"options": "Company:company:default_currency",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-09-30 13:02:43.040762",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair Purchase Invoice",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AssetRepairPurchaseInvoice(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
expense_account: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
purchase_invoice: DF.Link | None
|
||||
repair_cost: DF.Currency
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -153,7 +153,9 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
post_depreciation_entries(getdate("2023-08-21"))
|
||||
|
||||
# create asset repair
|
||||
asset_repair = create_asset_repair(asset=asset_doc, capitalize_repair_cost=1, submit=1)
|
||||
asset_repair = create_asset_repair(
|
||||
asset=asset_doc, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1
|
||||
)
|
||||
|
||||
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Active")
|
||||
@@ -177,8 +179,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
|
||||
# Test gl entry creted from asset value adjustemnet
|
||||
expected_gle = (
|
||||
("_Test Difference Account - _TC", 5625.29, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 5625.29),
|
||||
("_Test Difference Account - _TC", 5175.29, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 5175.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
@@ -244,12 +246,12 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
["2023-05-31", 9983.33, 45408.05],
|
||||
["2023-06-30", 9983.33, 55391.38],
|
||||
["2023-07-31", 9983.33, 65374.71],
|
||||
["2023-08-31", 8133.33, 73508.04],
|
||||
["2023-09-30", 8133.33, 81641.37],
|
||||
["2023-10-31", 8133.33, 89774.7],
|
||||
["2023-11-30", 8133.33, 97908.03],
|
||||
["2023-12-31", 8133.33, 106041.36],
|
||||
["2024-01-15", 8133.35, 114174.71],
|
||||
["2023-08-31", 8208.33, 73583.04],
|
||||
["2023-09-30", 8208.33, 81791.37],
|
||||
["2023-10-31", 8208.33, 89999.7],
|
||||
["2023-11-30", 8208.33, 98208.03],
|
||||
["2023-12-31", 8208.33, 106416.36],
|
||||
["2024-01-15", 8208.35, 114624.71],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
|
||||
Reference in New Issue
Block a user