mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 08:24:47 +00:00
Merge pull request #53138 from frappe/mergify/bp/version-16-hotfix/pr-52784
fix(stock): validate company for receipt documents and expense accounts (backport #52784)
This commit is contained in:
@@ -14,6 +14,10 @@ from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
|||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectCompanyValidationError(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LandedCostVoucher(Document):
|
class LandedCostVoucher(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
# This code is auto-generated. Do not modify anything in this block.
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
@@ -75,6 +79,7 @@ class LandedCostVoucher(Document):
|
|||||||
self.check_mandatory()
|
self.check_mandatory()
|
||||||
self.validate_receipt_documents()
|
self.validate_receipt_documents()
|
||||||
self.validate_line_items()
|
self.validate_line_items()
|
||||||
|
self.validate_expense_accounts()
|
||||||
init_landed_taxes_and_totals(self)
|
init_landed_taxes_and_totals(self)
|
||||||
self.set_total_taxes_and_charges()
|
self.set_total_taxes_and_charges()
|
||||||
if not self.get("items"):
|
if not self.get("items"):
|
||||||
@@ -116,11 +121,28 @@ class LandedCostVoucher(Document):
|
|||||||
receipt_documents = []
|
receipt_documents = []
|
||||||
|
|
||||||
for d in self.get("purchase_receipts"):
|
for d in self.get("purchase_receipts"):
|
||||||
docstatus = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus")
|
docstatus, company = frappe.get_cached_value(
|
||||||
|
d.receipt_document_type, d.receipt_document, ["docstatus", "company"]
|
||||||
|
)
|
||||||
if docstatus != 1:
|
if docstatus != 1:
|
||||||
msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
|
msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
|
||||||
frappe.throw(_(msg), title=_("Invalid Document"))
|
frappe.throw(_(msg), title=_("Invalid Document"))
|
||||||
|
|
||||||
|
if company != self.company:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row {0}: {1} {2} is linked to company {3}. Please select a document belonging to company {4}."
|
||||||
|
).format(
|
||||||
|
d.idx,
|
||||||
|
d.receipt_document_type,
|
||||||
|
frappe.bold(d.receipt_document),
|
||||||
|
frappe.bold(company),
|
||||||
|
frappe.bold(self.company),
|
||||||
|
),
|
||||||
|
title=_("Incorrect Company"),
|
||||||
|
exc=IncorrectCompanyValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
if d.receipt_document_type == "Purchase Invoice":
|
if d.receipt_document_type == "Purchase Invoice":
|
||||||
update_stock = frappe.db.get_value(
|
update_stock = frappe.db.get_value(
|
||||||
d.receipt_document_type, d.receipt_document, "update_stock"
|
d.receipt_document_type, d.receipt_document, "update_stock"
|
||||||
@@ -152,6 +174,24 @@ class LandedCostVoucher(Document):
|
|||||||
_("Row {0}: Cost center is required for an item {1}").format(item.idx, item.item_code)
|
_("Row {0}: Cost center is required for an item {1}").format(item.idx, item.item_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_expense_accounts(self):
|
||||||
|
for t in self.taxes:
|
||||||
|
company = frappe.get_cached_value("Account", t.expense_account, "company")
|
||||||
|
|
||||||
|
if company != self.company:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row {0}: Expense Account {1} is linked to company {2}. Please select an account belonging to company {3}."
|
||||||
|
).format(
|
||||||
|
t.idx,
|
||||||
|
frappe.bold(t.expense_account),
|
||||||
|
frappe.bold(company),
|
||||||
|
frappe.bold(self.company),
|
||||||
|
),
|
||||||
|
title=_("Incorrect Account"),
|
||||||
|
exc=IncorrectCompanyValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
def set_total_taxes_and_charges(self):
|
def set_total_taxes_and_charges(self):
|
||||||
self.total_taxes_and_charges = sum(flt(d.base_amount) for d in self.get("taxes"))
|
self.total_taxes_and_charges = sum(flt(d.base_amount) for d in self.get("taxes"))
|
||||||
|
|
||||||
|
|||||||
@@ -178,6 +178,39 @@ class TestLandedCostVoucher(IntegrationTestCase):
|
|||||||
self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
|
self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
|
||||||
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
||||||
|
|
||||||
|
def test_lcv_validates_company(self):
|
||||||
|
from erpnext.stock.doctype.landed_cost_voucher.landed_cost_voucher import (
|
||||||
|
IncorrectCompanyValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
company_a = "_Test Company"
|
||||||
|
company_b = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
company=company_a,
|
||||||
|
warehouse="Stores - _TC",
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
lcv = make_landed_cost_voucher(
|
||||||
|
company=company_b,
|
||||||
|
receipt_document_type="Purchase Receipt",
|
||||||
|
receipt_document=pr.name,
|
||||||
|
charges=50,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(IncorrectCompanyValidationError, lcv.validate_receipt_documents)
|
||||||
|
lcv.company = company_a
|
||||||
|
|
||||||
|
self.assertRaises(IncorrectCompanyValidationError, lcv.validate_expense_accounts)
|
||||||
|
lcv.taxes[0].expense_account = get_expense_account(company_a)
|
||||||
|
|
||||||
|
lcv.save()
|
||||||
|
distribute_landed_cost_on_items(lcv)
|
||||||
|
lcv.submit()
|
||||||
|
|
||||||
def test_landed_cost_voucher_for_zero_purchase_rate(self):
|
def test_landed_cost_voucher_for_zero_purchase_rate(self):
|
||||||
"Test impact of LCV on future stock balances."
|
"Test impact of LCV on future stock balances."
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
@@ -1260,6 +1293,7 @@ def make_landed_cost_voucher(**args):
|
|||||||
lcv = frappe.new_doc("Landed Cost Voucher")
|
lcv = frappe.new_doc("Landed Cost Voucher")
|
||||||
lcv.company = args.company or "_Test Company"
|
lcv.company = args.company or "_Test Company"
|
||||||
lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount"
|
lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount"
|
||||||
|
expense_account = get_expense_account(args.company or "_Test Company")
|
||||||
|
|
||||||
lcv.set(
|
lcv.set(
|
||||||
"purchase_receipts",
|
"purchase_receipts",
|
||||||
@@ -1280,7 +1314,7 @@ def make_landed_cost_voucher(**args):
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"description": "Shipping Charges",
|
"description": "Shipping Charges",
|
||||||
"expense_account": args.expense_account or "Expenses Included In Valuation - TCP1",
|
"expense_account": args.expense_account or expense_account,
|
||||||
"amount": args.charges,
|
"amount": args.charges,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1300,6 +1334,7 @@ def create_landed_cost_voucher(receipt_document_type, receipt_document, company,
|
|||||||
lcv = frappe.new_doc("Landed Cost Voucher")
|
lcv = frappe.new_doc("Landed Cost Voucher")
|
||||||
lcv.company = company
|
lcv.company = company
|
||||||
lcv.distribute_charges_based_on = "Amount"
|
lcv.distribute_charges_based_on = "Amount"
|
||||||
|
expense_account = get_expense_account(company)
|
||||||
|
|
||||||
lcv.set(
|
lcv.set(
|
||||||
"purchase_receipts",
|
"purchase_receipts",
|
||||||
@@ -1319,7 +1354,7 @@ def create_landed_cost_voucher(receipt_document_type, receipt_document, company,
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"description": "Insurance Charges",
|
"description": "Insurance Charges",
|
||||||
"expense_account": "Expenses Included In Valuation - TCP1",
|
"expense_account": expense_account,
|
||||||
"amount": charges,
|
"amount": charges,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1334,6 +1369,11 @@ def create_landed_cost_voucher(receipt_document_type, receipt_document, company,
|
|||||||
return lcv
|
return lcv
|
||||||
|
|
||||||
|
|
||||||
|
def get_expense_account(company):
|
||||||
|
company_abbr = frappe.get_cached_value("Company", company, "abbr")
|
||||||
|
return f"Expenses Included In Valuation - {company_abbr}"
|
||||||
|
|
||||||
|
|
||||||
def distribute_landed_cost_on_items(lcv):
|
def distribute_landed_cost_on_items(lcv):
|
||||||
based_on = lcv.distribute_charges_based_on.lower()
|
based_on = lcv.distribute_charges_based_on.lower()
|
||||||
total = sum(flt(d.get(based_on)) for d in lcv.get("items"))
|
total = sum(flt(d.get(based_on)) for d in lcv.get("items"))
|
||||||
|
|||||||
Reference in New Issue
Block a user