fix(query-tool): show hold lot id via containername and align export

This commit is contained in:
egg
2026-02-22 18:32:07 +08:00
parent bfec6b2293
commit 3dc7886c90
6 changed files with 146 additions and 26 deletions

View File

@@ -111,6 +111,11 @@ const activeColumnLabels = computed(() => {
CONTAINERNAME: 'LOT ID',
};
}
if (props.activeSubTab === 'holds') {
return {
CONTAINERNAME: 'LOT ID',
};
}
return {};
});
@@ -118,6 +123,9 @@ const activeHiddenColumns = computed(() => {
if (props.activeSubTab === 'materials') {
return ['CONTAINERID', 'WORKCENTER_GROUP'];
}
if (props.activeSubTab === 'holds') {
return ['CONTAINERID'];
}
return [];
});
@@ -134,6 +142,23 @@ const activeColumnOrder = computed(() => {
'TXNDATE',
];
}
if (props.activeSubTab === 'holds') {
return [
'CONTAINERNAME',
'WORKCENTERNAME',
'HOLDTXNDATE',
'RELEASETXNDATE',
'HOLD_STATUS',
'HOLD_HOURS',
'HOLDREASONNAME',
'HOLDCOMMENTS',
'HOLDEMP',
'HOLDEMPDEPTNAME',
'RELEASEEMP',
'RELEASECOMMENTS',
'NCRID',
];
}
return [];
});

View File

@@ -104,6 +104,29 @@ def _format_lot_materials_export_rows(rows):
return normalized_rows
def _format_lot_holds_export_rows(rows):
"""Normalize LOT hold export columns for UI/CSV consistency."""
normalized_rows = []
for row in rows or []:
lot_id = row.get('CONTAINERNAME') or row.get('CONTAINERID') or ''
normalized_rows.append({
'LOT ID': lot_id,
'WORKCENTERNAME': row.get('WORKCENTERNAME', ''),
'HOLDTXNDATE': row.get('HOLDTXNDATE', ''),
'RELEASETXNDATE': row.get('RELEASETXNDATE', ''),
'HOLD_STATUS': row.get('HOLD_STATUS', ''),
'HOLD_HOURS': row.get('HOLD_HOURS', ''),
'HOLDREASONNAME': row.get('HOLDREASONNAME', ''),
'HOLDCOMMENTS': row.get('HOLDCOMMENTS', ''),
'HOLDEMP': row.get('HOLDEMP', ''),
'HOLDEMPDEPTNAME': row.get('HOLDEMPDEPTNAME', ''),
'RELEASEEMP': row.get('RELEASEEMP', ''),
'RELEASECOMMENTS': row.get('RELEASECOMMENTS', ''),
'NCRID': row.get('NCRID', ''),
})
return normalized_rows
# ============================================================
# Page Route
# ============================================================
@@ -613,6 +636,8 @@ def export_csv():
if export_type == 'lot_materials':
export_data = _format_lot_materials_export_rows(export_data)
elif export_type == 'lot_holds':
export_data = _format_lot_holds_export_rows(export_data)
# Stream CSV response
return Response(

View File

@@ -49,8 +49,9 @@ _DOMAIN_SPECS: Dict[str, Dict[str, Any]] = {
"default_window": 60,
},
"holds": {
"filter_column": "CONTAINERID",
"filter_column": "h.CONTAINERID",
"cache_ttl": 180,
"schema_version": 2,
"bucket": "event-holds",
"max_env": "EVT_HOLDS_RATE_MAX_REQUESTS",
"window_env": "EVT_HOLDS_RATE_WINDOW_SECONDS",

View File

@@ -8,27 +8,30 @@
-- NULL RELEASETXNDATE means currently HOLD
SELECT
CONTAINERID,
WORKCENTERNAME,
HOLDTXNDATE,
HOLDEMP,
HOLDEMPDEPTNAME,
HOLDREASONNAME,
HOLDCOMMENTS,
RELEASETXNDATE,
RELEASEEMP,
RELEASECOMMENTS,
NCRID,
h.CONTAINERID,
NVL(TRIM(c.CONTAINERNAME), TRIM(h.CONTAINERID)) AS CONTAINERNAME,
h.WORKCENTERNAME,
h.HOLDTXNDATE,
h.HOLDEMP,
h.HOLDEMPDEPTNAME,
h.HOLDREASONNAME,
h.HOLDCOMMENTS,
h.RELEASETXNDATE,
h.RELEASEEMP,
h.RELEASECOMMENTS,
h.NCRID,
CASE
WHEN RELEASETXNDATE IS NULL THEN 'HOLD'
WHEN h.RELEASETXNDATE IS NULL THEN 'HOLD'
ELSE 'RELEASED'
END AS HOLD_STATUS,
CASE
WHEN RELEASETXNDATE IS NULL THEN
ROUND((SYSDATE - HOLDTXNDATE) * 24, 2)
WHEN h.RELEASETXNDATE IS NULL THEN
ROUND((SYSDATE - h.HOLDTXNDATE) * 24, 2)
ELSE
ROUND((RELEASETXNDATE - HOLDTXNDATE) * 24, 2)
ROUND((h.RELEASETXNDATE - h.HOLDTXNDATE) * 24, 2)
END AS HOLD_HOURS
FROM DWH.DW_MES_HOLDRELEASEHISTORY
WHERE CONTAINERID = :container_id
ORDER BY HOLDTXNDATE DESC
FROM DWH.DW_MES_HOLDRELEASEHISTORY h
LEFT JOIN DWH.DW_MES_CONTAINER c
ON c.CONTAINERID = h.CONTAINERID
WHERE h.CONTAINERID = :container_id
ORDER BY h.HOLDTXNDATE DESC

View File

@@ -136,3 +136,27 @@ def test_fetch_events_rejects_branch_replaces_aliased_container_filter(
assert "r.CONTAINERID = :container_id" not in sql
assert "IN" in sql.upper()
assert params == {"p0": "CID-1", "p1": "CID-2"}
@patch("mes_dashboard.services.event_fetcher.cache_set")
@patch("mes_dashboard.services.event_fetcher.cache_get", return_value=None)
@patch("mes_dashboard.services.event_fetcher.read_sql_df")
@patch("mes_dashboard.services.event_fetcher.SQLLoader.load")
def test_fetch_events_holds_branch_replaces_aliased_container_filter(
mock_sql_load,
mock_read_sql_df,
_mock_cache_get,
_mock_cache_set,
):
mock_sql_load.return_value = (
"SELECT * FROM t h LEFT JOIN c ON c.CONTAINERID = h.CONTAINERID "
"WHERE h.CONTAINERID = :container_id ORDER BY h.HOLDTXNDATE DESC"
)
mock_read_sql_df.return_value = pd.DataFrame([])
EventFetcher.fetch_events(["CID-1", "CID-2"], "holds")
sql, params = mock_read_sql_df.call_args.args
assert "h.CONTAINERID = :container_id" not in sql
assert "IN" in sql.upper()
assert params == {"p0": "CID-1", "p1": "CID-2"}

View File

@@ -750,6 +750,48 @@ class TestExportCsvEndpoint:
assert 'LOT ID' in decoded
assert 'GA25010001-A01' in decoded
@patch('mes_dashboard.routes.query_tool_routes.get_lot_holds')
def test_export_lot_holds_uses_container_name_as_lot_id(
self,
mock_get_holds,
client,
):
mock_get_holds.return_value = {
'data': [
{
'CONTAINERID': '488103800029578b',
'CONTAINERNAME': 'GA25010001-A01',
'WORKCENTERNAME': '成型',
'HOLDTXNDATE': '2026-02-22 16:53:27',
'RELEASETXNDATE': None,
'HOLD_STATUS': 'HOLD',
'HOLD_HOURS': 1.46,
'HOLDREASONNAME': 'Q-Time Fail',
'HOLDCOMMENTS': '',
'HOLDEMP': 'U001',
'HOLDEMPDEPTNAME': '成型(D)',
'RELEASEEMP': '',
'RELEASECOMMENTS': '',
'NCRID': '',
}
],
'total': 1,
}
response = client.post(
'/api/query-tool/export-csv',
json={
'export_type': 'lot_holds',
'params': {'container_id': '488103800029578b'}
}
)
assert response.status_code == 200
assert 'lot_holds_488103800029578b.csv' in response.headers.get('Content-Disposition', '')
decoded = response.data.decode('utf-8-sig')
assert 'LOT ID' in decoded
assert 'GA25010001-A01' in decoded
class TestEquipmentListEndpoint:
"""Tests for /api/query-tool/equipment-list endpoint."""