fix(hold-history): align KPI cards with trend data, improve filters and UX across pages

Use Daily Trend as single source of truth for On Hold and New Hold KPI cards
instead of separate snapshot SQL queries, eliminating value mismatches. Fix
timezone bug in default date range (toISOString UTC offset), add 1st-of-month
fallback to previous month, replace Hold Type radio buttons with select dropdown,
reorder/relabel summary cards with 累計 prefix, add job-query MultiSelect for
equipment filter, and fix heatmap chart X-axis overlap with visualMap legend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-12 08:13:07 +08:00
parent 35d83d424c
commit 8550f6dc3e
13 changed files with 100 additions and 144 deletions

View File

@@ -41,9 +41,8 @@ class TestHoldHistoryPageRoute(TestHoldHistoryRoutesBase):
class TestHoldHistoryTrendRoute(TestHoldHistoryRoutesBase):
"""Test GET /api/hold-history/trend endpoint."""
@patch('mes_dashboard.routes.hold_history_routes.get_still_on_hold_count')
@patch('mes_dashboard.routes.hold_history_routes.get_hold_history_trend')
def test_trend_passes_date_range(self, mock_trend, mock_count):
def test_trend_passes_date_range(self, mock_trend):
mock_trend.return_value = {
'days': [
{
@@ -54,16 +53,14 @@ class TestHoldHistoryTrendRoute(TestHoldHistoryRoutesBase):
}
]
}
mock_count.return_value = {'quality': 4, 'non_quality': 2, 'all': 6}
response = self.client.get('/api/hold-history/trend?start_date=2026-02-01&end_date=2026-02-07')
payload = json.loads(response.data)
self.assertEqual(response.status_code, 200)
self.assertTrue(payload['success'])
self.assertEqual(payload['data']['stillOnHoldCount'], {'quality': 4, 'non_quality': 2, 'all': 6})
self.assertIn('days', payload['data'])
mock_trend.assert_called_once_with('2026-02-01', '2026-02-07')
mock_count.assert_called_once_with()
def test_trend_invalid_date_returns_400(self):
response = self.client.get('/api/hold-history/trend?start_date=2026/02/01&end_date=2026-02-07')
@@ -72,10 +69,9 @@ class TestHoldHistoryTrendRoute(TestHoldHistoryRoutesBase):
self.assertEqual(response.status_code, 400)
self.assertFalse(payload['success'])
@patch('mes_dashboard.routes.hold_history_routes.get_still_on_hold_count')
@patch('mes_dashboard.routes.hold_history_routes.get_hold_history_trend')
@patch('mes_dashboard.core.rate_limit.check_and_record', return_value=(True, 8))
def test_trend_rate_limited_returns_429(self, _mock_limit, mock_service, _mock_count):
def test_trend_rate_limited_returns_429(self, _mock_limit, mock_service):
response = self.client.get('/api/hold-history/trend?start_date=2026-02-01&end_date=2026-02-07')
payload = json.loads(response.data)

View File

@@ -339,28 +339,6 @@ class TestHoldHistoryServiceFunctions(unittest.TestCase):
self.assertEqual(result['pagination']['total'], 3)
self.assertEqual(result['pagination']['totalPages'], 2)
@patch('mes_dashboard.services.hold_history_service.read_sql_df')
def test_still_on_hold_count_formats_response(self, mock_read_sql_df):
mock_read_sql_df.return_value = pd.DataFrame(
[{'QUALITY_COUNT': 4, 'NON_QUALITY_COUNT': 2, 'ALL_COUNT': 6}]
)
result = hold_history_service.get_still_on_hold_count()
self.assertIsNotNone(result)
self.assertEqual(result['quality'], 4)
self.assertEqual(result['non_quality'], 2)
self.assertEqual(result['all'], 6)
@patch('mes_dashboard.services.hold_history_service.read_sql_df')
def test_still_on_hold_count_empty_returns_zeros(self, mock_read_sql_df):
mock_read_sql_df.return_value = pd.DataFrame()
result = hold_history_service.get_still_on_hold_count()
self.assertIsNotNone(result)
self.assertEqual(result, {'quality': 0, 'non_quality': 0, 'all': 0})
def test_trend_sql_contains_shift_boundary_logic(self):
sql = hold_history_service._load_hold_history_sql('trend')