From ea0d53e2f302dd0a1972830e42a64f7a09db521c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 14 Apr 2026 14:59:01 +0530 Subject: [PATCH] fix: add drop ship logic in gross profit report (#54220) --- .../report/gross_profit/gross_profit.py | 30 +++++++++++++++++-- .../report/gross_profit/test_gross_profit.py | 25 ++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index a53c2134e3f..518b49f0ea5 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -578,7 +578,11 @@ class GrossProfitGenerator: # get buying rate if flt(row.qty): - row.buying_rate = flt(row.buying_amount / flt(row.qty), self.float_precision) + row.buying_rate = ( + flt(row.buying_amount / flt(row.qty), self.float_precision) + if not row.delivered_by_supplier + else None + ) row.base_rate = flt(row.base_amount / flt(row.qty), self.float_precision) else: if self.is_not_invoice_row(row): @@ -630,7 +634,8 @@ class GrossProfitGenerator: returned_item_row.qty += row.qty returned_item_row.base_amount += row.base_amount - row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) + if not row.delivered_by_supplier: + row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) def get_average_rate_based_on_group_by(self): for key in list(self.grouped): @@ -799,6 +804,26 @@ class GrossProfitGenerator: return self.calculate_buying_amount_from_sle( row, my_sle, parenttype, parent, item_row, item_code ) + elif ( + row.delivered_by_supplier + and row.so_detail + and ( + po_details := frappe.get_all( + "Purchase Order Item", + filters={"sales_order_item": row.so_detail, "docstatus": 1}, + pluck="name", + ) + ) + ): + from frappe.query_builder.functions import Sum + + table = frappe.qb.DocType("Purchase Invoice Item") + query = ( + frappe.qb.from_(table) + .select(Sum(table.stock_qty * table.base_net_rate)) + .where((table.po_detail.isin(po_details)) & (table.docstatus == 1)) + ) + return flt(query.run()[0][0]) elif row.sales_order and row.so_detail: incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code) if incoming_amount: @@ -951,6 +976,7 @@ class GrossProfitGenerator: SalesInvoice.is_return, SalesInvoiceItem.cost_center, SalesInvoiceItem.serial_and_batch_bundle, + SalesInvoiceItem.delivered_by_supplier, ) if self.filters.group_by == "Sales Person": diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index ac1ca89b5e4..74f4a0eba6b 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -731,6 +731,31 @@ class TestGrossProfit(ERPNextTestSuite): self.assertEqual(total[7], 1000.0) self.assertEqual(total[8], 100.0) + def test_drop_ship(self): + from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice + from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order, make_sales_invoice + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item("_Test Drop Ship Item", properties={"is_stock_item": 1, "delivered_by_supplier": 1}) + + so = make_sales_order(item=item.name, qty=10, rate=100) + po = make_purchase_order(so.name, selected_items=[so.items[0]])[0] + po.items[0].rate = 80 + po.supplier = "_Test Supplier" + po.submit() + make_purchase_invoice(po.name).submit() + si = make_sales_invoice(so.name).submit() + + filters = frappe._dict( + company=si.company, from_date=si.posting_date, to_date=si.posting_date, group_by="Invoice" + ) + + _, data = execute(filters=filters) + self.assertEqual(data[1].buying_amount, 800) + self.assertIsNone(data[1].buying_rate) + self.assertEqual(data[1]["gross_profit_%"], 20) + def make_sales_person(sales_person_name="_Test Sales Person"): if not frappe.db.exists("Sales Person", {"sales_person_name": sales_person_name}):