mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
fix(stock): make Available Batch report GROUP BY Postgres-valid
Both get_batchwise_data_from_stock_ledger and get_batchwise_data_from_serial_batch_bundle select Batch columns (expiry_date, and item_name when show_item_name is set) while grouping only by Stock Ledger Entry columns. MariaDB arbitrary-picks the Batch columns; Postgres rejects the query with "column ... must appear in the GROUP BY clause". Add the Batch PK (batch.name) to both GROUP BYs. batch.name is 1:1 with the grouped batch_no (the join condition), so groups are unchanged and the result is identical on MariaDB. The serial-batch-bundle query additionally grouped by ch_table.warehouse while selecting table.warehouse; group by the selected (SLE) warehouse so the grouped and selected columns match (also required by Postgres). Adds a test (no test file existed) that receives batch stock and asserts the report lists it with the correct balance, exercising the GROUP BY on both engines (with show_item_name set to force the extra Batch column). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -106,7 +106,9 @@ def get_batchwise_data_from_stock_ledger(filters):
|
||||
Sum(table.actual_qty).as_("balance_qty"),
|
||||
)
|
||||
.where(table.is_cancelled == 0)
|
||||
.groupby(table.batch_no, table.item_code, table.warehouse)
|
||||
# batch.expiry_date comes from the Batch table; postgres requires its PK in the GROUP BY for
|
||||
# it to be selectable. batch.name is 1:1 with the grouped batch_no, so groups are unchanged.
|
||||
.groupby(table.batch_no, table.item_code, table.warehouse, batch.name)
|
||||
)
|
||||
|
||||
query = get_query_based_on_filters(query, batch, table, filters)
|
||||
@@ -137,7 +139,10 @@ def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters):
|
||||
Sum(ch_table.qty).as_("balance_qty"),
|
||||
)
|
||||
.where((table.is_cancelled == 0) & (table.docstatus == 1))
|
||||
.groupby(ch_table.batch_no, table.item_code, ch_table.warehouse)
|
||||
# Group by the same (SLE) warehouse that is selected -- the original grouped by
|
||||
# ch_table.warehouse while selecting table.warehouse, which postgres rejects. Also group by
|
||||
# the Batch PK so batch.expiry_date is selectable (1:1 with the grouped batch_no).
|
||||
.groupby(ch_table.batch_no, table.item_code, table.warehouse, batch.name)
|
||||
)
|
||||
|
||||
query = get_query_based_on_filters(query, batch, table, filters)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.utils import today
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestAvailableBatchReport(ERPNextTestSuite):
|
||||
@staticmethod
|
||||
def _cancel_and_delete_stock_entry(name):
|
||||
if not frappe.db.exists("Stock Entry", name):
|
||||
return
|
||||
doc = frappe.get_doc("Stock Entry", name)
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
frappe.delete_doc("Stock Entry", name, force=1)
|
||||
|
||||
def test_report_runs_and_lists_batch_qty(self):
|
||||
# The report selects Batch columns (expiry_date, and item_name when show_item_name is set)
|
||||
# while grouping by SLE columns; the Batch PK must be in the GROUP BY for the report to run
|
||||
# on Postgres. show_item_name=1 forces the extra Batch column to be selected.
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.available_batch_report.available_batch_report import execute
|
||||
|
||||
item = make_item(
|
||||
"_Test Available Batch Report Item",
|
||||
{"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
|
||||
).name
|
||||
se = make_stock_entry(
|
||||
item_code=item, target="_Test Warehouse - _TC", qty=7, basic_rate=10, purpose="Material Receipt"
|
||||
)
|
||||
# make_item is idempotent (returns the existing item), but each receipt stacks a new batch,
|
||||
# so cancel+delete the stock entry to keep repeated runs clean.
|
||||
self.addCleanup(self._cancel_and_delete_stock_entry, se.name)
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
filters = frappe._dict(to_date=today(), item_code=item, show_item_name=1)
|
||||
columns, data = execute(filters)
|
||||
|
||||
self.assertTrue(columns)
|
||||
row = next((d for d in data if d.batch_no == batch_no), None)
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row.balance_qty, 7)
|
||||
Reference in New Issue
Block a user