mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-09 23:39:28 +00:00
test(stock): cover accounting dimensions on landed cost vouchers
This commit is contained in:
@@ -1408,3 +1408,265 @@ def distribute_landed_cost_on_items(lcv):
|
||||
for item in lcv.get("items"):
|
||||
item.applicable_charges = flt(item.get(based_on)) * flt(lcv.total_taxes_and_charges) / flt(total)
|
||||
item.applicable_charges = flt(item.applicable_charges, lcv.precision("applicable_charges", item))
|
||||
|
||||
|
||||
def ensure_dimension_fields_on_lcv_charges(dimensions):
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
make_dimension_in_accounting_doctypes,
|
||||
)
|
||||
|
||||
created = False
|
||||
|
||||
for name in dimensions:
|
||||
dimension = frappe.get_doc("Accounting Dimension", name)
|
||||
if frappe.db.exists(
|
||||
"Custom Field", {"dt": "Landed Cost Taxes and Charges", "fieldname": dimension.fieldname}
|
||||
):
|
||||
continue
|
||||
|
||||
make_dimension_in_accounting_doctypes(dimension, ["Landed Cost Taxes and Charges"])
|
||||
created = True
|
||||
|
||||
if created:
|
||||
frappe.clear_cache(doctype="Landed Cost Taxes and Charges")
|
||||
|
||||
|
||||
def create_branch(branch):
|
||||
if not frappe.db.exists("Branch", branch):
|
||||
frappe.get_doc({"doctype": "Branch", "branch": branch}).insert()
|
||||
|
||||
return branch
|
||||
|
||||
|
||||
class TestLandedCostVoucherAccountingDimensions(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
self.company = "_Test Company with perpetual inventory"
|
||||
self.warehouse = "Stores - TCP1"
|
||||
self.expense_account = get_expense_account(self.company)
|
||||
|
||||
ensure_dimension_fields_on_lcv_charges(["Branch"])
|
||||
self.branch_a = create_branch("_Test LCV Branch A")
|
||||
self.branch_b = create_branch("_Test LCV Branch B")
|
||||
|
||||
def make_lcv(self, pr, charges, do_not_submit=False):
|
||||
lcv = frappe.new_doc("Landed Cost Voucher")
|
||||
lcv.company = self.company
|
||||
lcv.distribute_charges_based_on = "Amount"
|
||||
lcv.set(
|
||||
"purchase_receipts",
|
||||
[
|
||||
{
|
||||
"receipt_document_type": "Purchase Receipt",
|
||||
"receipt_document": pr.name,
|
||||
"supplier": pr.supplier,
|
||||
"posting_date": pr.posting_date,
|
||||
"grand_total": pr.base_grand_total,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
for idx, charge in enumerate(charges):
|
||||
lcv.append(
|
||||
"taxes",
|
||||
{
|
||||
"description": f"_Test Charge {idx + 1}",
|
||||
"expense_account": charge.pop("expense_account", self.expense_account),
|
||||
**charge,
|
||||
},
|
||||
)
|
||||
|
||||
lcv.insert()
|
||||
|
||||
if not do_not_submit:
|
||||
lcv.submit()
|
||||
|
||||
return lcv
|
||||
|
||||
def get_lcv_gl_entries(self, pr, account=None):
|
||||
return frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": pr.name,
|
||||
"is_cancelled": 0,
|
||||
**({"account": account} if account else {}),
|
||||
},
|
||||
fields=["account", "debit", "credit", "cost_center", "project", "branch"],
|
||||
order_by="credit desc",
|
||||
)
|
||||
|
||||
def make_dimension_mandatory(self, name, mandatory_for_pl=0, mandatory_for_bs=0):
|
||||
dimension = frappe.get_doc("Accounting Dimension", name)
|
||||
row = next((d for d in dimension.dimension_defaults if d.company == self.company), None)
|
||||
|
||||
if row:
|
||||
previous = (row.mandatory_for_pl, row.mandatory_for_bs)
|
||||
self.addCleanup(self.restore_dimension_default, name, previous)
|
||||
else:
|
||||
row = dimension.append(
|
||||
"dimension_defaults",
|
||||
{"company": self.company, "reference_document": dimension.document_type},
|
||||
)
|
||||
self.addCleanup(self.remove_dimension_default, name)
|
||||
|
||||
row.mandatory_for_pl = mandatory_for_pl
|
||||
row.mandatory_for_bs = mandatory_for_bs
|
||||
dimension.save()
|
||||
|
||||
def restore_dimension_default(self, name, previous):
|
||||
dimension = frappe.get_doc("Accounting Dimension", name)
|
||||
for row in dimension.dimension_defaults:
|
||||
if row.company == self.company:
|
||||
row.mandatory_for_pl, row.mandatory_for_bs = previous
|
||||
dimension.save()
|
||||
|
||||
def remove_dimension_default(self, name):
|
||||
dimension = frappe.get_doc("Accounting Dimension", name)
|
||||
dimension.set(
|
||||
"dimension_defaults",
|
||||
[d for d in dimension.dimension_defaults if d.company != self.company],
|
||||
)
|
||||
dimension.save()
|
||||
|
||||
def test_charge_row_dimension_reaches_gl_entry(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_lcv(pr, [{"amount": 100, "branch": self.branch_a}])
|
||||
|
||||
charge_entries = self.get_lcv_gl_entries(pr, self.expense_account)
|
||||
self.assertEqual(len(charge_entries), 1)
|
||||
self.assertEqual(charge_entries[0].credit, 100.0)
|
||||
self.assertEqual(charge_entries[0].branch, self.branch_a)
|
||||
|
||||
stock_account = get_inventory_account(self.company, self.warehouse)
|
||||
self.assertFalse(self.get_lcv_gl_entries(pr, stock_account)[0].branch)
|
||||
|
||||
def test_charge_row_cost_center_and_project_override_receipt_item(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
|
||||
create_cost_center(
|
||||
cost_center_name="_Test LCV Cost Center",
|
||||
company=self.company,
|
||||
parent_cost_center=f"{self.company} - TCP1",
|
||||
)
|
||||
cost_center = "_Test LCV Cost Center - TCP1"
|
||||
|
||||
if not frappe.db.exists("Project", {"project_name": "_Test LCV Project"}):
|
||||
frappe.get_doc(
|
||||
{"doctype": "Project", "project_name": "_Test LCV Project", "company": self.company}
|
||||
).insert()
|
||||
project = frappe.db.get_value("Project", {"project_name": "_Test LCV Project"})
|
||||
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
item_cost_center = pr.items[0].cost_center
|
||||
|
||||
self.make_lcv(pr, [{"amount": 100, "cost_center": cost_center, "project": project}])
|
||||
|
||||
charge_entries = self.get_lcv_gl_entries(pr, self.expense_account)
|
||||
self.assertEqual(len(charge_entries), 1)
|
||||
self.assertEqual(charge_entries[0].cost_center, cost_center)
|
||||
self.assertEqual(charge_entries[0].project, project)
|
||||
|
||||
stock_account = get_inventory_account(self.company, self.warehouse)
|
||||
self.assertEqual(self.get_lcv_gl_entries(pr, stock_account)[0].cost_center, item_cost_center)
|
||||
|
||||
def test_blank_charge_row_falls_back_to_receipt_item(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_lcv(pr, [{"amount": 100}])
|
||||
|
||||
charge_entries = self.get_lcv_gl_entries(pr, self.expense_account)
|
||||
self.assertEqual(len(charge_entries), 1)
|
||||
self.assertEqual(charge_entries[0].cost_center, pr.items[0].cost_center)
|
||||
self.assertFalse(charge_entries[0].branch)
|
||||
|
||||
def test_charge_rows_on_same_account_with_different_dimensions_stay_separate(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_lcv(
|
||||
pr,
|
||||
[
|
||||
{"amount": 60, "branch": self.branch_a},
|
||||
{"amount": 40, "branch": self.branch_b},
|
||||
],
|
||||
)
|
||||
|
||||
charge_entries = self.get_lcv_gl_entries(pr, self.expense_account)
|
||||
self.assertEqual(len(charge_entries), 2)
|
||||
self.assertEqual(
|
||||
{(e.branch, e.credit) for e in charge_entries},
|
||||
{(self.branch_a, 60.0), (self.branch_b, 40.0)},
|
||||
)
|
||||
self.assertEqual(sum(e.credit for e in charge_entries), 100.0)
|
||||
|
||||
def test_two_vouchers_on_same_account_with_different_dimensions_stay_separate(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_lcv(pr, [{"amount": 60, "branch": self.branch_a}])
|
||||
self.make_lcv(pr, [{"amount": 40, "branch": self.branch_b}])
|
||||
|
||||
charge_entries = self.get_lcv_gl_entries(pr, self.expense_account)
|
||||
self.assertEqual(len(charge_entries), 2)
|
||||
self.assertEqual(
|
||||
{(e.branch, e.credit) for e in charge_entries},
|
||||
{(self.branch_a, 60.0), (self.branch_b, 40.0)},
|
||||
)
|
||||
|
||||
def test_mandatory_pl_dimension_is_satisfied_by_charge_row(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_dimension_mandatory("Branch", mandatory_for_pl=1)
|
||||
|
||||
self.make_lcv(pr, [{"amount": 100, "branch": self.branch_a}])
|
||||
|
||||
charge_entries = self.get_lcv_gl_entries(pr, self.expense_account)
|
||||
self.assertEqual(len(charge_entries), 1)
|
||||
self.assertEqual(charge_entries[0].branch, self.branch_a)
|
||||
|
||||
def test_missing_mandatory_dimension_is_reported_on_the_voucher(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_dimension_mandatory("Branch", mandatory_for_pl=1)
|
||||
|
||||
with self.assertRaises(frappe.ValidationError) as raised:
|
||||
self.make_lcv(pr, [{"amount": 100}])
|
||||
|
||||
message = str(raised.exception)
|
||||
self.assertIn("Branch", message)
|
||||
self.assertIn(self.expense_account, message)
|
||||
|
||||
def test_dimensions_survive_reposting(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
self.make_lcv(
|
||||
pr,
|
||||
[
|
||||
{"amount": 60, "branch": self.branch_a},
|
||||
{"amount": 40, "branch": self.branch_b},
|
||||
],
|
||||
)
|
||||
|
||||
before = {(e.branch, e.credit) for e in self.get_lcv_gl_entries(pr, self.expense_account)}
|
||||
|
||||
items, warehouses = pr.get_items_and_warehouses()
|
||||
update_gl_entries_after(pr.posting_date, pr.posting_time, warehouses, items, company=pr.company)
|
||||
|
||||
after = {(e.branch, e.credit) for e in self.get_lcv_gl_entries(pr, self.expense_account)}
|
||||
self.assertEqual(before, after)
|
||||
|
||||
def test_cancelling_the_voucher_nets_each_dimension_to_zero(self):
|
||||
pr = make_purchase_receipt(company=self.company, warehouse=self.warehouse)
|
||||
lcv = self.make_lcv(
|
||||
pr,
|
||||
[
|
||||
{"amount": 60, "branch": self.branch_a},
|
||||
{"amount": 40, "branch": self.branch_b},
|
||||
],
|
||||
)
|
||||
|
||||
lcv.reload()
|
||||
lcv.cancel()
|
||||
|
||||
balances = {}
|
||||
for entry in frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": pr.name, "account": self.expense_account},
|
||||
fields=["branch", "debit", "credit"],
|
||||
):
|
||||
balances[entry.branch] = balances.get(entry.branch, 0.0) + entry.debit - entry.credit
|
||||
|
||||
for branch, balance in balances.items():
|
||||
self.assertEqual(flt(balance, 2), 0.0, msg=f"branch {branch} does not net to zero")
|
||||
|
||||
Reference in New Issue
Block a user