feat(query-tool): align lot reject detail with reject-history layout
This commit is contained in:
@@ -4,6 +4,7 @@ import { computed } from 'vue';
|
||||
import ExportButton from './ExportButton.vue';
|
||||
import LotAssociationTable from './LotAssociationTable.vue';
|
||||
import LotHistoryTable from './LotHistoryTable.vue';
|
||||
import LotRejectTable from './LotRejectTable.vue';
|
||||
import LotTimeline from './LotTimeline.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -105,36 +106,35 @@ const activeEmptyText = computed(() => {
|
||||
});
|
||||
|
||||
const activeColumnLabels = computed(() => {
|
||||
if (props.activeSubTab !== 'materials') {
|
||||
return {};
|
||||
if (props.activeSubTab === 'materials') {
|
||||
return {
|
||||
CONTAINERNAME: 'LOT ID',
|
||||
};
|
||||
}
|
||||
return {
|
||||
CONTAINERNAME: 'LOT ID',
|
||||
};
|
||||
return {};
|
||||
});
|
||||
|
||||
const activeHiddenColumns = computed(() => {
|
||||
if (props.activeSubTab !== 'materials') {
|
||||
return [];
|
||||
if (props.activeSubTab === 'materials') {
|
||||
return ['CONTAINERID', 'WORKCENTER_GROUP'];
|
||||
}
|
||||
return ['CONTAINERID'];
|
||||
return [];
|
||||
});
|
||||
|
||||
const activeColumnOrder = computed(() => {
|
||||
if (props.activeSubTab !== 'materials') {
|
||||
return [];
|
||||
if (props.activeSubTab === 'materials') {
|
||||
return [
|
||||
'CONTAINERNAME',
|
||||
'MATERIALPARTNAME',
|
||||
'MATERIALLOTNAME',
|
||||
'QTYCONSUMED',
|
||||
'WORKCENTERNAME',
|
||||
'SPECNAME',
|
||||
'EQUIPMENTNAME',
|
||||
'TXNDATE',
|
||||
];
|
||||
}
|
||||
return [
|
||||
'CONTAINERNAME',
|
||||
'MATERIALPARTNAME',
|
||||
'MATERIALLOTNAME',
|
||||
'QTYCONSUMED',
|
||||
'WORKCENTERNAME',
|
||||
'SPECNAME',
|
||||
'EQUIPMENTNAME',
|
||||
'TXNDATE',
|
||||
'WORKCENTER_GROUP',
|
||||
];
|
||||
return [];
|
||||
});
|
||||
|
||||
const canExport = computed(() => {
|
||||
@@ -223,7 +223,7 @@ const detailCountLabel = computed(() => {
|
||||
</div>
|
||||
|
||||
<LotAssociationTable
|
||||
v-else
|
||||
v-else-if="activeSubTab !== 'rejects'"
|
||||
:rows="activeRows"
|
||||
:loading="activeLoading"
|
||||
:empty-text="activeLoaded ? activeEmptyText : '尚未查詢此分頁資料'"
|
||||
@@ -231,6 +231,13 @@ const detailCountLabel = computed(() => {
|
||||
:hidden-columns="activeHiddenColumns"
|
||||
:column-order="activeColumnOrder"
|
||||
/>
|
||||
|
||||
<LotRejectTable
|
||||
v-else
|
||||
:rows="activeRows"
|
||||
:loading="activeLoading"
|
||||
:empty-text="activeLoaded ? activeEmptyText : '尚未查詢此分頁資料'"
|
||||
/>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
169
frontend/src/query-tool/components/LotRejectTable.vue
Normal file
169
frontend/src/query-tool/components/LotRejectTable.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { formatDateTime, parseDateTime } from '../utils/values.js';
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '無報廢資料',
|
||||
},
|
||||
});
|
||||
|
||||
const showRejectBreakdown = ref(false);
|
||||
|
||||
function toNumber(value, defaultValue = 0) {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) ? num : defaultValue;
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
return toNumber(value).toLocaleString('zh-TW');
|
||||
}
|
||||
|
||||
function normalizeText(value, fallback = '') {
|
||||
const text = String(value || '').trim();
|
||||
return text || fallback;
|
||||
}
|
||||
|
||||
function normalizeRejectRow(row) {
|
||||
const rejectQty = toNumber(row?.REJECT_QTY ?? row?.REJECTQTY);
|
||||
const standbyQty = toNumber(row?.STANDBY_QTY ?? row?.STANDBYQTY);
|
||||
const qtyToProcessQty = toNumber(row?.QTYTOPROCESS_QTY ?? row?.QTYTOPROCESS);
|
||||
const inProcessQty = toNumber(row?.INPROCESS_QTY ?? row?.INPROCESSQTY);
|
||||
const processedQty = toNumber(row?.PROCESSED_QTY ?? row?.PROCESSEDQTY);
|
||||
|
||||
const computedRejectTotal = rejectQty + standbyQty + qtyToProcessQty + inProcessQty + processedQty;
|
||||
const rejectTotalQty = toNumber(row?.REJECT_TOTAL_QTY, computedRejectTotal);
|
||||
const defectQty = toNumber(row?.DEFECT_QTY);
|
||||
|
||||
const txnTimeRaw = row?.TXN_TIME || row?.TXNDATE || row?.TXN_DAY || '';
|
||||
const txnDate = parseDateTime(txnTimeRaw);
|
||||
|
||||
return {
|
||||
CONTAINERNAME: normalizeText(row?.CONTAINERNAME, normalizeText(row?.CONTAINERID)),
|
||||
WORKCENTERNAME: normalizeText(row?.WORKCENTERNAME),
|
||||
PRODUCTLINENAME: normalizeText(row?.PRODUCTLINENAME),
|
||||
PJ_FUNCTION: normalizeText(row?.PJ_FUNCTION),
|
||||
PJ_TYPE: normalizeText(row?.PJ_TYPE),
|
||||
PRODUCTNAME: normalizeText(row?.PRODUCTNAME),
|
||||
LOSSREASONNAME: normalizeText(row?.LOSSREASONNAME),
|
||||
EQUIPMENTNAME: normalizeText(row?.EQUIPMENTNAME),
|
||||
REJECTCOMMENT: normalizeText(row?.REJECTCOMMENT || row?.COMMENTS),
|
||||
REJECT_TOTAL_QTY: rejectTotalQty,
|
||||
REJECT_QTY: rejectQty,
|
||||
STANDBY_QTY: standbyQty,
|
||||
QTYTOPROCESS_QTY: qtyToProcessQty,
|
||||
INPROCESS_QTY: inProcessQty,
|
||||
PROCESSED_QTY: processedQty,
|
||||
DEFECT_QTY: defectQty,
|
||||
TXN_TIME_RAW: txnTimeRaw,
|
||||
TXN_TIME: txnDate ? formatDateTime(txnDate) : normalizeText(txnTimeRaw),
|
||||
TXN_DAY_SORT: txnDate ? txnDate.getTime() : 0,
|
||||
WORKCENTERSEQUENCE_GROUP: toNumber(row?.WORKCENTERSEQUENCE_GROUP, 999),
|
||||
};
|
||||
}
|
||||
|
||||
const normalizedRows = computed(() => {
|
||||
return (props.rows || []).map(normalizeRejectRow);
|
||||
});
|
||||
|
||||
const sortedRows = computed(() => {
|
||||
return [...normalizedRows.value].sort((a, b) => {
|
||||
if (a.TXN_DAY_SORT !== b.TXN_DAY_SORT) {
|
||||
return b.TXN_DAY_SORT - a.TXN_DAY_SORT;
|
||||
}
|
||||
if (a.WORKCENTERSEQUENCE_GROUP !== b.WORKCENTERSEQUENCE_GROUP) {
|
||||
return a.WORKCENTERSEQUENCE_GROUP - b.WORKCENTERSEQUENCE_GROUP;
|
||||
}
|
||||
if (a.WORKCENTERNAME !== b.WORKCENTERNAME) {
|
||||
return a.WORKCENTERNAME.localeCompare(b.WORKCENTERNAME, 'zh-Hant');
|
||||
}
|
||||
if (a.REJECT_TOTAL_QTY !== b.REJECT_TOTAL_QTY) {
|
||||
return b.REJECT_TOTAL_QTY - a.REJECT_TOTAL_QTY;
|
||||
}
|
||||
return a.CONTAINERNAME.localeCompare(b.CONTAINERNAME, 'zh-Hant');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="rounded-card border border-stroke-soft bg-white p-3">
|
||||
<div v-if="loading" class="rounded-card border border-dashed border-stroke-soft bg-surface-muted/40 px-3 py-5 text-center text-xs text-slate-500">
|
||||
讀取中...
|
||||
</div>
|
||||
|
||||
<div v-else-if="sortedRows.length === 0" class="rounded-card border border-dashed border-stroke-soft bg-surface-muted/40 px-3 py-5 text-center text-xs text-slate-500">
|
||||
{{ emptyText }}
|
||||
</div>
|
||||
|
||||
<div v-else class="max-h-[420px] overflow-auto rounded-card border border-stroke-soft">
|
||||
<table class="min-w-full border-collapse text-xs">
|
||||
<thead class="sticky top-0 z-10 bg-slate-100 text-slate-700">
|
||||
<tr>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">LOT</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">WORKCENTER</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">Package</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">FUNCTION</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">TYPE</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">PRODUCT</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">原因</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">EQUIPMENT</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">COMMENT</th>
|
||||
<th
|
||||
class="cursor-pointer whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold hover:text-brand-700"
|
||||
@click="showRejectBreakdown = !showRejectBreakdown"
|
||||
>
|
||||
扣帳報廢量 <span>{{ showRejectBreakdown ? '▾' : '▸' }}</span>
|
||||
</th>
|
||||
<template v-if="showRejectBreakdown">
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">REJECT</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">STANDBY</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">QTYTOPROCESS</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">INPROCESS</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">PROCESSED</th>
|
||||
</template>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">不扣帳報廢量</th>
|
||||
<th class="whitespace-nowrap border-b border-stroke-soft px-2 py-1.5 text-left font-semibold">報廢時間</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(row, idx) in sortedRows"
|
||||
:key="`${row.TXN_TIME_RAW}-${row.CONTAINERNAME}-${row.LOSSREASONNAME}-${idx}`"
|
||||
class="odd:bg-white even:bg-slate-50"
|
||||
>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.CONTAINERNAME }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.WORKCENTERNAME || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.PRODUCTLINENAME || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.PJ_FUNCTION || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.PJ_TYPE || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.PRODUCTNAME || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.LOSSREASONNAME || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.EQUIPMENTNAME || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.REJECTCOMMENT || '-' }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.REJECT_TOTAL_QTY) }}</td>
|
||||
<template v-if="showRejectBreakdown">
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.REJECT_QTY) }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.STANDBY_QTY) }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.QTYTOPROCESS_QTY) }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.INPROCESS_QTY) }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.PROCESSED_QTY) }}</td>
|
||||
</template>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ formatNumber(row.DEFECT_QTY) }}</td>
|
||||
<td class="whitespace-nowrap border-b border-stroke-soft/70 px-2 py-1.5 text-slate-700">{{ row.TXN_TIME || '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -31,6 +31,7 @@ _DOMAIN_SPECS: Dict[str, Dict[str, Any]] = {
|
||||
"materials": {
|
||||
"filter_column": "m.CONTAINERID",
|
||||
"cache_ttl": 300,
|
||||
"schema_version": 2,
|
||||
"bucket": "event-materials",
|
||||
"max_env": "EVT_MATERIALS_RATE_MAX_REQUESTS",
|
||||
"window_env": "EVT_MATERIALS_RATE_WINDOW_SECONDS",
|
||||
@@ -38,8 +39,9 @@ _DOMAIN_SPECS: Dict[str, Dict[str, Any]] = {
|
||||
"default_window": 60,
|
||||
},
|
||||
"rejects": {
|
||||
"filter_column": "CONTAINERID",
|
||||
"filter_column": "r.CONTAINERID",
|
||||
"cache_ttl": 300,
|
||||
"schema_version": 2,
|
||||
"bucket": "event-rejects",
|
||||
"max_env": "EVT_REJECTS_RATE_MAX_REQUESTS",
|
||||
"window_env": "EVT_REJECTS_RATE_WINDOW_SECONDS",
|
||||
@@ -116,7 +118,8 @@ class EventFetcher:
|
||||
def _cache_key(domain: str, container_ids: List[str]) -> str:
|
||||
normalized = sorted(_normalize_ids(container_ids))
|
||||
digest = hashlib.md5("|".join(normalized).encode("utf-8")).hexdigest()[:12]
|
||||
return f"evt:{domain}:{digest}"
|
||||
schema_version = int(_DOMAIN_SPECS.get(domain, {}).get("schema_version", 1))
|
||||
return f"evt:{domain}:v{schema_version}:{digest}"
|
||||
|
||||
@staticmethod
|
||||
def _replace_container_filter(sql: str, condition_sql: str) -> str:
|
||||
|
||||
@@ -1,26 +1,70 @@
|
||||
-- LOT Reject Records Query
|
||||
-- Retrieves reject (defect) records for a LOT
|
||||
--
|
||||
-- Parameters:
|
||||
-- container_id - CONTAINERID to query (16-char hex)
|
||||
--
|
||||
-- Note: Uses LOSSREASONNAME (NOT REJECTREASONNAME)
|
||||
-- Uses TXNDATE (NOT TXNDATETIME)
|
||||
-- Only has EQUIPMENTNAME, NO EQUIPMENTID field
|
||||
-- DEFECTQTY = SUM of REJECTQTY + STANDBYQTY + QTYTOPROCESS + INPROCESSQTY + PROCESSEDQTY
|
||||
|
||||
SELECT
|
||||
CONTAINERID,
|
||||
LOSSREASONNAME,
|
||||
REJECTQTY,
|
||||
NVL(REJECTQTY, 0) + NVL(STANDBYQTY, 0) + NVL(QTYTOPROCESS, 0)
|
||||
+ NVL(INPROCESSQTY, 0) + NVL(PROCESSEDQTY, 0) AS DEFECTQTY,
|
||||
WORKCENTERNAME,
|
||||
EQUIPMENTNAME,
|
||||
TXNDATE,
|
||||
COMMENTS,
|
||||
REJECTCAUSE,
|
||||
REJECTCOMMENT
|
||||
FROM DWH.DW_MES_LOTREJECTHISTORY
|
||||
WHERE CONTAINERID = :container_id
|
||||
ORDER BY TXNDATE
|
||||
-- LOT Reject Records Query
|
||||
-- Retrieves reject (defect) records for a LOT
|
||||
--
|
||||
-- Parameters:
|
||||
-- container_id - CONTAINERID to query (16-char hex)
|
||||
--
|
||||
-- Note: Aligns output semantics and ordering with Reject History detail:
|
||||
-- - REJECT_TOTAL_QTY = REJECT + STANDBY + QTYTOPROCESS + INPROCESS + PROCESSED
|
||||
-- - DEFECT_QTY uses DEFECTQTY (non-charge-off scrap)
|
||||
-- - Order: TXN_DAY desc, WORKCENTER sequence asc, WORKCENTER asc, REJECT_TOTAL_QTY desc, LOT asc
|
||||
|
||||
WITH spec_map AS (
|
||||
SELECT
|
||||
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 SPEC IS NOT NULL
|
||||
GROUP BY SPEC
|
||||
)
|
||||
SELECT
|
||||
r.CONTAINERID,
|
||||
NVL(TRIM(c.CONTAINERNAME), TRIM(r.CONTAINERID)) AS CONTAINERNAME,
|
||||
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(c.PRODUCTLINENAME), '(NA)') AS PRODUCTLINENAME,
|
||||
NVL(TRIM(c.PJ_FUNCTION), '(NA)') AS PJ_FUNCTION,
|
||||
NVL(TRIM(c.PJ_TYPE), '(NA)') AS PJ_TYPE,
|
||||
NVL(TRIM(c.PRODUCTNAME), '(NA)') AS PRODUCTNAME,
|
||||
NVL(TRIM(r.SPECNAME), '(NA)') AS SPECNAME,
|
||||
NVL(TRIM(r.LOSSREASONNAME), '(未填寫)') AS LOSSREASONNAME,
|
||||
NVL(TRIM(r.EQUIPMENTNAME), '(NA)') AS EQUIPMENTNAME,
|
||||
TRIM(r.REJECTCOMMENT) AS REJECTCOMMENT,
|
||||
NVL(r.REJECTQTY, 0) AS REJECT_QTY,
|
||||
NVL(r.STANDBYQTY, 0) AS STANDBY_QTY,
|
||||
NVL(r.QTYTOPROCESS, 0) AS QTYTOPROCESS_QTY,
|
||||
NVL(r.INPROCESSQTY, 0) AS INPROCESS_QTY,
|
||||
NVL(r.PROCESSEDQTY, 0) AS PROCESSED_QTY,
|
||||
NVL(r.REJECTQTY, 0)
|
||||
+ NVL(r.STANDBYQTY, 0)
|
||||
+ NVL(r.QTYTOPROCESS, 0)
|
||||
+ NVL(r.INPROCESSQTY, 0)
|
||||
+ NVL(r.PROCESSEDQTY, 0) AS REJECT_TOTAL_QTY,
|
||||
NVL(r.DEFECTQTY, 0) AS DEFECT_QTY,
|
||||
r.TXNDATE AS TXN_TIME,
|
||||
r.TXNDATE,
|
||||
TRUNC(r.TXNDATE) AS TXN_DAY
|
||||
FROM DWH.DW_MES_LOTREJECTHISTORY r
|
||||
LEFT JOIN DWH.DW_MES_CONTAINER c
|
||||
ON c.CONTAINERID = r.CONTAINERID
|
||||
LEFT JOIN spec_map sm
|
||||
ON sm.SPEC = TRIM(r.SPECNAME)
|
||||
WHERE r.CONTAINERID = :container_id
|
||||
ORDER BY
|
||||
TRUNC(r.TXNDATE) DESC,
|
||||
NVL(sm.WORKCENTERSEQUENCE_GROUP, 999) ASC,
|
||||
NVL(TRIM(sm.WORK_CENTER), NVL(TRIM(r.WORKCENTERNAME), '(NA)')) ASC,
|
||||
NVL(r.REJECTQTY, 0)
|
||||
+ NVL(r.STANDBYQTY, 0)
|
||||
+ NVL(r.QTYTOPROCESS, 0)
|
||||
+ NVL(r.INPROCESSQTY, 0)
|
||||
+ NVL(r.PROCESSEDQTY, 0) DESC,
|
||||
NVL(TRIM(c.CONTAINERNAME), TRIM(r.CONTAINERID)) ASC,
|
||||
r.TXNDATE DESC
|
||||
|
||||
@@ -112,3 +112,27 @@ def test_fetch_events_materials_branch_replaces_aliased_container_filter(
|
||||
assert "m.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_rejects_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 r LEFT JOIN c ON c.CONTAINERID = r.CONTAINERID "
|
||||
"WHERE r.CONTAINERID = :container_id ORDER BY r.TXNDATE"
|
||||
)
|
||||
mock_read_sql_df.return_value = pd.DataFrame([])
|
||||
|
||||
EventFetcher.fetch_events(["CID-1", "CID-2"], "rejects")
|
||||
|
||||
sql, params = mock_read_sql_df.call_args.args
|
||||
assert "r.CONTAINERID = :container_id" not in sql
|
||||
assert "IN" in sql.upper()
|
||||
assert params == {"p0": "CID-1", "p1": "CID-2"}
|
||||
|
||||
Reference in New Issue
Block a user