diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 150f68b7bd3..c71ea3648b9 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -204,7 +204,9 @@ class Account(NestedSet): if not self.account_currency: self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency") - elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"): + gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency") + + if gl_currency and self.account_currency != gl_currency: if frappe.db.get_value("GL Entry", {"account": self.name}): frappe.throw(_("Currency can not be changed after making entries using some other currency")) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index efc063de56a..f9c9173af08 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -241,6 +241,28 @@ class TestAccount(unittest.TestCase): for doc in to_delete: frappe.delete_doc("Account", doc) + def test_validate_account_currency(self): + from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry + + if not frappe.db.get_value("Account", "Test Currency Account - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Test Currency Account" + acc.parent_account = "Tax Assets - _TC" + acc.company = "_Test Company" + acc.insert() + else: + acc = frappe.get_doc("Account", "Test Currency Account - _TC") + + self.assertEqual(acc.account_currency, "INR") + + # Make a JV against this account + make_journal_entry( + "Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True + ) + + acc.account_currency = "USD" + self.assertRaises(frappe.ValidationError, acc.save) + def _make_test_records(verbose=None): from frappe.test_runner import make_test_objects diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 57bc0a77213..e6a46d0676b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -249,8 +249,9 @@ class PurchaseInvoice(BuyingController): def validate_warehouse(self, for_validate=True): if self.update_stock and for_validate: + stock_items = self.get_stock_items() for d in self.get("items"): - if not d.warehouse: + if not d.warehouse and d.item_code in stock_items: frappe.throw( _( "Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}" diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index f681b3480c2..e759ad0719b 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -68,7 +68,7 @@ class TestAsset(AssetSetup): def test_item_exists(self): asset = create_asset(item_code="MacBook", do_not_save=1) - self.assertRaises(frappe.DoesNotExistError, asset.save) + self.assertRaises(frappe.ValidationError, asset.save) def test_validate_item(self): asset = create_asset(item_code="MacBook Pro", do_not_save=1) diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index f97cd5e9dd0..e904af0dce3 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -3,19 +3,19 @@ import json +from typing import Dict import frappe from frappe import _ -from frappe.utils import cint, cstr, flt +from frappe.utils import cint, cstr, flt, getdate from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_end_of_life -def update_last_purchase_rate(doc, is_submit): +def update_last_purchase_rate(doc, is_submit) -> None: """updates last_purchase_rate in item table for each item""" - import frappe.utils - this_purchase_date = frappe.utils.getdate(doc.get("posting_date") or doc.get("transaction_date")) + this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date")) for d in doc.get("items"): # get last purchase details @@ -41,7 +41,7 @@ def update_last_purchase_rate(doc, is_submit): frappe.db.set_value("Item", d.item_code, "last_purchase_rate", flt(last_purchase_rate)) -def validate_for_items(doc): +def validate_for_items(doc) -> None: items = [] for d in doc.get("items"): if not d.qty: @@ -49,40 +49,11 @@ def validate_for_items(doc): continue frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code)) - # update with latest quantities - bin = frappe.db.sql( - """select projected_qty from `tabBin` where - item_code = %s and warehouse = %s""", - (d.item_code, d.warehouse), - as_dict=1, - ) - - f_lst = { - "projected_qty": bin and flt(bin[0]["projected_qty"]) or 0, - "ordered_qty": 0, - "received_qty": 0, - } - if d.doctype in ("Purchase Receipt Item", "Purchase Invoice Item"): - f_lst.pop("received_qty") - for x in f_lst: - if d.meta.get_field(x): - d.set(x, f_lst[x]) - - item = frappe.db.sql( - """select is_stock_item, - is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""", - d.item_code, - as_dict=1, - )[0] - + set_stock_levels(row=d) # update with latest quantities + item = validate_item_and_get_basic_data(row=d) + validate_stock_item_warehouse(row=d, item=item) validate_end_of_life(d.item_code, item.end_of_life, item.disabled) - # validate stock item - if item.is_stock_item == 1 and d.qty and not d.warehouse and not d.get("delivered_by_supplier"): - frappe.throw( - _("Warehouse is mandatory for stock Item {0} in row {1}").format(d.item_code, d.idx) - ) - items.append(cstr(d.item_code)) if ( @@ -93,7 +64,57 @@ def validate_for_items(doc): frappe.throw(_("Same item cannot be entered multiple times.")) -def check_on_hold_or_closed_status(doctype, docname): +def set_stock_levels(row) -> None: + projected_qty = frappe.db.get_value( + "Bin", + { + "item_code": row.item_code, + "warehouse": row.warehouse, + }, + "projected_qty", + ) + + qty_data = { + "projected_qty": flt(projected_qty), + "ordered_qty": 0, + "received_qty": 0, + } + if row.doctype in ("Purchase Receipt Item", "Purchase Invoice Item"): + qty_data.pop("received_qty") + + for field in qty_data: + if row.meta.get_field(field): + row.set(field, qty_data[field]) + + +def validate_item_and_get_basic_data(row) -> Dict: + item = frappe.db.get_values( + "Item", + filters={"name": row.item_code}, + fieldname=["is_stock_item", "is_sub_contracted_item", "end_of_life", "disabled"], + as_dict=1, + ) + if not item: + frappe.throw(_("Row #{0}: Item {1} does not exist").format(row.idx, frappe.bold(row.item_code))) + + return item[0] + + +def validate_stock_item_warehouse(row, item) -> None: + if ( + item.is_stock_item == 1 + and row.qty + and not row.warehouse + and not row.get("delivered_by_supplier") + ): + frappe.throw( + _("Row #{1}: Warehouse is mandatory for stock Item {0}").format( + frappe.bold(row.item_code), row.idx + ) + ) + + +def check_on_hold_or_closed_status(doctype, docname) -> None: status = frappe.db.get_value(doctype, docname, "status") if status in ("Closed", "On Hold"): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 72ac1b37ef6..3a20d3f232f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1267,17 +1267,9 @@ class AccountsController(TransactionBase): stock_items = [] item_codes = list(set(item.item_code for item in self.get("items"))) if item_codes: - stock_items = [ - r[0] - for r in frappe.db.sql( - """ - select name from `tabItem` - where name in (%s) and is_stock_item=1 - """ - % (", ".join(["%s"] * len(item_codes)),), - item_codes, - ) - ] + stock_items = frappe.db.get_values( + "Item", {"name": ["in", item_codes], "is_stock_item": 1}, pluck="name", cache=True + ) return stock_items