Repository: frappe/erpnext
Pull request: fix: Gross Profit Report with Correct Totals and Gross Margin
Support ticket: [Support Ticket  - 30225](https://support.frappe.io/helpdesk/tickets/30225)
- closes : #35942

Before : 
The add_total_row option was checked, causing the subtotals to combine, which resulted in an incorrect **Selling Amount**, **Buying Amount**,  **Gross Profit**, **Gross Profit Percentage**.

<img width="847" alt="Screenshot 2025-01-27 at 6 55 10 PM" src="https://github.com/user-attachments/assets/374c917f-727b-4bdf-a93b-2dc548a440a9" />


After : 
The total Selling Amount and Buying Amount are now calculated correctly.
The total **Gross Profit** and **Gross Profit Percentage** is calculated using a formula.
The add_total_row option was disabled using a patch.

<img width="847" alt="Screenshot 2025-01-27 at 9 58 45 PM" src="https://github.com/user-attachments/assets/45151881-71a0-4ab1-a55b-15f42fc99321" />





Problem statement:
Gross Profit Report error
### Information about bug

Using the Gross Profit report grouped by invoices shows incorrect gross profit average figure in the 'total' row. Also when changing the grouping to item code, it again gives another average figure which is also wrong. 

### Module

accounts

### Version

ERPNext: v14.28.0 (HEAD)
Frappe Framework: v14.40.1 (HEAD)
India Compliance: v14.10.2 (HEAD)

### Installation method

None

### Relevant log output / Stack trace / Full Error Message.

_No response_

Hints from issue discussion:
I think I figured out the error. In each grouping the figure changes except in monthly when it gives the right figure. 

So, it takes the average of the gross profit% column rather than calculate using the totals. That has to be fixed as its a fundamental error in giving averages which have got to be on the totals (i.e. weighted).
Hoping for a resolution to this fundamental issue.
Can confirm, still an issue in v15
```
Frappe CRM: v1.19.0
South Africa Customisations: v0.1.6
ERPNext: v15.34.2
Frappe Framework: v15.40.3
Helpdesk: v0.10.0
Frappe HR: v15.28.3
```
https://github.com/frappe/erpnext/issues/33911

Commit message: test: added test cases for total

Patch to review:
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index 0730ffd77e5b..dfb7a991e3ea 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -1,5 +1,5 @@
 {
- "add_total_row": 1,
+ "add_total_row": 0,
  "columns": [],
  "creation": "2013-02-25 17:03:34",
  "disable_prepared_report": 0,
@@ -9,7 +9,7 @@
  "filters": [],
  "idx": 3,
  "is_standard": "Yes",
- "modified": "2022-02-11 10:18:36.956558",
+ "modified": "2025-01-27 18:40:24.493829",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Gross Profit",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 5df37603b9a9..7afbf2d0dbb4 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -178,7 +178,14 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
 	# removing Item Code and Item Name columns
 	del columns[4:6]
 
+	total_base_amount = 0
+	total_buying_amount = 0
+
 	for src in gross_profit_data.si_list:
+		if src.indent == 1:
+			total_base_amount += src.base_amount or 0.0
+			total_buying_amount += src.buying_amount or 0.0
+
 		row = frappe._dict()
 		row.indent = src.indent
 		row.parent_invoice = src.parent_invoice
@@ -189,6 +196,24 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
 
 		data.append(row)
 
+	total_gross_profit = total_base_amount - total_buying_amount
+	data.append(
+		frappe._dict(
+			{
+				"sales_invoice": "Total",
+				"qty": None,
+				"avg._selling_rate": None,
+				"valuation_rate": None,
+				"selling_amount": total_base_amount,
+				"buying_amount": total_buying_amount,
+				"gross_profit": total_gross_profit,
+				"gross_profit_%": flt((total_gross_profit / total_base_amount) * 100.0, 3)
+				if total_base_amount
+				else 0,
+			}
+		)
+	)
+
 
 def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
 	for src in gross_profit_data.grouped_data:
diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py
index 6d060db1d155..b483555d701b 100644
--- a/erpnext/accounts/report/gross_profit/test_gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py
@@ -612,3 +612,33 @@ def test_valuation_rate_without_previous_sle(self):
 		item_from_sinv2 = [x for x in data if x.parent_invoice == sinv2.name]
 		self.assertEqual(len(item_from_sinv2), 1)
 		self.assertEqual(1800, item_from_sinv2[0].valuation_rate)
+
+	def test_gross_profit_groupby_invoices(self):
+		create_sales_invoice(
+			qty=1,
+			rate=100,
+			company=self.company,
+			customer=self.customer,
+			item_code=self.item,
+			item_name=self.item,
+			cost_center=self.cost_center,
+			warehouse=self.warehouse,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			update_stock=0,
+			currency="INR",
+			income_account=self.income_account,
+			expense_account=self.expense_account,
+		)
+
+		filters = frappe._dict(
+			company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+		)
+
+		_, data = execute(filters=filters)
+		total = data[-1]
+
+		self.assertEqual(total.selling_amount, 100.0)
+		self.assertEqual(total.buying_amount, 0.0)
+		self.assertEqual(total.gross_profit, 100.0)
+		self.assertEqual(total.get("gross_profit_%"), 100.0)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 098000b0b03b..9b2688020ad9 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -399,3 +399,4 @@ erpnext.patches.v15_0.rename_manufacturing_settings_field
 erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
 erpnext.patches.v15_0.sync_auto_reconcile_config
 execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
+erpnext.patches.v14_0.disable_add_row_in_gross_profit
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/disable_add_row_in_gross_profit.py b/erpnext/patches/v14_0/disable_add_row_in_gross_profit.py
new file mode 100644
index 000000000000..d95503bef0aa
--- /dev/null
+++ b/erpnext/patches/v14_0/disable_add_row_in_gross_profit.py
@@ -0,0 +1,5 @@
+import frappe
+
+
+def execute():
+	frappe.db.set_value("Report", "Gross Profit", "add_total_row", 0)


Code review comments:
diff_hunk: @@ -189,6 +196,24 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
 
 		data.append(row)
 
+	total_gross_profit = total_base_amount - total_buying_amount
+	data.append(
+		frappe._dict(
+			{
+				"sales_invoice": "Total",
+				"qty": None,
+				"avg._selling_rate": None,
+				"valuation_rate": None,
+				"selling_amount": total_base_amount,
+				"buying_amount": total_buying_amount,
+				"gross_profit": total_gross_profit,
+				"gross_profit_%": flt((total_gross_profit / total_base_amount) * 100.0, 3)
path: erpnext/accounts/report/gross_profit/gross_profit.py
text: @user1:
 Use `currency_precision` instead of hardcoding it.