[fix] added validation to match account currency with existing gle and test case
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 5551ab7..3927b8e 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -6,12 +6,10 @@
from frappe import _
from frappe.utils import flt, fmt_money, getdate, formatdate
from frappe.model.document import Document
-from erpnext.accounts.party import get_party_account_currency
+from erpnext.accounts.party import validate_party_gle_currency, get_party_account_currency
from erpnext.accounts.utils import get_account_currency
-
-class CustomerFrozen(frappe.ValidationError): pass
-class InvalidCurrency(frappe.ValidationError): pass
-class InvalidAccountCurrency(frappe.ValidationError): pass
+from erpnext.setup.doctype.company.company import get_company_currency
+from erpnext.exceptions import InvalidAccountCurrency, CustomerFrozen
class GLEntry(Document):
def validate(self):
@@ -103,16 +101,16 @@
frappe.throw("{0} {1} is frozen".format(self.party_type, self.party), CustomerFrozen)
def validate_currency(self):
- company_currency = frappe.db.get_value("Company", self.company, "default_currency")
+ company_currency = get_company_currency(self.company)
account_currency = get_account_currency(self.account)
if not self.account_currency:
self.account_currency = company_currency
+
if account_currency != self.account_currency:
frappe.throw(_("Accounting Entry for {0} can only be made in currency: {1}")
.format(self.account, (account_currency or company_currency)), InvalidAccountCurrency)
-
if self.party_type and self.party:
party_account_currency = get_party_account_currency(self.party_type, self.party, self.company)
@@ -120,6 +118,8 @@
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
.format(self.party_type, self.party, party_account_currency), InvalidAccountCurrency)
+ validate_party_gle_currency(self.party_type, self.party, self.company)
+
def validate_balance_type(account, adv_adj=False):
if not adv_adj and account:
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 753e012..a4d6406 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -5,6 +5,7 @@
import unittest, frappe
from frappe.utils import flt
from erpnext.accounts.utils import get_actual_expense, BudgetError, get_fiscal_year
+from erpnext.exceptions import InvalidAccountCurrency
class TestJournalEntry(unittest.TestCase):
@@ -166,15 +167,15 @@
existing_expense = self.get_actual_expense(posting_date)
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True)
-
+
def test_multi_currency(self):
jv = make_journal_entry("_Test Bank USD - _TC",
"_Test Bank - _TC", 100, exchange_rate=50, save=False)
-
+
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
-
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+
+ gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", jv.name, as_dict=1)
@@ -197,12 +198,10 @@
"credit_in_account_currency": 5000
}
}
-
+
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[gle.account][field], gle[field])
-
-
# cancel
jv.cancel()
@@ -212,6 +211,40 @@
self.assertFalse(gle)
+ def test_disallow_change_in_account_currency_for_a_party(self):
+ # create jv in USD
+ jv = make_journal_entry("_Test Bank USD - _TC",
+ "_Test Receivable USD - _TC", 100, save=False)
+
+ jv.accounts[1].update({
+ "party_type": "Customer",
+ "party": "_Test Customer USD"
+ })
+
+ jv.submit()
+
+ # create jv in USD, but account currency in INR
+ jv = make_journal_entry("_Test Bank - _TC",
+ "_Test Receivable - _TC", 100, save=False)
+
+ jv.accounts[1].update({
+ "party_type": "Customer",
+ "party": "_Test Customer USD"
+ })
+
+ self.assertRaises(InvalidAccountCurrency, jv.submit)
+
+ # back in USD
+ jv = make_journal_entry("_Test Bank USD - _TC",
+ "_Test Receivable USD - _TC", 100, save=False)
+
+ jv.accounts[1].update({
+ "party_type": "Customer",
+ "party": "_Test Customer USD"
+ })
+
+ jv.submit()
+
def make_journal_entry(account1, account2, amount, cost_center=None, exchange_rate=1, save=True, submit=False):
jv = frappe.new_doc("Journal Entry")
jv.posting_date = "2013-02-14"
@@ -231,7 +264,7 @@
"cost_center": cost_center,
"credit_in_account_currency": amount if amount > 0 else 0,
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
- exchange_rate: exchange_rate
+ "exchange_rate": exchange_rate
}
])
if save or submit:
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index b39f30b..67286db 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -10,7 +10,7 @@
import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
test_records as pr_test_records
-from erpnext.controllers.accounts_controller import InvalidCurrency
+from erpnext.exceptions import InvalidCurrency
test_dependencies = ["Item", "Cost Center"]
test_ignore = ["Serial No"]
@@ -219,7 +219,7 @@
pi.load_from_db()
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
- where reference_type='Purchase Invoice'
+ where reference_type='Purchase Invoice'
and reference_name=%s and debit_in_account_currency=300""", pi.name))
self.assertEqual(pi.outstanding_amount, 1212.30)
@@ -237,17 +237,17 @@
existing_purchase_cost = frappe.db.sql("""select sum(ifnull(base_net_amount, 0))
from `tabPurchase Invoice Item` where project_name = '_Test Project' and docstatus=1""")
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
-
+
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project_name="_Test Project")
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
existing_purchase_cost + 15000)
pi1 = make_purchase_invoice(qty=10, project_name="_Test Project")
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
existing_purchase_cost + 15500)
pi1.cancel()
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
existing_purchase_cost + 15000)
pi.cancel()
@@ -278,14 +278,14 @@
self.assertEquals(expected_values[gle.account][1], gle.credit)
set_perpetual_inventory(0)
-
+
def test_multi_currency_gle(self):
set_perpetual_inventory(0)
-
- pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
+
+ pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
order by account asc""", pi.name, as_dict=1)
@@ -308,16 +308,16 @@
"credit_in_account_currency": 0
}
}
-
+
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[gle.account][field], gle[field])
-
-
+
+
# Check for valid currency
pi1 = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
do_not_save=True)
-
+
self.assertRaises(InvalidCurrency, pi1.save)
# cancel
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 449f98d..0eae7cb 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -7,8 +7,7 @@
from frappe.utils import nowdate, add_days, flt
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
-from erpnext.controllers.accounts_controller import InvalidCurrency
-from erpnext.accounts.doctype.gl_entry.gl_entry import InvalidAccountCurrency
+from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -842,13 +841,13 @@
self.assertEquals(si.total_taxes_and_charges, 234.44)
self.assertEquals(si.base_grand_total, 859.44)
self.assertEquals(si.grand_total, 859.44)
-
+
def test_multi_currency_gle(self):
set_perpetual_inventory(0)
- si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
+ si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
- gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1)
@@ -871,7 +870,7 @@
"credit_in_account_currency": 5000
}
}
-
+
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[gle.account][field], gle[field])
@@ -883,38 +882,38 @@
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)
self.assertFalse(gle)
-
+
def test_invalid_currency(self):
# Customer currency = USD
-
+
# Transaction currency cannot be INR
- si1 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
+ si1 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
do_not_save=True)
-
+
self.assertRaises(InvalidCurrency, si1.save)
-
+
# Transaction currency cannot be EUR
- si2 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
+ si2 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="EUR", conversion_rate=80, do_not_save=True)
-
+
self.assertRaises(InvalidCurrency, si2.save)
-
+
# Transaction currency only allowed in USD
- si3 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
+ si3 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
-
+
# Party Account currency must be in USD, as there is existing GLE with USD
- si4 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
+ si4 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
currency="USD", conversion_rate=50, do_not_submit=True)
-
+
self.assertRaises(InvalidAccountCurrency, si4.submit)
-
+
# Party Account currency must be in USD, force customer currency as there is no GLE
-
+
si3.cancel()
- si5 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
+ si5 = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable - _TC",
currency="USD", conversion_rate=50, do_not_submit=True)
-
+
self.assertRaises(InvalidAccountCurrency, si5.submit)
def create_sales_invoice(**args):
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 8181feb..1eaf8a7 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -10,6 +10,7 @@
from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff
from erpnext.utilities.doctype.address.address import get_address_display
from erpnext.utilities.doctype.contact.contact import get_contact_details
+from erpnext.exceptions import InvalidAccountCurrency
class DuplicatePartyAccountError(frappe.ValidationError): pass
@@ -191,6 +192,25 @@
return frappe.local_cache("party_account_currency", (party_type, party, company), generator)
+def get_party_gle_currency(party_type, party, company):
+ def generator():
+ existing_gle_currency = frappe.db.sql("""select account_currency from `tabGL Entry`
+ where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
+ limit 1""", { "company": company, "party_type": party_type, "party": party })
+
+ return existing_gle_currency[0][0] if existing_gle_currency else None
+
+ return frappe.local_cache("party_gle_currency", (party_type, party, company), generator)
+
+def validate_party_gle_currency(party_type, party, company):
+ """Validate party account currency with existing GL Entry's currency"""
+ party_account_currency = get_party_account_currency(party_type, party, company)
+ existing_gle_currency = get_party_gle_currency(party_type, party, company)
+
+ if existing_gle_currency and party_account_currency != existing_gle_currency:
+ frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
+ .format(party_type, party, existing_gle_currency), InvalidAccountCurrency)
+
def validate_party_accounts(doc):
companies = []
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 4f3e6c8..a92d070 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -10,13 +10,11 @@
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
from erpnext.controllers.sales_and_purchase_return import validate_return
-from erpnext.accounts.party import get_party_account_currency
+from erpnext.accounts.party import get_party_account_currency, validate_party_gle_currency
+from erpnext.exceptions import CustomerFrozen, InvalidCurrency
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
-class CustomerFrozen(frappe.ValidationError): pass
-class InvalidCurrency(frappe.ValidationError): pass
-
class AccountsController(TransactionBase):
def __init__(self, arg1, arg2=None):
super(AccountsController, self).__init__(arg1, arg2)
diff --git a/erpnext/exceptions.py b/erpnext/exceptions.py
new file mode 100644
index 0000000..c339edf
--- /dev/null
+++ b/erpnext/exceptions.py
@@ -0,0 +1,7 @@
+from __future__ import unicode_literals
+import frappe
+
+# accounts
+class CustomerFrozen(frappe.ValidationError): pass
+class InvalidAccountCurrency(frappe.ValidationError): pass
+class InvalidCurrency(frappe.ValidationError): pass
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 4bd02d5..f18194f 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -7,7 +7,7 @@
import unittest
from frappe.test_runner import make_test_records
-from erpnext.controllers.accounts_controller import CustomerFrozen
+from erpnext.exceptions import CustomerFrozen
test_ignore = ["Price List"]
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index ccb792f..ba5f025 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -7,6 +7,8 @@
import unittest
from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
+from erpnext.accounts.doctype.journal_entry.test_journal_entry \
+ import make_journal_entry
from frappe.tests.test_permissions import set_user_permission_doctypes
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 785e7aa..d337805 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -46,13 +46,13 @@
if for_company != self.name:
frappe.throw(_("Account {0} does not belong to company: {1}")
.format(self.get(field), self.name))
-
+
def validate_currency(self):
self.previous_default_currency = frappe.db.get_value("Company", self.name, "default_currency")
if self.default_currency and self.previous_default_currency and \
self.default_currency != self.previous_default_currency and \
self.check_if_transactions_exist():
- frappe.throw(_("Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency."))
+ frappe.throw(_("Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency."))
def on_update(self):
if not frappe.db.sql("""select name from tabAccount
@@ -208,7 +208,7 @@
# clear default accounts, warehouses from item
if warehouses:
-
+
for f in ["default_warehouse", "website_warehouse"]:
frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)"""
% (f, f, ', '.join(['%s']*len(warehouses))), tuple(warehouses))
@@ -257,3 +257,7 @@
parts.append(company_abbr)
return " - ".join(parts)
+
+def get_company_currency(company):
+ return frappe.local_cache("company_currency", company,
+ lambda: frappe.db.get_value("Company", company, "default_currency"))