feat: multiple purchase invoice in asset repair doctype (#43460)

Co-authored-by: “rahulgupta8848” <“rahul.gupta@8848digital.com”>
This commit is contained in:
rahulgupta8848
2024-10-01 13:00:51 +05:30
committed by GitHub
parent c5d5421c0f
commit 2384309b43
8 changed files with 248 additions and 57 deletions

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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": []
}

View File

@@ -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

View File

@@ -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 = [