mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
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:
@@ -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");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user