fix(query-tool): export button hover visibility, jobs tab hide columns and export with txn history
- Increase CSS specificity for .btn-export to prevent portal-shell override on hover - Remove RESOURCEID and CONTAINERIDS from jobs tab display columns - Add lot_jobs_with_txn.sql joining JOB with JOBTXNHISTORY for complete export - Route lot_jobs export through get_lot_jobs_with_history() for full transaction data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,6 @@ ensureMesApiAvailable();
|
||||
|
||||
const JOB_COLUMN_PRIORITY = Object.freeze([
|
||||
'JOBID',
|
||||
'RESOURCEID',
|
||||
'RESOURCENAME',
|
||||
'JOBSTATUS',
|
||||
'JOBMODELNAME',
|
||||
@@ -42,7 +41,6 @@ const JOB_COLUMN_PRIORITY = Object.freeze([
|
||||
'PJ_SYMPTOMCODE2NAME',
|
||||
'CREATE_EMPNAME',
|
||||
'COMPLETE_EMPNAME',
|
||||
'CONTAINERIDS',
|
||||
'CONTAINERNAMES',
|
||||
]);
|
||||
|
||||
|
||||
@@ -116,16 +116,17 @@ body {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
.btn.btn-export {
|
||||
background: #0f766e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-export:hover {
|
||||
.btn.btn-export:hover {
|
||||
background: #0b5e59;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-export:disabled {
|
||||
.btn.btn-export:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ from mes_dashboard.services.query_tool_service import (
|
||||
get_lot_holds,
|
||||
get_lot_splits,
|
||||
get_lot_jobs,
|
||||
get_lot_jobs_with_history,
|
||||
get_lot_associations_batch,
|
||||
get_equipment_status_hours,
|
||||
get_equipment_lots,
|
||||
@@ -683,7 +684,7 @@ def export_csv():
|
||||
filename = f'lot_splits_{container_id}.csv'
|
||||
|
||||
elif export_type == 'lot_jobs':
|
||||
result = get_lot_jobs(
|
||||
result = get_lot_jobs_with_history(
|
||||
params.get('equipment_id'),
|
||||
params.get('time_start'),
|
||||
params.get('time_end')
|
||||
|
||||
@@ -1411,6 +1411,66 @@ def get_lot_jobs(
|
||||
return {'error': f'查詢失敗: {str(exc)}'}
|
||||
|
||||
|
||||
def get_lot_jobs_with_history(
|
||||
equipment_id: str,
|
||||
time_start: str,
|
||||
time_end: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get JOB records with full transaction history for export.
|
||||
|
||||
Joins DW_MES_JOB with DW_MES_JOBTXNHISTORY so each row contains
|
||||
both job-level and transaction-level columns, matching the pattern
|
||||
used by the job-query export.
|
||||
|
||||
Args:
|
||||
equipment_id: Equipment ID (RESOURCEID)
|
||||
time_start: Start time (ISO format)
|
||||
time_end: End time (ISO format)
|
||||
|
||||
Returns:
|
||||
Dict with 'data' (flattened job+txn records) and 'total', or 'error'.
|
||||
"""
|
||||
if not all([equipment_id, time_start, time_end]):
|
||||
return {'error': '請指定設備和時間範圍'}
|
||||
|
||||
try:
|
||||
if isinstance(time_start, str):
|
||||
start = datetime.strptime(time_start, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
start = time_start
|
||||
|
||||
if isinstance(time_end, str):
|
||||
end = datetime.strptime(time_end, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
end = time_end
|
||||
|
||||
sql = SQLLoader.load("query_tool/lot_jobs_with_txn")
|
||||
params = {
|
||||
'equipment_id': equipment_id,
|
||||
'time_start': start,
|
||||
'time_end': end,
|
||||
}
|
||||
|
||||
df = read_sql_df(sql, params)
|
||||
data = _df_to_records(df)
|
||||
|
||||
logger.debug(
|
||||
f"LOT jobs with txn history: {len(data)} records for {equipment_id}"
|
||||
)
|
||||
|
||||
return {
|
||||
'data': data,
|
||||
'total': len(data),
|
||||
'equipment_id': equipment_id,
|
||||
}
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
f"LOT jobs with txn history query failed for {equipment_id}: {exc}"
|
||||
)
|
||||
return {'error': f'查詢失敗: {str(exc)}'}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Equipment Period Query Functions
|
||||
# ============================================================
|
||||
|
||||
38
src/mes_dashboard/sql/query_tool/lot_jobs_with_txn.sql
Normal file
38
src/mes_dashboard/sql/query_tool/lot_jobs_with_txn.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- LOT Related JOB Records with Transaction History Export
|
||||
-- Joins JOB with JOBTXNHISTORY for complete CSV export
|
||||
--
|
||||
-- Parameters:
|
||||
-- :equipment_id - Equipment ID (EQUIPMENTID = RESOURCEID)
|
||||
-- :time_start - Start time of LOT processing
|
||||
-- :time_end - End time of LOT processing
|
||||
|
||||
SELECT
|
||||
j.RESOURCENAME,
|
||||
j.JOBID,
|
||||
j.JOBSTATUS AS JOB_FINAL_STATUS,
|
||||
j.JOBMODELNAME,
|
||||
j.JOBORDERNAME,
|
||||
j.CREATEDATE AS JOB_CREATEDATE,
|
||||
j.COMPLETEDATE AS JOB_COMPLETEDATE,
|
||||
j.CAUSECODENAME AS JOB_CAUSECODENAME,
|
||||
j.REPAIRCODENAME AS JOB_REPAIRCODENAME,
|
||||
j.SYMPTOMCODENAME AS JOB_SYMPTOMCODENAME,
|
||||
h.TXNDATE,
|
||||
h.FROMJOBSTATUS,
|
||||
h.JOBSTATUS AS TXN_JOBSTATUS,
|
||||
h.STAGENAME,
|
||||
h.CAUSECODENAME AS TXN_CAUSECODENAME,
|
||||
h.REPAIRCODENAME AS TXN_REPAIRCODENAME,
|
||||
h.SYMPTOMCODENAME AS TXN_SYMPTOMCODENAME,
|
||||
h.USER_NAME,
|
||||
h.EMP_NAME,
|
||||
h.COMMENTS
|
||||
FROM DWH.DW_MES_JOB j
|
||||
JOIN DWH.DW_MES_JOBTXNHISTORY h ON j.JOBID = h.JOBID
|
||||
WHERE j.RESOURCEID = :equipment_id
|
||||
AND (
|
||||
(j.CREATEDATE BETWEEN :time_start AND :time_end)
|
||||
OR (j.COMPLETEDATE BETWEEN :time_start AND :time_end)
|
||||
OR (j.CREATEDATE <= :time_start AND (j.COMPLETEDATE IS NULL OR j.COMPLETEDATE >= :time_end))
|
||||
)
|
||||
ORDER BY j.JOBID, h.TXNDATE
|
||||
@@ -1027,6 +1027,39 @@ class TestExportCsvBatchEndpoint:
|
||||
data = response.get_json()
|
||||
assert 'CONTAINERID' in data.get('error', '')
|
||||
|
||||
@patch('mes_dashboard.routes.query_tool_routes.get_lot_jobs_with_history')
|
||||
def test_export_lot_jobs_calls_with_history(self, mock_jobs_hist, client):
|
||||
"""lot_jobs export should call get_lot_jobs_with_history (includes txn)."""
|
||||
mock_jobs_hist.return_value = {
|
||||
'data': [
|
||||
{
|
||||
'RESOURCENAME': 'ASSY-01',
|
||||
'JOBID': 'JOB-001',
|
||||
'JOB_FINAL_STATUS': 'Complete',
|
||||
'TXNDATE': '2026-01-15 10:00:00',
|
||||
'TXN_JOBSTATUS': 'Complete',
|
||||
'STAGENAME': 'Repair',
|
||||
},
|
||||
],
|
||||
'total': 1,
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
'/api/query-tool/export-csv',
|
||||
json={
|
||||
'export_type': 'lot_jobs',
|
||||
'params': {
|
||||
'equipment_id': 'EQ001',
|
||||
'time_start': '2026-01-01 00:00:00',
|
||||
'time_end': '2026-01-31 23:59:59',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'text/csv' in response.content_type
|
||||
mock_jobs_hist.assert_called_once_with('EQ001', '2026-01-01 00:00:00', '2026-01-31 23:59:59')
|
||||
|
||||
|
||||
class TestEquipmentListEndpoint:
|
||||
"""Tests for /api/query-tool/equipment-list endpoint."""
|
||||
|
||||
Reference in New Issue
Block a user