fix(reject-history): resolve SPEC→WORKCENTER via lookup and fix Pareto reactivity
SQL CTEs now join on SPECNAME instead of WORKCENTERNAME to resolve correct WORKCENTER/GROUP from DW_MES_SPEC_WORKCENTER_V, fixing cases where the raw WORKCENTERNAME was mismatched (e.g. W/B-END with 成型_料). WORKCENTER_GROUP filter converts groups→specs via cached mapping before querying. Pareto chart now recalculates on legend toggle by spreading the ECharts selected object to trigger Vue reactivity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -568,7 +568,9 @@ function onTrendDateClick(dateStr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onTrendLegendChange(selected) {
|
function onTrendLegendChange(selected) {
|
||||||
trendLegendSelected.value = selected;
|
// Spread to create a new object — ECharts reuses the same internal reference,
|
||||||
|
// and Vue's ref setter skips trigger when Object.is(old, new) is true.
|
||||||
|
trendLegendSelected.value = { ...selected };
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
updateUrlState();
|
updateUrlState();
|
||||||
void loadListOnly();
|
void loadListOnly();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ _CACHE = {
|
|||||||
'workcenter_mapping': None, # Dict {workcentername: {group, sequence}}
|
'workcenter_mapping': None, # Dict {workcentername: {group, sequence}}
|
||||||
'workcenter_to_short': None, # Dict {workcentername: short_name}
|
'workcenter_to_short': None, # Dict {workcentername: short_name}
|
||||||
'spec_order_mapping': None, # Dict {spec_name_upper: spec_order}
|
'spec_order_mapping': None, # Dict {spec_name_upper: spec_order}
|
||||||
|
'spec_workcenter_mapping': None, # Dict {spec_name_upper: {workcenter, group, sequence}}
|
||||||
'last_refresh': None,
|
'last_refresh': None,
|
||||||
'is_loading': False,
|
'is_loading': False,
|
||||||
}
|
}
|
||||||
@@ -162,6 +163,33 @@ def get_spec_order_mapping(force_refresh: bool = False) -> Dict[str, int]:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_spec_workcenter_mapping(force_refresh: bool = False) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Get SPEC -> {workcenter, group, sequence} mapping.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict mapping normalized SPEC name (uppercase) to workcenter info.
|
||||||
|
"""
|
||||||
|
_ensure_cache_loaded(force_refresh)
|
||||||
|
return _CACHE.get('spec_workcenter_mapping') or {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_specs_for_groups(groups: List[str]) -> List[str]:
|
||||||
|
"""Get list of SPEC names that belong to specified workcenter groups.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
groups: List of WORKCENTER_GROUP names
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of normalized SPEC names (uppercase) belonging to those groups.
|
||||||
|
"""
|
||||||
|
mapping = get_spec_workcenter_mapping()
|
||||||
|
if not mapping:
|
||||||
|
return []
|
||||||
|
target = {g.strip().upper() for g in groups if g}
|
||||||
|
return [spec for spec, info in mapping.items()
|
||||||
|
if info['group'].strip().upper() in target]
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Cache Management
|
# Cache Management
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -181,6 +209,7 @@ def get_cache_status() -> Dict[str, Any]:
|
|||||||
'workcenter_groups_count': len(_CACHE.get('workcenter_groups') or []),
|
'workcenter_groups_count': len(_CACHE.get('workcenter_groups') or []),
|
||||||
'workcenter_mapping_count': len(_CACHE.get('workcenter_mapping') or {}),
|
'workcenter_mapping_count': len(_CACHE.get('workcenter_mapping') or {}),
|
||||||
'spec_order_mapping_count': len(_CACHE.get('spec_order_mapping') or {}),
|
'spec_order_mapping_count': len(_CACHE.get('spec_order_mapping') or {}),
|
||||||
|
'spec_workcenter_mapping_count': len(_CACHE.get('spec_workcenter_mapping') or {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -231,19 +260,22 @@ def _load_cache() -> bool:
|
|||||||
# Load workcenter groups - prioritize SPEC_WORKCENTER_V
|
# Load workcenter groups - prioritize SPEC_WORKCENTER_V
|
||||||
wc_groups, wc_mapping, wc_short = _load_workcenter_data()
|
wc_groups, wc_mapping, wc_short = _load_workcenter_data()
|
||||||
spec_order_mapping = _load_spec_order_mapping_from_spec()
|
spec_order_mapping = _load_spec_order_mapping_from_spec()
|
||||||
|
spec_wc_mapping = _load_spec_workcenter_mapping()
|
||||||
|
|
||||||
with _CACHE_LOCK:
|
with _CACHE_LOCK:
|
||||||
_CACHE['workcenter_groups'] = wc_groups
|
_CACHE['workcenter_groups'] = wc_groups
|
||||||
_CACHE['workcenter_mapping'] = wc_mapping
|
_CACHE['workcenter_mapping'] = wc_mapping
|
||||||
_CACHE['workcenter_to_short'] = wc_short
|
_CACHE['workcenter_to_short'] = wc_short
|
||||||
_CACHE['spec_order_mapping'] = spec_order_mapping
|
_CACHE['spec_order_mapping'] = spec_order_mapping
|
||||||
|
_CACHE['spec_workcenter_mapping'] = spec_wc_mapping
|
||||||
_CACHE['last_refresh'] = datetime.now()
|
_CACHE['last_refresh'] = datetime.now()
|
||||||
_CACHE['is_loading'] = False
|
_CACHE['is_loading'] = False
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Filter cache refreshed: {len(wc_groups or [])} groups, "
|
f"Filter cache refreshed: {len(wc_groups or [])} groups, "
|
||||||
f"{len(wc_mapping or {})} workcenters, "
|
f"{len(wc_mapping or {})} workcenters, "
|
||||||
f"{len(spec_order_mapping or {})} specs"
|
f"{len(spec_order_mapping or {})} specs, "
|
||||||
|
f"{len(spec_wc_mapping or {})} spec-wc mappings"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -418,6 +450,37 @@ def _load_spec_order_mapping_from_spec() -> Dict[str, int]:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_spec_workcenter_mapping() -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Load SPEC -> {workcenter, group, sequence} mapping from SPEC_WORKCENTER_V."""
|
||||||
|
try:
|
||||||
|
sql = f"""
|
||||||
|
SELECT SPEC, WORK_CENTER, WORK_CENTER_GROUP, WORKCENTERSEQUENCE_GROUP
|
||||||
|
FROM {SPEC_WORKCENTER_VIEW}
|
||||||
|
WHERE SPEC IS NOT NULL AND WORK_CENTER IS NOT NULL
|
||||||
|
"""
|
||||||
|
df = read_sql_df(sql)
|
||||||
|
if df is None or df.empty:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
mapping: Dict[str, Dict[str, Any]] = {}
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
spec = _normalize_spec_name(row.get('SPEC'))
|
||||||
|
if not spec:
|
||||||
|
continue
|
||||||
|
seq = _safe_sort_value(row.get('WORKCENTERSEQUENCE_GROUP'))
|
||||||
|
prev = mapping.get(spec)
|
||||||
|
if prev is None or seq < prev['sequence']:
|
||||||
|
mapping[spec] = {
|
||||||
|
'workcenter': str(row['WORK_CENTER']).strip(),
|
||||||
|
'group': str(row['WORK_CENTER_GROUP']).strip(),
|
||||||
|
'sequence': seq,
|
||||||
|
}
|
||||||
|
return mapping
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Failed to load SPEC_WORKCENTER mapping from SPEC_WORKCENTER_V: {exc}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _extract_workcenter_data_from_df(df):
|
def _extract_workcenter_data_from_df(df):
|
||||||
"""Extract workcenter groups and mapping from DataFrame.
|
"""Extract workcenter groups and mapping from DataFrame.
|
||||||
|
|
||||||
|
|||||||
@@ -206,7 +206,14 @@ def _build_where_clause(
|
|||||||
normalized_categories = sorted({_normalize_text(v) for v in (categories or []) if _normalize_text(v)})
|
normalized_categories = sorted({_normalize_text(v) for v in (categories or []) if _normalize_text(v)})
|
||||||
|
|
||||||
if normalized_wc_groups:
|
if normalized_wc_groups:
|
||||||
builder.add_in_condition("b.WORKCENTER_GROUP", normalized_wc_groups)
|
from mes_dashboard.services.filter_cache import get_specs_for_groups
|
||||||
|
specs_in_groups = get_specs_for_groups(normalized_wc_groups)
|
||||||
|
if specs_in_groups:
|
||||||
|
# Specs in cache are uppercase; use UPPER() for case-insensitive match
|
||||||
|
builder.add_in_condition("UPPER(b.SPECNAME)", specs_in_groups)
|
||||||
|
else:
|
||||||
|
# Fallback: cache not ready or no specs found for these groups
|
||||||
|
builder.add_in_condition("b.WORKCENTER_GROUP", normalized_wc_groups)
|
||||||
if normalized_packages:
|
if normalized_packages:
|
||||||
builder.add_in_condition("b.PRODUCTLINENAME", normalized_packages)
|
builder.add_in_condition("b.PRODUCTLINENAME", normalized_packages)
|
||||||
if reason_name_filters:
|
if reason_name_filters:
|
||||||
|
|||||||
@@ -16,16 +16,19 @@
|
|||||||
-- 3) MOVEIN_QTY is de-duplicated at event level by HISTORYMAINLINEID.
|
-- 3) MOVEIN_QTY is de-duplicated at event level by HISTORYMAINLINEID.
|
||||||
-- 4) SPEC_WORKCENTER_V is pre-aggregated to avoid row multiplication.
|
-- 4) SPEC_WORKCENTER_V is pre-aggregated to avoid row multiplication.
|
||||||
|
|
||||||
WITH workcenter_map AS (
|
WITH spec_map AS (
|
||||||
SELECT
|
SELECT
|
||||||
WORK_CENTER,
|
SPEC,
|
||||||
|
MIN(WORK_CENTER) KEEP (
|
||||||
|
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||||
|
) AS WORK_CENTER,
|
||||||
MIN(WORK_CENTER_GROUP) KEEP (
|
MIN(WORK_CENTER_GROUP) KEEP (
|
||||||
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||||
) AS WORKCENTER_GROUP,
|
) AS WORKCENTER_GROUP,
|
||||||
MIN(WORKCENTERSEQUENCE_GROUP) AS WORKCENTERSEQUENCE_GROUP
|
MIN(WORKCENTERSEQUENCE_GROUP) AS WORKCENTERSEQUENCE_GROUP
|
||||||
FROM DWH.DW_MES_SPEC_WORKCENTER_V
|
FROM DWH.DW_MES_SPEC_WORKCENTER_V
|
||||||
WHERE WORK_CENTER IS NOT NULL
|
WHERE SPEC IS NOT NULL
|
||||||
GROUP BY WORK_CENTER
|
GROUP BY SPEC
|
||||||
),
|
),
|
||||||
reject_raw AS (
|
reject_raw AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -36,9 +39,9 @@ reject_raw AS (
|
|||||||
NVL(TRIM(c.PJ_TYPE), '(NA)') AS PJ_TYPE,
|
NVL(TRIM(c.PJ_TYPE), '(NA)') AS PJ_TYPE,
|
||||||
NVL(TRIM(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
NVL(TRIM(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
||||||
NVL(TRIM(c.OBJECTTYPE), '(NA)') AS SCRAP_OBJECTTYPE,
|
NVL(TRIM(c.OBJECTTYPE), '(NA)') AS SCRAP_OBJECTTYPE,
|
||||||
NVL(TRIM(r.WORKCENTERNAME), '(NA)') AS WORKCENTERNAME,
|
NVL(TRIM(sm.WORK_CENTER), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTERNAME,
|
||||||
NVL(TRIM(wm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
NVL(TRIM(sm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
||||||
NVL(wm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
NVL(sm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
||||||
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
||||||
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
||||||
NVL(
|
NVL(
|
||||||
@@ -73,8 +76,8 @@ reject_raw AS (
|
|||||||
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
||||||
LEFT JOIN DWH.DW_MES_CONTAINER c
|
LEFT JOIN DWH.DW_MES_CONTAINER c
|
||||||
ON c.CONTAINERID = r.CONTAINERID
|
ON c.CONTAINERID = r.CONTAINERID
|
||||||
LEFT JOIN workcenter_map wm
|
LEFT JOIN spec_map sm
|
||||||
ON wm.WORK_CENTER = r.WORKCENTERNAME
|
ON sm.SPEC = TRIM(r.SPECNAME)
|
||||||
WHERE r.TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
WHERE r.TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
||||||
AND r.TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
AND r.TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,16 +5,19 @@
|
|||||||
-- :start_date - Start date (YYYY-MM-DD)
|
-- :start_date - Start date (YYYY-MM-DD)
|
||||||
-- :end_date - End date (YYYY-MM-DD)
|
-- :end_date - End date (YYYY-MM-DD)
|
||||||
|
|
||||||
WITH workcenter_map AS (
|
WITH spec_map AS (
|
||||||
SELECT
|
SELECT
|
||||||
WORK_CENTER,
|
SPEC,
|
||||||
|
MIN(WORK_CENTER) KEEP (
|
||||||
|
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||||
|
) AS WORK_CENTER,
|
||||||
MIN(WORK_CENTER_GROUP) KEEP (
|
MIN(WORK_CENTER_GROUP) KEEP (
|
||||||
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||||
) AS WORKCENTER_GROUP,
|
) AS WORKCENTER_GROUP,
|
||||||
MIN(WORKCENTERSEQUENCE_GROUP) AS WORKCENTERSEQUENCE_GROUP
|
MIN(WORKCENTERSEQUENCE_GROUP) AS WORKCENTERSEQUENCE_GROUP
|
||||||
FROM DWH.DW_MES_SPEC_WORKCENTER_V
|
FROM DWH.DW_MES_SPEC_WORKCENTER_V
|
||||||
WHERE WORK_CENTER IS NOT NULL
|
WHERE SPEC IS NOT NULL
|
||||||
GROUP BY WORK_CENTER
|
GROUP BY SPEC
|
||||||
),
|
),
|
||||||
reject_raw AS (
|
reject_raw AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -29,9 +32,9 @@ reject_raw AS (
|
|||||||
NVL(TRIM(c.PRODUCTNAME), '(NA)') AS PRODUCTNAME,
|
NVL(TRIM(c.PRODUCTNAME), '(NA)') AS PRODUCTNAME,
|
||||||
NVL(TRIM(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
NVL(TRIM(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
||||||
NVL(TRIM(c.OBJECTTYPE), '(NA)') AS SCRAP_OBJECTTYPE,
|
NVL(TRIM(c.OBJECTTYPE), '(NA)') AS SCRAP_OBJECTTYPE,
|
||||||
NVL(TRIM(r.WORKCENTERNAME), '(NA)') AS WORKCENTERNAME,
|
NVL(TRIM(sm.WORK_CENTER), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTERNAME,
|
||||||
NVL(TRIM(wm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
NVL(TRIM(sm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
||||||
NVL(wm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
NVL(sm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
||||||
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
||||||
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
||||||
NVL(
|
NVL(
|
||||||
@@ -67,8 +70,8 @@ reject_raw AS (
|
|||||||
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
||||||
LEFT JOIN DWH.DW_MES_CONTAINER c
|
LEFT JOIN DWH.DW_MES_CONTAINER c
|
||||||
ON c.CONTAINERID = r.CONTAINERID
|
ON c.CONTAINERID = r.CONTAINERID
|
||||||
LEFT JOIN workcenter_map wm
|
LEFT JOIN spec_map sm
|
||||||
ON wm.WORK_CENTER = r.WORKCENTERNAME
|
ON sm.SPEC = TRIM(r.SPECNAME)
|
||||||
WHERE r.TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
WHERE r.TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
||||||
AND r.TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
AND r.TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user