From 57a9001f12f4f1bb5e9bfade33820a93f5ba52d3 Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Fri, 16 Feb 2024 18:11:26 +0100 Subject: [PATCH] feat(gl)!: Prevent using multiple journals in a single transaction --- .../test_accounting_journal.py | 47 ++++++++++++++++++- .../accounts_settings/accounts_settings.json | 9 +++- .../doctype/journal_entry/journal_entry.py | 6 ++- erpnext/accounts/general_ledger.py | 35 ++++++++++++-- erpnext/regional/france/setup.py | 5 +- erpnext/translations/fr.csv | 2 + 6 files changed, 95 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_journal/test_accounting_journal.py b/erpnext/accounts/doctype/accounting_journal/test_accounting_journal.py index bef61e936d0..46d3f9fcd54 100644 --- a/erpnext/accounts/doctype/accounting_journal/test_accounting_journal.py +++ b/erpnext/accounts/doctype/accounting_journal/test_accounting_journal.py @@ -2,10 +2,13 @@ # See license.txt +from unittest.mock import patch + import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import nowdate +import erpnext.accounts.doctype.journal_entry.journal_entry from erpnext.accounts.doctype.accounting_journal.accounting_journal import ( accounting_journal_adjustment, ) @@ -85,3 +88,45 @@ class TestAccountingJournal(FrappeTestCase): si_status = frappe.db.get_value("Sales Invoice", sales_invoice.name, "status") self.assertEqual(si_status, "Paid") + + def make_journal_entry_multi_journals(self): + return frappe.get_doc( + { + "doctype": "Journal Entry", + "company": "_Test Company", + "posting_date": nowdate(), + "multi_currency": 1, # to avoid validation error + "accounts": [ + { + "account": "_Test Bank - _TC", + "debit_in_account_currency": 100, + "accounting_journal": "BQ", + }, + { + "account": "_Test Bank - _TC", + "credit_in_account_currency": 100, + "accounting_journal": "MD", + }, + ], + } + ) + + @change_settings("Accounts Settings", {"force_unique_journal_in_transaction": 1}) + def test_force_unique_journal_in_journal_entry(self): + with self.assertRaisesRegex( + frappe.ValidationError, + "Your entries are linked to different journals. Please make sure it is correct.", + ): + self.make_journal_entry_multi_journals().insert() + + @change_settings("Accounts Settings", {"force_unique_journal_in_transaction": 1}) + @patch.object( + erpnext.accounts.doctype.journal_entry.journal_entry.JournalEntry, + "validate_accounting_journals", + lambda *args, **kwargs: None, + ) + def test_force_unique_journal_in_transaction(self): + with self.assertRaisesRegex( + frappe.ValidationError, "Multiple accounting journals found in the same transaction: BQ, MD" + ): + self.make_journal_entry_multi_journals().submit() diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index b85055f4a2c..0e155a25b57 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -8,6 +8,7 @@ "field_order": [ "invoice_and_billing_tab", "mandatory_accounting_journal", + "force_unique_journal_in_transaction", "validate_posting_date_chronology_in_sales_invoices", "column_break_3", "enable_features_section", @@ -474,6 +475,12 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" + }, + { + "default": "0", + "fieldname": "force_unique_journal_in_transaction", + "fieldtype": "Check", + "label": "Prevent using multiple journals in a single transaction" } ], "icon": "uil uil-setting", @@ -481,7 +488,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-01-30 14:04:26.553554", + "modified": "2024-02-16 12:01:51.905532", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 30e618c6f50..49579c51526 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1183,10 +1183,14 @@ class JournalEntry(AccountsController): self.db_set("unreconciled_amount", abs(amount), update_modified=False) def validate_accounting_journals(self): + raise_exception = frappe.db.get_single_value( + "Accounts Settings", "force_unique_journal_in_transaction" + ) accounting_journals = set(account.accounting_journal for account in self.accounts) if len(accounting_journals) > 1: frappe.msgprint( - _("Your entries are linked to different journals. Please make sure it is correct.") + _("Your entries are linked to different journals. Please make sure it is correct."), + raise_exception=raise_exception, ) def set_advance_for_down_payment_entries(self): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index b2d27b27341..1ccdb31a938 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -371,14 +371,37 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"]) accounting_number = get_accounting_number(gl_map[0]) + for entry in gl_map: + # @dokos: Make sure that every entry has successive numbers entry["accounting_entry_number"] = accounting_number + + # @dokos: Add an accounting journal to the entry if not entry.get("accounting_journal"): get_accounting_journal(entry) validate_allowed_dimensions(entry, dimension_filter_map) make_entry(entry, adv_adj, update_outstanding, from_repost) + # @dokos: Check if there are multiple accounting journals in the same transaction + validate_unique_accounting_journal(gl_map) + + +def validate_unique_accounting_journal(gl_map): + if not frappe.db.get_single_value("Accounts Settings", "force_unique_journal_in_transaction"): + return + + accounting_journals = set() + for entry in gl_map: + accounting_journals.add(entry.get("accounting_journal") or "") + + if len(accounting_journals) > 1: + frappe.throw( + _("Multiple accounting journals found in the same transaction: {0}").format( + ", ".join(sorted(accounting_journals)) + ) + ) + def get_accounting_number(doc: dict) -> str: return make_autoname(_("AEN-.fiscal_year.-.#########"), "GL Entry", doc) @@ -398,11 +421,10 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): validate_expense_against_budget(args) -def get_accounting_journal(entry): - applicable_rules = [] - rules = frappe.get_all( +def get_accounting_journal_rules(company: str): + return frappe.get_all( "Accounting Journal", - filters={"company": entry.get("company"), "disabled": 0}, + filters={"company": company, "disabled": 0}, fields=[ "name", "type", @@ -412,6 +434,11 @@ def get_accounting_journal(entry): ], ) + +def get_accounting_journal(entry): + applicable_rules = [] + rules = get_accounting_journal_rules(entry.get("company")) + applicable_rules = [ rule for rule in rules if (rule.account in (entry.account, entry.against, None)) ] diff --git a/erpnext/regional/france/setup.py b/erpnext/regional/france/setup.py index 0993a6c7ad0..63ce23287de 100644 --- a/erpnext/regional/france/setup.py +++ b/erpnext/regional/france/setup.py @@ -10,7 +10,7 @@ def setup(company=None, patch=True): setup_company_independent_fixtures() if not patch: make_fixtures(company) - set_accounting_journal_as_mandatory() + setup_accounts_settings() def setup_company_independent_fixtures(): @@ -202,8 +202,9 @@ def default_accounts_mapping(accounts, company): } -def set_accounting_journal_as_mandatory(): +def setup_accounts_settings(): frappe.db.set_single_value("Accounts Settings", "mandatory_accounting_journal", 1) + frappe.db.set_single_value("Accounts Settings", "force_unique_journal_in_transaction", 1) def update_regional_tax_settings(country, company): diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 96b6f4fc46e..cc6307be41e 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -6890,6 +6890,7 @@ Multiple Loyalty Programs found for Customer {}. Please select manually.,Plusieu Multiple Tier Program,Programme à plusieurs échelons, Multiple Variants,Variantes multiples, Multiple Warehouse Accounts,Comptes d'entrepôt multiples, +Multiple accounting journals found in the same transaction: {0},Impossible d'utiliser des journaux comptables différents dans la même transaction : {0}, Multiple default mode of payment is not allowed,De multiples modes de paiement par défaut ne sont pas autorisés, Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year,Plusieurs Exercices existent pour la date {0}. Veuillez définir la société dans l'Exercice, Multiple items cannot be marked as finished item,Plusieurs articles ne peuvent pas être notés comme des articles finis., @@ -8539,6 +8540,7 @@ Prevdoc DocType,DocPréc DocType, Prevent POs,Interdire les Bons de Commande d'Achat, Prevent Purchase Orders,Interdire les Bons de Commande d'Achat, Prevent RFQs,Interdire les Appels d'Offres, +Prevent using multiple journals in a single transaction,Empêcher l'utilisation de journaux différents dans une même transaction, Preventive Action,Action préventive, Preventive Maintenance,Maintenance préventive, Preventive,Préventive, -- GitLab