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) {
|
||||
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;
|
||||
updateUrlState();
|
||||
void loadListOnly();
|
||||
|
||||
@@ -32,6 +32,7 @@ _CACHE = {
|
||||
'workcenter_mapping': None, # Dict {workcentername: {group, sequence}}
|
||||
'workcenter_to_short': None, # Dict {workcentername: short_name}
|
||||
'spec_order_mapping': None, # Dict {spec_name_upper: spec_order}
|
||||
'spec_workcenter_mapping': None, # Dict {spec_name_upper: {workcenter, group, sequence}}
|
||||
'last_refresh': None,
|
||||
'is_loading': False,
|
||||
}
|
||||
@@ -162,6 +163,33 @@ def get_spec_order_mapping(force_refresh: bool = False) -> Dict[str, int]:
|
||||
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
|
||||
# ============================================================
|
||||
@@ -181,6 +209,7 @@ def get_cache_status() -> Dict[str, Any]:
|
||||
'workcenter_groups_count': len(_CACHE.get('workcenter_groups') or []),
|
||||
'workcenter_mapping_count': len(_CACHE.get('workcenter_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
|
||||
wc_groups, wc_mapping, wc_short = _load_workcenter_data()
|
||||
spec_order_mapping = _load_spec_order_mapping_from_spec()
|
||||
spec_wc_mapping = _load_spec_workcenter_mapping()
|
||||
|
||||
with _CACHE_LOCK:
|
||||
_CACHE['workcenter_groups'] = wc_groups
|
||||
_CACHE['workcenter_mapping'] = wc_mapping
|
||||
_CACHE['workcenter_to_short'] = wc_short
|
||||
_CACHE['spec_order_mapping'] = spec_order_mapping
|
||||
_CACHE['spec_workcenter_mapping'] = spec_wc_mapping
|
||||
_CACHE['last_refresh'] = datetime.now()
|
||||
_CACHE['is_loading'] = False
|
||||
|
||||
logger.info(
|
||||
f"Filter cache refreshed: {len(wc_groups or [])} groups, "
|
||||
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
|
||||
|
||||
@@ -418,6 +450,37 @@ def _load_spec_order_mapping_from_spec() -> Dict[str, int]:
|
||||
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):
|
||||
"""Extract workcenter groups and mapping from DataFrame.
|
||||
|
||||
|
||||
@@ -206,6 +206,13 @@ def _build_where_clause(
|
||||
normalized_categories = sorted({_normalize_text(v) for v in (categories or []) if _normalize_text(v)})
|
||||
|
||||
if 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:
|
||||
builder.add_in_condition("b.PRODUCTLINENAME", normalized_packages)
|
||||
|
||||
@@ -16,16 +16,19 @@
|
||||
-- 3) MOVEIN_QTY is de-duplicated at event level by HISTORYMAINLINEID.
|
||||
-- 4) SPEC_WORKCENTER_V is pre-aggregated to avoid row multiplication.
|
||||
|
||||
WITH workcenter_map AS (
|
||||
WITH spec_map AS (
|
||||
SELECT
|
||||
WORK_CENTER,
|
||||
SPEC,
|
||||
MIN(WORK_CENTER) KEEP (
|
||||
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||
) AS WORK_CENTER,
|
||||
MIN(WORK_CENTER_GROUP) KEEP (
|
||||
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||
) AS WORKCENTER_GROUP,
|
||||
MIN(WORKCENTERSEQUENCE_GROUP) AS WORKCENTERSEQUENCE_GROUP
|
||||
FROM DWH.DW_MES_SPEC_WORKCENTER_V
|
||||
WHERE WORK_CENTER IS NOT NULL
|
||||
GROUP BY WORK_CENTER
|
||||
WHERE SPEC IS NOT NULL
|
||||
GROUP BY SPEC
|
||||
),
|
||||
reject_raw AS (
|
||||
SELECT
|
||||
@@ -36,9 +39,9 @@ reject_raw AS (
|
||||
NVL(TRIM(c.PJ_TYPE), '(NA)') AS PJ_TYPE,
|
||||
NVL(TRIM(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
||||
NVL(TRIM(c.OBJECTTYPE), '(NA)') AS SCRAP_OBJECTTYPE,
|
||||
NVL(TRIM(r.WORKCENTERNAME), '(NA)') AS WORKCENTERNAME,
|
||||
NVL(TRIM(wm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
||||
NVL(wm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
||||
NVL(TRIM(sm.WORK_CENTER), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTERNAME,
|
||||
NVL(TRIM(sm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
||||
NVL(sm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
||||
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
||||
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
||||
NVL(
|
||||
@@ -73,8 +76,8 @@ reject_raw AS (
|
||||
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
||||
LEFT JOIN DWH.DW_MES_CONTAINER c
|
||||
ON c.CONTAINERID = r.CONTAINERID
|
||||
LEFT JOIN workcenter_map wm
|
||||
ON wm.WORK_CENTER = r.WORKCENTERNAME
|
||||
LEFT JOIN spec_map sm
|
||||
ON sm.SPEC = TRIM(r.SPECNAME)
|
||||
WHERE r.TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
||||
AND r.TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
||||
),
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
-- :start_date - Start date (YYYY-MM-DD)
|
||||
-- :end_date - End date (YYYY-MM-DD)
|
||||
|
||||
WITH workcenter_map AS (
|
||||
WITH spec_map AS (
|
||||
SELECT
|
||||
WORK_CENTER,
|
||||
SPEC,
|
||||
MIN(WORK_CENTER) KEEP (
|
||||
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||
) AS WORK_CENTER,
|
||||
MIN(WORK_CENTER_GROUP) KEEP (
|
||||
DENSE_RANK FIRST ORDER BY WORKCENTERSEQUENCE_GROUP
|
||||
) AS WORKCENTER_GROUP,
|
||||
MIN(WORKCENTERSEQUENCE_GROUP) AS WORKCENTERSEQUENCE_GROUP
|
||||
FROM DWH.DW_MES_SPEC_WORKCENTER_V
|
||||
WHERE WORK_CENTER IS NOT NULL
|
||||
GROUP BY WORK_CENTER
|
||||
WHERE SPEC IS NOT NULL
|
||||
GROUP BY SPEC
|
||||
),
|
||||
reject_raw AS (
|
||||
SELECT
|
||||
@@ -29,9 +32,9 @@ reject_raw AS (
|
||||
NVL(TRIM(c.PRODUCTNAME), '(NA)') AS PRODUCTNAME,
|
||||
NVL(TRIM(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
||||
NVL(TRIM(c.OBJECTTYPE), '(NA)') AS SCRAP_OBJECTTYPE,
|
||||
NVL(TRIM(r.WORKCENTERNAME), '(NA)') AS WORKCENTERNAME,
|
||||
NVL(TRIM(wm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
||||
NVL(wm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
||||
NVL(TRIM(sm.WORK_CENTER), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTERNAME,
|
||||
NVL(TRIM(sm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) AS WORKCENTER_GROUP,
|
||||
NVL(sm.WORKCENTERSEQUENCE_GROUP, 999) AS WORKCENTERSEQUENCE_GROUP,
|
||||
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
||||
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
||||
NVL(
|
||||
@@ -67,8 +70,8 @@ reject_raw AS (
|
||||
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
||||
LEFT JOIN DWH.DW_MES_CONTAINER c
|
||||
ON c.CONTAINERID = r.CONTAINERID
|
||||
LEFT JOIN workcenter_map wm
|
||||
ON wm.WORK_CENTER = r.WORKCENTERNAME
|
||||
LEFT JOIN spec_map sm
|
||||
ON sm.SPEC = TRIM(r.SPECNAME)
|
||||
WHERE r.TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
||||
AND r.TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user