From baae9bfb2250367f5642ca4a966aa6d1fae054eb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 1 Jul 2026 22:48:35 +0530 Subject: [PATCH 1/3] test: add coverage for Production Analytics report --- .../test_production_analytics.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 erpnext/manufacturing/report/production_analytics/test_production_analytics.py diff --git a/erpnext/manufacturing/report/production_analytics/test_production_analytics.py b/erpnext/manufacturing/report/production_analytics/test_production_analytics.py new file mode 100644 index 00000000000..17a26ef06bd --- /dev/null +++ b/erpnext/manufacturing/report/production_analytics/test_production_analytics.py @@ -0,0 +1,60 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe.utils import get_first_day, get_last_day, today + +from erpnext.tests.utils import ERPNextTestSuite + + +class TestProductionAnalytics(ERPNextTestSuite): + def run_report(self, **extra): + from erpnext.manufacturing.report.production_analytics.production_analytics import execute + + filters = frappe._dict( + { + "company": "_Test Company", + "from_date": get_first_day(today()), + "to_date": get_last_day(today()), + "range": "Monthly", + } + ) + filters.update(extra) + columns, data, _msg, _chart = execute(filters) + return columns, data + + def get_period_count(self, columns, data, status, period_label): + """Return the count for a status row under the period column resolved by label.""" + period_fieldname = next(col["fieldname"] for col in columns if col.get("label") == period_label) + row = next(row for row in data if row["status"] == status) + return row[period_fieldname] + + def test_submitted_work_order_increments_status_count(self): + from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + + # The current month is the period a newly created Work Order falls into (bucketed by creation date). + cols_before, data_before = self.run_report() + period_label = cols_before[-1]["label"] + before = self.get_period_count(cols_before, data_before, "Not Started", period_label) + + wo = make_wo_order_test_record(production_item="_Test FG Item", qty=10, company="_Test Company") + self.assertEqual(wo.docstatus, 1) + # A freshly submitted Work Order with no material transfer has status "Not Started". + self.assertEqual(wo.status, "Not Started") + + cols_after, data_after = self.run_report() + after = self.get_period_count(cols_after, data_after, "Not Started", period_label) + + self.assertEqual(after, before + 1) + + def test_report_shape(self): + columns, data = self.run_report() + + # First column is the Status column, followed by one column per period. + self.assertEqual(columns[0]["fieldname"], "status") + self.assertGreaterEqual(len(columns), 2) + + # One row per known Work Order status. + statuses = {row["status"] for row in data} + for status in ("Not Started", "Overdue", "Pending", "Completed", "Closed", "Stopped"): + self.assertIn(status, statuses) From ece8c9538deaf448032f35e3fab2b146877ad556 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2026 14:06:37 +0530 Subject: [PATCH 2/3] test: locale-safe status match and stable period window in Production Analytics --- .../test_production_analytics.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/report/production_analytics/test_production_analytics.py b/erpnext/manufacturing/report/production_analytics/test_production_analytics.py index 17a26ef06bd..c02e94f249f 100644 --- a/erpnext/manufacturing/report/production_analytics/test_production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/test_production_analytics.py @@ -2,6 +2,7 @@ # See license.txt import frappe +from frappe import _ from frappe.utils import get_first_day, get_last_day, today from erpnext.tests.utils import ERPNextTestSuite @@ -26,14 +27,19 @@ class TestProductionAnalytics(ERPNextTestSuite): def get_period_count(self, columns, data, status, period_label): """Return the count for a status row under the period column resolved by label.""" period_fieldname = next(col["fieldname"] for col in columns if col.get("label") == period_label) - row = next(row for row in data if row["status"] == status) + # the report stores the translated status label, so translate before matching + row = next(row for row in data if row["status"] == _(status)) return row[period_fieldname] def test_submitted_work_order_increments_status_count(self): from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + # pin the reporting window once so both runs use the same period even if the + # test happens to straddle a month boundary + from_date, to_date = get_first_day(today()), get_last_day(today()) + # The current month is the period a newly created Work Order falls into (bucketed by creation date). - cols_before, data_before = self.run_report() + cols_before, data_before = self.run_report(from_date=from_date, to_date=to_date) period_label = cols_before[-1]["label"] before = self.get_period_count(cols_before, data_before, "Not Started", period_label) @@ -42,7 +48,7 @@ class TestProductionAnalytics(ERPNextTestSuite): # A freshly submitted Work Order with no material transfer has status "Not Started". self.assertEqual(wo.status, "Not Started") - cols_after, data_after = self.run_report() + cols_after, data_after = self.run_report(from_date=from_date, to_date=to_date) after = self.get_period_count(cols_after, data_after, "Not Started", period_label) self.assertEqual(after, before + 1) @@ -57,4 +63,4 @@ class TestProductionAnalytics(ERPNextTestSuite): # One row per known Work Order status. statuses = {row["status"] for row in data} for status in ("Not Started", "Overdue", "Pending", "Completed", "Closed", "Stopped"): - self.assertIn(status, statuses) + self.assertIn(_(status), statuses) From 0790d2e6df40e961719b48d1317abcab98d5ead8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2026 15:09:00 +0530 Subject: [PATCH 3/3] fix(manufacturing): include last-day records in Production Analytics `get_work_orders` bounded a BETWEEN on the datetime columns `creation` and `actual_end_date` with a bare date `to_date`, which MariaDB coerces to midnight. Work orders created after 00:00:00 on the period's last day were therefore dropped from the report (and made the new coverage test fail on month-end CI runs). Extend `to_date` to end of day. --- .../report/production_analytics/production_analytics.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 9da87022e46..2fbd42210af 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -4,7 +4,7 @@ import frappe from frappe import _, scrub -from frappe.utils import getdate, today +from frappe.utils import get_datetime, getdate, today from erpnext.stock.report.stock_analytics.stock_analytics import ( get_period, @@ -31,7 +31,9 @@ def get_columns(period_columns): def get_work_orders(filters): from_date = filters.get("from_date") - to_date = filters.get("to_date") + # `creation` and `actual_end_date` are datetime columns, so a bare date upper + # bound would coerce to midnight and drop records created later on the last day. + to_date = get_datetime(filters.get("to_date")).replace(hour=23, minute=59, second=59) WorkOrder = frappe.qb.DocType("Work Order")