Merge pull request #30097 from nextchamp-saqib/fix-pos-round-off

fix(pos): multiple pos round off cases
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index ddca68a..d4513c6 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -84,20 +84,12 @@
 		sales_invoice.set_posting_time = 1
 		sales_invoice.posting_date = getdate(self.posting_date)
 		sales_invoice.save()
-		self.write_off_fractional_amount(sales_invoice, data)
 		sales_invoice.submit()
 
 		self.consolidated_invoice = sales_invoice.name
 
 		return sales_invoice.name
 
-	def write_off_fractional_amount(self, invoice, data):
-		pos_invoice_grand_total = sum(d.grand_total for d in data)
-
-		if abs(pos_invoice_grand_total - invoice.grand_total) < 1:
-			invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total)
-			invoice.save()
-
 	def process_merging_into_credit_note(self, data):
 		credit_note = self.get_new_sales_invoice()
 		credit_note.is_return = 1
@@ -110,7 +102,6 @@
 		# TODO: return could be against multiple sales invoice which could also have been consolidated?
 		# credit_note.return_against = self.consolidated_invoice
 		credit_note.save()
-		self.write_off_fractional_amount(credit_note, data)
 		credit_note.submit()
 
 		self.consolidated_credit_note = credit_note.name
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index 5930aa0..89f7f18 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -5,6 +5,7 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import change_settings
 
 from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
 from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
@@ -280,3 +281,100 @@
 			frappe.set_user("Administrator")
 			frappe.db.sql("delete from `tabPOS Profile`")
 			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	@change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3})
+	def test_consolidation_round_off_error_3(self):
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			make_stock_entry(
+				to_warehouse="_Test Warehouse - _TC",
+				item_code="_Test Item",
+				rate=8000,
+				qty=10,
+			)
+			init_user_and_profile()
+
+			item_rates = [69, 59, 29]
+			for i in [1, 2]:
+				inv = create_pos_invoice(is_return=1, do_not_save=1)
+				inv.items = []
+				for rate in item_rates:
+					inv.append("items", {
+						"item_code": "_Test Item",
+						"warehouse": "_Test Warehouse - _TC",
+						"qty": -1,
+						"rate": rate,
+						"income_account": "Sales - _TC",
+						"expense_account": "Cost of Goods Sold - _TC",
+						"cost_center": "_Test Cost Center - _TC",
+					})
+				inv.append("taxes", {
+					"account_head": "_Test Account VAT - _TC",
+					"charge_type": "On Net Total",
+					"cost_center": "_Test Cost Center - _TC",
+					"description": "VAT",
+					"doctype": "Sales Taxes and Charges",
+					"rate": 15,
+					"included_in_print_rate": 1
+				})
+				inv.payments = []
+				inv.append('payments', {
+					'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157
+				})
+				inv.paid_amount = -157
+				inv.save()
+				inv.submit()
+
+			consolidate_pos_invoices()
+
+			inv.load_from_db()
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			self.assertEqual(consolidated_invoice.status, 'Return')
+			self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	def test_consolidation_rounding_adjustment(self):
+		'''
+		Test if the rounding adjustment is calculated correctly
+		'''
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			make_stock_entry(
+				to_warehouse="_Test Warehouse - _TC",
+				item_code="_Test Item",
+				rate=8000,
+				qty=10,
+			)
+
+			init_user_and_profile()
+
+			inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
+			inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70
+			})
+			inv.insert()
+			inv.submit()
+
+			inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
+			inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60
+			})
+			inv2.insert()
+			inv2.submit()
+
+			consolidate_pos_invoices()
+
+			inv.load_from_db()
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index b894f90..573da27 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -263,6 +263,9 @@
 		self.process_common_party_accounting()
 
 	def validate_pos_return(self):
+		if self.is_consolidated:
+			# pos return is already validated in pos invoice
+			return
 
 		if self.is_pos and self.is_return:
 			total_amount_in_payments = 0
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index dc58e11..a1bb667 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -277,7 +277,8 @@
 			shipping_rule.apply(self.doc)
 
 	def calculate_taxes(self):
-		if not self.doc.get('is_consolidated'):
+		rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
+		if not rounding_adjustment_computed:
 			self.doc.rounding_adjustment = 0
 
 		# maintain actual tax rate based on idx
@@ -333,7 +334,7 @@
 					if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
 						and self.doc.discount_amount \
 						and self.doc.apply_discount_on == "Grand Total" \
-						and not self.doc.get('is_consolidated'):
+						and not rounding_adjustment_computed:
 							self.doc.rounding_adjustment = flt(self.doc.grand_total
 								- flt(self.doc.discount_amount) - tax.total,
 								self.doc.precision("rounding_adjustment"))
@@ -472,20 +473,22 @@
 					self.doc.total_net_weight += d.total_weight
 
 	def set_rounded_total(self):
-		if not self.doc.get('is_consolidated'):
-			if self.doc.meta.get_field("rounded_total"):
-				if self.doc.is_rounded_total_disabled():
-					self.doc.rounded_total = self.doc.base_rounded_total = 0
-					return
+		if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
+			return
 
-				self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
-					self.doc.currency, self.doc.precision("rounded_total"))
+		if self.doc.meta.get_field("rounded_total"):
+			if self.doc.is_rounded_total_disabled():
+				self.doc.rounded_total = self.doc.base_rounded_total = 0
+				return
 
-				#if print_in_rate is set, we would have already calculated rounding adjustment
-				self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
-					self.doc.precision("rounding_adjustment"))
+			self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
+				self.doc.currency, self.doc.precision("rounded_total"))
 
-				self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
+			#if print_in_rate is set, we would have already calculated rounding adjustment
+			self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
+				self.doc.precision("rounding_adjustment"))
+
+			self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
 
 	def _cleanup(self):
 		if not self.doc.get('is_consolidated'):