From 97863344e43c38505fb005b4c3dca9fb8851ec20 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 7 Nov 2025 16:03:04 +0530 Subject: [PATCH 1/3] feat(pos): prevent disabling POS Profile when open POS sessions exist --- .../accounts/doctype/pos_profile/pos_profile.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 0cf816f350..b1a2756e6a 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -75,6 +75,7 @@ class POSProfile(Document): # end: auto-generated types def validate(self): + self.validate_disabled() self.validate_default_profile() self.validate_all_link_fields() self.validate_duplicate_groups() @@ -99,6 +100,21 @@ class POSProfile(Document): title=_("Mandatory Accounting Dimension"), ) + def validate_disabled(self): + old_doc = self.get_doc_before_save() + + if ( + old_doc + and self.disabled + and old_doc.disabled != self.disabled + and frappe.db.exists("POS Opening Entry", {"pos_profile": self.name, "status": "Open"}) + ): + frappe.throw( + _("POS Profile {0} cannot be disabled as there are ongoing POS sessions.").format( + frappe.bold(self.name) + ) + ) + def validate_default_profile(self): for row in self.applicable_for_users: res = frappe.db.sql( -- GitLab From c29e28af221b71315436f6312f40d9a72d63c14f Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 8 Nov 2025 12:31:09 +0530 Subject: [PATCH 2/3] test: added test to validate disabled pos profile --- .../doctype/pos_profile/test_pos_profile.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 53fc64d317..b2b9070e64 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -4,6 +4,7 @@ import unittest import frappe from frappe.tests import IntegrationTestCase +from frappe.utils import cint from erpnext.accounts.doctype.pos_profile.pos_profile import ( get_child_nodes, @@ -39,6 +40,50 @@ class TestPOSProfile(IntegrationTestCase): frappe.db.sql("delete from `tabPOS Profile`") + def test_disabled_pos_profile_creation(self): + make_pos_profile(name="_Test POS Profile 001", disabled=1) + + pos_profile = frappe.get_doc("POS Profile", "_Test POS Profile 001") + + if pos_profile: + self.assertEqual(pos_profile.disabled, 1) + + def test_disabled_pos_profile_after_opening(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry + + test_user, pos_profile = init_user_and_profile() + + if pos_profile: + create_opening_entry(pos_profile, test_user.name) + self.assertEqual(pos_profile.disabled, 0) + + pos_profile.disabled = 1 + self.assertRaises(frappe.ValidationError, pos_profile.save) + + def test_disabled_pos_profile_after_completing_session(self): + from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( + make_closing_entry_from_opening, + ) + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import ( + create_opening_entry, + ) + + test_user, pos_profile = init_user_and_profile() + + if pos_profile: + opening_entry = create_opening_entry(pos_profile, test_user.name) + + closing_entry = make_closing_entry_from_opening(opening_entry) + closing_entry.submit() + + pos_profile.disabled = 1 + pos_profile.save() + pos_profile.reload() + + self.assertEqual(pos_profile.disabled, 1) + def get_customers_list(pos_profile=None): if pos_profile is None: @@ -118,6 +163,7 @@ def make_pos_profile(**args): "write_off_account": args.write_off_account or "_Test Write Off - _TC", "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC", "location": "Block 1" if not args.do_not_set_accounting_dimension else None, + "disabled": cint(args.disabled) or 0, } ) -- GitLab From c1e118ebd7b2b271aec7a8acf1236acf42eb7b8d Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Sat, 8 Nov 2025 12:31:40 +0530 Subject: [PATCH 3/3] fix: prevent pos opening entry creation for disabled pos profile --- .../doctype/pos_opening_entry/pos_opening_entry.py | 14 ++++++++++++-- .../pos_opening_entry/test_pos_opening_entry.py | 6 ++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py index 1b09704f3a..31b6eaa296 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py @@ -18,9 +18,19 @@ class POSOpeningEntry(StatusUpdater): self.set_status() def validate_pos_profile_and_cashier(self): - if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"): + if not frappe.db.exists("POS Profile", self.pos_profile): + frappe.throw(_("POS Profile {} does not exist.").format(self.pos_profile)) + + pos_profile_company, pos_profile_disabled = frappe.db.get_value( + "POS Profile", self.pos_profile, ["company", "disabled"] + ) + + if pos_profile_disabled: + frappe.throw(_("POS Profile {} is disabled.").format(frappe.bold(self.pos_profile))) + + if self.company != pos_profile_company: frappe.throw( - _("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company) + _("POS Profile {} does not belong to company {}").format(self.pos_profile, self.company) ) if not cint(frappe.db.get_value("User", self.user, "enabled")): diff --git a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py index e371e5408f..2b23f94932 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py @@ -40,6 +40,12 @@ class TestPOSOpeningEntry(IntegrationTestCase): self.assertEqual(opening_entry.status, "Open") self.assertNotEqual(opening_entry.docstatus, 0) + def test_pos_opening_entry_on_disabled_pos(self): + test_user, pos_profile = self.init_user_and_profile(disabled=1) + + with self.assertRaises(frappe.ValidationError): + create_opening_entry(pos_profile, test_user.name) + def test_multiple_pos_opening_entries_for_same_pos_profile(self): test_user, pos_profile = self.init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) -- GitLab