From 7f47c218ceff59b68b966484983254191d4b0ac1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 18 Jun 2026 13:43:12 +0530 Subject: [PATCH] test(sales_invoice): characterize POSService default and mode-of-payment logic Pin the behaviour of POSService.set_pos_fields (POS-profile default resolution and the for_validate guard) and the mode-of-payment query helpers before refactoring them. --- .../sales_invoice/test_sales_invoice.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e168a9e9df1..aa1a969b59b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -20,6 +20,12 @@ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( unlink_payment_on_cancel_of_invoice, ) from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_transaction +from erpnext.accounts.doctype.sales_invoice.services.pos import ( + POSService, + get_all_mode_of_payments, + get_mode_of_payment_info, + get_mode_of_payments_info, +) from erpnext.accounts.utils import PaymentEntryUnlinkError from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.assets.doctype.asset.test_asset import create_asset @@ -1346,6 +1352,101 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(pos.grand_total, 100.0) self.assertEqual(pos.write_off_amount, 0) + def test_set_pos_fields_populates_invoice_from_profile(self): + terms = frappe.db.exists("Terms and Conditions", "_Test POS Terms") + if not terms: + terms = ( + frappe.get_doc( + { + "doctype": "Terms and Conditions", + "title": "_Test POS Terms", + "terms": "POS terms and conditions", + "selling": 1, + } + ) + .insert() + .name + ) + + profile = make_pos_profile() + profile.customer = "_Test Customer" + profile.tax_category = "_Test Tax Category 1" + profile.account_for_change_amount = "Cash - _TC" + profile.ignore_pricing_rule = 1 + profile.update_stock = 1 + profile.apply_discount_on = "Grand Total" + profile.tc_name = terms + profile.taxes_and_charges = "_Test Sales Taxes and Charges Template - _TC" + profile.save() + + si = create_sales_invoice(do_not_save=True) + si.is_pos = 1 + si.pos_profile = profile.name + si.customer = None + si.taxes = [] + + POSService(si).set_pos_fields(for_validate=False) + + self.assertEqual(si.customer, "_Test Customer") + self.assertEqual(si.tax_category, "_Test Tax Category 1") + self.assertEqual(si.ignore_pricing_rule, 1) + self.assertEqual(si.account_for_change_amount, "Cash - _TC") + self.assertEqual(si.taxes_and_charges, "_Test Sales Taxes and Charges Template - _TC") + self.assertEqual(si.apply_discount_on, "Grand Total") + self.assertEqual(si.update_stock, 1) + self.assertEqual(si.terms, "POS terms and conditions") + self.assertTrue(si.get("payments")) + self.assertTrue(si.get("taxes")) + + def test_set_pos_fields_for_validate_preserves_existing_values(self): + profile = make_pos_profile() + profile.tax_category = "_Test Tax Category 1" + profile.save() + + si = create_sales_invoice(do_not_save=True) + si.is_pos = 1 + si.pos_profile = profile.name + si.apply_discount_on = "Net Total" + existing_customer = si.customer + + POSService(si).set_pos_fields(for_validate=True) + + # for_validate must not overwrite a field the user already set + self.assertEqual(si.apply_discount_on, "Net Total") + # for_validate skips mode-of-payment fetch and profile-driven customer/tax_category + self.assertFalse(si.get("payments")) + self.assertEqual(si.customer, existing_customer) + self.assertFalse(si.tax_category) + + def test_set_pos_fields_uses_profile_price_list_without_customer(self): + profile = make_pos_profile(selling_price_list="_Test Price List") + profile.customer = None + profile.save() + + si = create_sales_invoice(do_not_save=True) + si.is_pos = 1 + si.pos_profile = profile.name + si.customer = None + + POSService(si).set_pos_fields(for_validate=False) + + self.assertEqual(si.selling_price_list, "_Test Price List") + + def test_pos_service_mode_of_payment_queries(self): + make_pos_profile() # ensures a Cash mode-of-payment account for _Test Company + si = create_sales_invoice(do_not_save=True) + + single = get_mode_of_payment_info("Cash", "_Test Company") + self.assertTrue(single) + self.assertEqual(single[0].parent, "Cash") + + all_modes = get_all_mode_of_payments(si) + self.assertTrue(any(row.parent == "Cash" for row in all_modes)) + + grouped = get_mode_of_payments_info(["Cash"], "_Test Company") + self.assertIn("Cash", grouped) + self.assertEqual(grouped["Cash"].mop, "Cash") + def test_auto_write_off_amount(self): make_pos_profile( company="_Test Company with perpetual inventory",