feat(opening invoice creation tool): add project to opening invoice child row (backport #54662) (#56401)

Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
This commit is contained in:
mergify[bot]
2026-06-23 21:07:26 +00:00
committed by GitHub
parent 4555c323af
commit d54938fa64
5 changed files with 160 additions and 102 deletions

View File

@@ -74,29 +74,31 @@ frappe.ui.form.on("Opening Invoice Creation Tool", {
}, },
setup_company_filters: function (frm) { setup_company_filters: function (frm) {
frm.set_query("cost_center", "invoices", function (doc, cdt, cdn) { frm.events.apply_company_query_filter(frm, "cost_center", "invoices", { is_group: 0 });
return { frm.events.apply_company_query_filter(frm, "project", "invoices");
filters: { frm.events.apply_company_query_filter(frm, "project");
company: doc.company, frm.events.apply_company_query_filter(frm, "cost_center", undefined, { is_group: 0 });
}, frm.events.apply_company_query_filter(frm, "temporary_opening_account", "invoices", {
}; account_type: "Temporary",
is_group: 0,
}); });
},
frm.set_query("cost_center", function (doc) { apply_company_query_filter: function (frm, field_name, child_doctype = null, filters = {}) {
const query = function (doc) {
return { return {
filters: { filters: {
company: doc.company, company: doc.company,
...filters,
}, },
}; };
}); };
frm.set_query("temporary_opening_account", "invoices", function (doc, cdt, cdn) { if (child_doctype) {
return { frm.set_query(field_name, child_doctype, query);
filters: { } else {
company: doc.company, frm.set_query(field_name, query);
}, }
};
});
}, },
company: function (frm) { company: function (frm) {
@@ -120,11 +122,6 @@ frappe.ui.form.on("Opening Invoice Creation Tool", {
}, },
invoice_type: function (frm) { invoice_type: function (frm) {
$.each(frm.doc.invoices, (idx, row) => {
row.party_type = frm.doc.invoice_type == "Sales" ? "Customer" : "Supplier";
frappe.model.set_value(row.doctype, row.name, "party", "");
frappe.model.set_value(row.doctype, row.name, "party_name", "");
});
frm.clear_table("invoices"); frm.clear_table("invoices");
frm.refresh_fields(); frm.refresh_fields();
frm.trigger("update_party_labels"); frm.trigger("update_party_labels");
@@ -219,7 +216,19 @@ frappe.ui.form.on("Opening Invoice Creation Tool Item", {
}); });
}, },
invoices_add: (frm) => { invoices_add: (frm, cdt, cdn) => {
const row = frappe.get_doc(cdt, cdn);
const field_copy = [];
["project", "cost_center"].forEach((fieldname) => {
if (frm.doc[fieldname]) {
frappe.model.set_value(cdt, cdn, fieldname, frm.doc[fieldname]);
} else {
field_copy.push(fieldname);
}
});
frm.script_manager.copy_from_first_row("invoices", row, field_copy);
frm.trigger("update_invoice_table"); frm.trigger("update_invoice_table");
}, },
}); });

View File

@@ -133,6 +133,17 @@ class OpeningInvoiceCreationTool(Document):
if not row.get(scrub(d)): if not row.get(scrub(d)):
frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type)) frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type))
self.validate_temporary_opening_account(row)
def validate_temporary_opening_account(self, row):
account_type = frappe.get_cached_value("Account", row.temporary_opening_account, "account_type")
if account_type != "Temporary":
frappe.throw(
_("Row #{0}: {1} account is not of type {2}").format(
row.idx, row.temporary_opening_account, "Temporary"
)
)
def get_invoices(self): def get_invoices(self):
invoices = [] invoices = []
for row in self.invoices: for row in self.invoices:
@@ -203,6 +214,7 @@ class OpeningInvoiceCreationTool(Document):
"description": row.item_name or "Opening Invoice Item", "description": row.item_name or "Opening Invoice Item",
income_expense_account_field: row.temporary_opening_account, income_expense_account_field: row.temporary_opening_account,
"cost_center": cost_center, "cost_center": cost_center,
"project": row.get("project") or self.get("project"),
} }
) )

View File

@@ -2,10 +2,12 @@
# See license.txt # See license.txt
import frappe import frappe
from frappe.utils import add_days, today
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import ( from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account, get_temporary_opening_account,
) )
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.tests.utils import ERPNextTestSuite from erpnext.tests.utils import ERPNextTestSuite
@@ -14,21 +16,26 @@ class TestOpeningInvoiceCreationTool(ERPNextTestSuite):
self, self,
invoice_type="Sales", invoice_type="Sales",
company=None, company=None,
party_1=None, invoices=None,
party_2=None, project=None,
invoice_number=None, cost_center=None,
department=None, department=None,
return_doc=False,
): ):
doc = frappe.get_single("Opening Invoice Creation Tool") doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict( args = get_opening_invoice_creation_dict(
invoice_type=invoice_type, invoice_type=invoice_type,
company=company, company=company,
party_1=party_1, invoices=invoices,
party_2=party_2, project=project,
invoice_number=invoice_number, cost_center=cost_center,
department=department, department=department,
) )
doc.update(args) doc.update(args)
if return_doc:
return doc
return doc.make_invoices() return doc.make_invoices()
def test_opening_sales_invoice_creation(self): def test_opening_sales_invoice_creation(self):
@@ -37,8 +44,8 @@ class TestOpeningInvoiceCreationTool(ERPNextTestSuite):
self.assertEqual(len(invoices), 2) self.assertEqual(len(invoices), 2)
expected_value = { expected_value = {
"keys": ["customer", "outstanding_amount", "status"], "keys": ["customer", "outstanding_amount", "status"],
0: ["_Test Customer", 300, "Overdue"], 0: ["_Test Customer", 200, "Overdue"],
1: ["_Test Customer 1", 250, "Overdue"], 1: ["_Test Customer 1", 200, "Overdue"],
} }
self.check_expected_values(invoices, expected_value) self.check_expected_values(invoices, expected_value)
@@ -55,48 +62,34 @@ class TestOpeningInvoiceCreationTool(ERPNextTestSuite):
for field_idx, field in enumerate(expected_value["keys"]): for field_idx, field in enumerate(expected_value["keys"]):
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx]) self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
def test_opening_invoice_requires_temporary_account_type(self):
doc = self.make_invoices(company="_Test Opening Invoice Company", return_doc=True)
doc.invoices[0].temporary_opening_account = "Sales - _TOIC"
self.assertRaises(frappe.ValidationError, doc.make_invoices)
def test_opening_purchase_invoice_creation(self): def test_opening_purchase_invoice_creation(self):
invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company") invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2) self.assertEqual(len(invoices), 2)
expected_value = { expected_value = {
"keys": ["supplier", "outstanding_amount", "status"], "keys": ["supplier", "outstanding_amount", "status"],
0: ["_Test Supplier", 300, "Overdue"], 0: ["_Test Supplier", 200, "Overdue"],
1: ["_Test Supplier 1", 250, "Overdue"], 1: ["_Test Supplier 1", 200, "Overdue"],
} }
self.check_expected_values(invoices, expected_value, "Purchase") self.check_expected_values(invoices, expected_value, "Purchase")
def test_opening_sales_invoice_creation_with_missing_debit_account(self): def test_opening_sales_invoice_creation_with_missing_debit_account(self):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B") party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account") old_default_receivable_account = frappe.db.get_value(
frappe.db.set_value("Company", company, "default_receivable_account", "") "Company", "_Test Opening Invoice Company", "default_receivable_account"
)
frappe.db.set_value("Company", "_Test Opening Invoice Company", "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"): self.make_invoices(
cc = frappe.get_doc( company="_Test Opening Invoice Company",
{ invoices=[{"party": party_1}, {"party": party_2}],
"doctype": "Cost Center", )
"cost_center_name": "_Test Opening Invoice Company",
"is_group": 1,
"company": "_Test Opening Invoice Company",
}
)
cc.insert(ignore_mandatory=True)
cc2 = frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "Main",
"is_group": 0,
"company": "_Test Opening Invoice Company",
"parent_cost_center": cc.name,
}
)
cc2.insert()
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
# Check if missing debit account error raised # Check if missing debit account error raised
error_log = frappe.db.exists( error_log = frappe.db.exists(
@@ -106,71 +99,107 @@ class TestOpeningInvoiceCreationTool(ERPNextTestSuite):
self.assertTrue(error_log) self.assertTrue(error_log)
# teardown # teardown
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account) frappe.db.set_value(
"Company",
def test_renaming_of_invoice_using_invoice_number_field(self): "_Test Opening Invoice Company",
company = "_Test Opening Invoice Company" "default_receivable_account",
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B") old_default_receivable_account,
self.make_invoices(
company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11"
) )
sales_inv1 = frappe.get_all("Sales Invoice", filters={"customer": "Customer A"})[0].get("name") def test_renaming_of_invoice_using_invoice_number_field(self):
sales_inv2 = frappe.get_all("Sales Invoice", filters={"customer": "Customer B"})[0].get("name") party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
self.assertEqual(sales_inv1, "TEST-NEW-INV-11") invoices = self.make_invoices(
company="_Test Opening Invoice Company",
invoices=[
{"party": party_1, "invoice_number": "TEST-NEW-INV-11"},
{"party": party_2},
],
)
# teardown self.assertEqual(invoices[0], "TEST-NEW-INV-11")
for inv in [sales_inv1, sales_inv2]:
doc = frappe.get_doc("Sales Invoice", inv)
doc.cancel()
def test_opening_invoice_with_accounting_dimension(self): def test_opening_invoice_with_accounting_dimension(self):
invoices = self.make_invoices( invoices = self.make_invoices(
invoice_type="Sales", company="_Test Opening Invoice Company", department="Sales - _TOIC" invoice_type="Sales", company="_Test Opening Invoice Company", department="Sales - _TOIC"
) )
expected_value = { for invoice in invoices:
"keys": ["customer", "outstanding_amount", "status", "department"], self.assertEqual(frappe.db.get_value("Sales Invoice", invoice, "department"), "Sales - _TOIC")
0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"],
1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"], def test_opening_entry_project_linking(self):
} doc = self.make_invoices(
self.check_expected_values(invoices, expected_value, invoice_type="Sales") company="_Test Opening Invoice Company", invoice_type="Sales", return_doc=True
)
project_1 = make_project(
{"project_name": "Test Opening Invoice projecty 01", "company": "_Test Opening Invoice Company"}
)
project_2 = make_project(
{"project_name": "Test Opening Invoice projecty 02", "company": "_Test Opening Invoice Company"}
)
doc.invoices[0].project = project_1.name
doc.invoices[1].project = project_2.name
invoices = doc.make_invoices()
sales_invoice_1 = frappe.get_doc("Sales Invoice", invoices[0])
sales_invoice_2 = frappe.get_doc("Sales Invoice", invoices[1])
self.assertEqual(sales_invoice_1.items[0].project, project_1.name)
self.assertEqual(sales_invoice_2.items[0].project, project_2.name)
def get_opening_invoice_creation_dict(**args): def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company") company = args.get("company", "_Test Company")
default_invoices = []
default_invoice_rows = [
{
"qty": 1.0,
"outstanding_amount": 200,
"party": f"_Test {party}",
"item_name": "Opening Item",
"due_date": add_days(today(), -10),
"posting_date": add_days(today(), -15),
"temporary_opening_account": get_temporary_opening_account(company),
},
{
"qty": 1.0,
"outstanding_amount": 200,
"party": f"_Test {party} 1",
"item_name": "Opening Item",
"due_date": add_days(today(), -10),
"posting_date": add_days(today(), -15),
"temporary_opening_account": get_temporary_opening_account(company),
},
]
for row in args.get("invoices") or default_invoice_rows:
default_invoices.append(
{
"qty": row.get("qty") or 1.0,
"outstanding_amount": row.get("outstanding_amount") or 200,
"party": row.get("party") or f"_Test {party}",
"item_name": row.get("item_name") or "Opening Item",
"due_date": row.get("due_date") or add_days(today(), -10),
"posting_date": row.get("posting_date") or add_days(today(), -15),
"temporary_opening_account": row.get("temporary_opening_account")
or get_temporary_opening_account(company),
"invoice_number": row.get("invoice_number"),
"project": row.get("project"),
"cost_center": row.get("cost_center"),
}
)
invoice_dict = frappe._dict( invoice_dict = frappe._dict(
{ {
"company": company, "company": company,
"invoice_type": args.get("invoice_type", "Sales"), "invoice_type": args.get("invoice_type", "Sales"),
"invoices": [ "project": args.get("project"),
{ "cost_center": args.get("cost_center"),
"qty": 1.0, "invoices": default_invoices,
"outstanding_amount": 300,
"party": args.get("party_1") or f"_Test {party}",
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
"temporary_opening_account": get_temporary_opening_account(company),
"invoice_number": args.get("invoice_number"),
},
{
"qty": 2.0,
"outstanding_amount": 250,
"party": args.get("party_2") or f"_Test {party} 1",
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
"temporary_opening_account": get_temporary_opening_account(company),
"invoice_number": None,
},
],
} }
) )
invoice_dict.update(args) invoice_dict.update(args)
invoice_dict.invoices = default_invoices
return invoice_dict return invoice_dict

View File

@@ -21,7 +21,8 @@
"qty", "qty",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break" "dimension_col_break",
"project"
], ],
"fields": [ "fields": [
{ {
@@ -125,11 +126,17 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Party Name" "label": "Party Name"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2026-03-20 02:11:42.023575", "modified": "2026-04-29 17:08:15.617047",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Opening Invoice Creation Tool Item", "name": "Opening Invoice Creation Tool Item",

View File

@@ -26,6 +26,7 @@ class OpeningInvoiceCreationToolItem(Document):
party_name: DF.Data | None party_name: DF.Data | None
party_type: DF.Link | None party_type: DF.Link | None
posting_date: DF.Date | None posting_date: DF.Date | None
project: DF.Link | None
qty: DF.Data | None qty: DF.Data | None
supplier_invoice_date: DF.Date | None supplier_invoice_date: DF.Date | None
temporary_opening_account: DF.Link | None temporary_opening_account: DF.Link | None