매미킴

This commit is contained in:
DDD1542
2026-04-27 16:15:42 +09:00
parent 34a44e0d9c
commit ee8f274feb
3 changed files with 228 additions and 30 deletions

View File

@@ -3586,6 +3586,11 @@ export const getProcessList = async (
res: Response, res: Response,
) => { ) => {
const pool = getPool(); const pool = getPool();
const __debugTag = `[POP-DEBUG][getProcessList][${req.user!.userId}@${req.user!.companyCode}][${new Date().toISOString()}]`;
console.log(`${__debugTag} ▶ ENTER`, {
query: req.query,
headers_user_agent: req.headers["user-agent"],
});
try { try {
const companyCode = req.user!.companyCode; const companyCode = req.user!.companyCode;
@@ -3606,8 +3611,15 @@ export const getProcessList = async (
[companyCode], [companyCode],
); );
const wops = wopRes.rows; const wops = wopRes.rows;
console.log(`${__debugTag} 1) wop SQL → rows:`, {
total: wops.length,
by_process: wops.reduce((m: Record<string, number>, w: any) => { m[w.process_code] = (m[w.process_code] || 0) + 1; return m; }, {}),
master_count: wops.filter((w: any) => !w.parent_process_id).length,
split_count: wops.filter((w: any) => w.parent_process_id).length,
});
if (wops.length === 0) { if (wops.length === 0) {
console.log(`${__debugTag} ◀ EXIT (no wop)`);
return res.json({ success: true, data: [] }); return res.json({ success: true, data: [] });
} }
@@ -3624,6 +3636,11 @@ export const getProcessList = async (
ORDER BY wop_id, seq ASC`, ORDER BY wop_id, seq ASC`,
[wopIds], [wopIds],
); );
console.log(`${__debugTag} 2) wopr SQL → rows:`, {
total: resRes.rows.length,
by_status: resRes.rows.reduce((m: Record<string, number>, r: any) => { m[r.status] = (m[r.status] || 0) + 1; return m; }, {}),
rework_count: resRes.rows.filter((r: any) => r.is_rework === "Y").length,
});
const resultsByWop = new Map<string, any[]>(); const resultsByWop = new Map<string, any[]>();
for (const r of resRes.rows) { for (const r of resRes.rows) {
@@ -3631,6 +3648,12 @@ export const getProcessList = async (
arr.push(r); arr.push(r);
resultsByWop.set(r.wop_id, arr); resultsByWop.set(r.wop_id, arr);
} }
const __wopWithoutResult = wops.filter((w: any) => !resultsByWop.has(w.id));
console.log(`${__debugTag} 2-1) wop ↔ wopr 매핑:`, {
wop_with_result: wops.length - __wopWithoutResult.length,
wop_WITHOUT_result: __wopWithoutResult.length,
missing_sample: __wopWithoutResult.slice(0, 5).map((w: any) => ({ id: w.id, process_code: w.process_code, seq_no: w.seq_no })),
});
// 3) 대표 상태/수량은 첫 실적 기준으로 매핑 (프론트는 accepted_results도 별도 순회) // 3) 대표 상태/수량은 첫 실적 기준으로 매핑 (프론트는 accepted_results도 별도 순회)
const data = wops.map((w: any) => { const data = wops.map((w: any) => {
@@ -3692,6 +3715,45 @@ export const getProcessList = async (
}; };
}); });
// 3) 응답 데이터 분포 로그
const masterFirstDist: Record<string, number> = {};
const p001WoprDist: Record<string, number> = {};
const reworkDist: Record<string, number> = {};
let totalWopr = 0;
const p001Cards: any[] = [];
for (const d of data) {
masterFirstDist[d.status] = (masterFirstDist[d.status] || 0) + 1;
if (d.process_code === "P001") {
p001Cards.push({
id: d.id,
seq_no: d.seq_no,
parent: d.parent_process_id,
master_status: d.status,
wopr_count: d.accepted_results.length,
wopr_statuses: d.accepted_results.map((r: any) => `${r.seq}:${r.status}${r.is_rework === "Y" ? "(R)" : ""}`),
});
}
for (const ar of d.accepted_results) {
totalWopr++;
if (d.process_code === "P001") {
p001WoprDist[ar.status] = (p001WoprDist[ar.status] || 0) + 1;
}
if (ar.is_rework === "Y") {
reworkDist[`${d.process_code}:${ar.status}`] = (reworkDist[`${d.process_code}:${ar.status}`] || 0) + 1;
}
}
}
console.log(`${__debugTag} 3) 응답 빌드 완료:`, {
data_length: data.length,
wopr_total_in_response: totalWopr,
master_first_status_dist: masterFirstDist,
p001_wopr_status_dist: p001WoprDist,
rework_dist: reworkDist,
});
console.log(`${__debugTag} 3-1) P001 카드 상세 (${p001Cards.length}개):`);
console.table(p001Cards);
console.log(`${__debugTag} ◀ EXIT`);
return res.json({ success: true, data }); return res.json({ success: true, data });
} catch (error: any) { } catch (error: any) {
logger.error("[pop/production] processes 조회 오류:", error); logger.error("[pop/production] processes 조회 오류:", error);

View File

@@ -831,7 +831,7 @@ export function WorkOrderList(props: WorkOrderListProps) {
const masterProcesses = useMemo(() => { const masterProcesses = useMemo(() => {
// 마스터 행 + 분할 행(진행중/완료/리워크) — 중복 제거 // 마스터 행 + 분할 행(진행중/완료/리워크) — 중복 제거
const seen = new Set<string>(); const seen = new Set<string>();
return allProcesses.filter((p) => { const result = allProcesses.filter((p) => {
if (seen.has(p.id)) return false; if (seen.has(p.id)) return false;
const include = const include =
!p.parent_process_id || // 마스터 행 !p.parent_process_id || // 마스터 행
@@ -841,6 +841,32 @@ export function WorkOrderList(props: WorkOrderListProps) {
if (include) seen.add(p.id); if (include) seen.add(p.id);
return include; return include;
}); });
const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
const dist: Record<string, number> = {};
for (const p of result) {
const kind = p.parent_process_id ? "split" : "master";
const rw = isReworkProcess(p) ? "(R)" : "";
const k = `${p.process_code}:${kind}${rw}:${p.status}`;
dist[k] = (dist[k] || 0) + 1;
}
console.log(`${__tag} 3) masterProcesses 생성:`, {
input_allProcesses: allProcesses.length,
output_masterProcesses: result.length,
excluded: allProcesses.length - result.length,
distribution: dist,
});
console.log(`${__tag} 3-1) masterProcesses — P001 + 모든 리워크 상세:`);
console.table(result
.filter((p) => p.process_code === "P001" || isReworkProcess(p))
.map((p) => ({
id: p.id,
wo_id: p.wo_id,
process_code: p.process_code,
kind: p.parent_process_id ? "split" : "master",
status: p.status,
is_rework: p.is_rework,
})));
return result;
}, [allProcesses]); }, [allProcesses]);
const equipmentMap = useMemo(() => { const equipmentMap = useMemo(() => {
@@ -857,36 +883,56 @@ export function WorkOrderList(props: WorkOrderListProps) {
/* ---- Filtered processes ---- */ /* ---- Filtered processes ---- */
const filteredProcesses = useMemo(() => { const filteredProcesses = useMemo(() => {
if (selectedProcess === "__all__") return []; // 공정 미선택 시 빈 목록 const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
return masterProcesses.filter((proc) => { if (selectedProcess === "__all__") {
console.log(`${__tag} 4) filteredProcesses: 공정 미선택 — 빈 배열`);
return [];
}
const __excluded: Array<{ id: string; reason: string; status: string; process_code: string }> = [];
const result = masterProcesses.filter((proc) => {
const isRework = isReworkProcess(proc); const isRework = isReworkProcess(proc);
const isMaster = !proc.parent_process_id; const isMaster = !proc.parent_process_id;
// 완료/진행중 탭에서는 SPLIT만 표시 (마스터 제외) if (isMaster && !isRework && (activeTab === "completed" || activeTab === "in_progress")) {
if ( __excluded.push({ id: proc.id, reason: "master_in_completed_or_in_progress_tab", status: proc.status, process_code: proc.process_code });
isMaster &&
!isRework &&
(activeTab === "completed" || activeTab === "in_progress")
)
return false; return false;
// 리워크 마스터가 in_progress/completed면 SPLIT이 생성된 것 → 리워크 마스터 숨김 (SPLIT은 표시) }
if ( if (isRework && !proc.parent_process_id && (proc.status === "in_progress" || proc.status === "completed")) {
isRework && __excluded.push({ id: proc.id, reason: "rework_master_with_split", status: proc.status, process_code: proc.process_code });
!proc.parent_process_id &&
(proc.status === "in_progress" || proc.status === "completed")
)
return false; return false;
// 재작업 카드는 공정 필터 무시 (모든 공정에서 표시) }
if (!isRework && proc.process_code !== selectedProcess) return false; if (!isRework && proc.process_code !== selectedProcess) {
__excluded.push({ id: proc.id, reason: `process_code_mismatch(${proc.process_code} vs ${selectedProcess})`, status: proc.status, process_code: proc.process_code });
return false;
}
if (selectedEquipment !== "__all__") { if (selectedEquipment !== "__all__") {
const wi = instructionMap[proc.wo_id]; const wi = instructionMap[proc.wo_id];
if (!wi) return false; if (!wi) {
__excluded.push({ id: proc.id, reason: "no_wi_for_equipment_filter", status: proc.status, process_code: proc.process_code });
return false;
}
const eqId = wi.equipment_id; const eqId = wi.equipment_id;
const eq = equipmentMap[eqId]; const eq = equipmentMap[eqId];
if (!eq || eq.equipment_code !== selectedEquipment) return false; if (!eq || eq.equipment_code !== selectedEquipment) {
__excluded.push({ id: proc.id, reason: `equipment_mismatch(${eq?.equipment_code} vs ${selectedEquipment})`, status: proc.status, process_code: proc.process_code });
return false;
}
}
if (activeTab !== "all" && proc.status !== activeTab) {
__excluded.push({ id: proc.id, reason: `tab_mismatch(${proc.status} vs ${activeTab})`, status: proc.status, process_code: proc.process_code });
return false;
} }
if (activeTab !== "all" && proc.status !== activeTab) return false;
return true; return true;
}); });
console.log(`${__tag} 4) filteredProcesses 계산:`, {
activeTab,
selectedProcess,
selectedEquipment,
input: masterProcesses.length,
output: result.length,
excluded_count: __excluded.length,
excluded_by_reason: __excluded.reduce((m: Record<string, number>, e) => { m[e.reason] = (m[e.reason] || 0) + 1; return m; }, {}),
});
return result;
}, [ }, [
masterProcesses, masterProcesses,
selectedProcess, selectedProcess,
@@ -923,23 +969,36 @@ export function WorkOrderList(props: WorkOrderListProps) {
waiting: 0, waiting: 0,
completed: 0, completed: 0,
}; };
const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
const breakdown: Array<{ id: string; wo_id: string; process_code: string; status: string; kind: string; is_rework: string; bucket: string }> = [];
for (const proc of preFiltered) { for (const proc of preFiltered) {
const isMaster = !proc.parent_process_id; const isMaster = !proc.parent_process_id;
const isRw = isReworkProcess(proc); const isRw = isReworkProcess(proc);
// 리워크 마스터가 in_progress/completed면 SPLIT이 있으므로 카운트 제외 const kind = isMaster ? "master" : "split";
if ( if (
isRw && isRw &&
!proc.parent_process_id && !proc.parent_process_id &&
(proc.status === "in_progress" || proc.status === "completed") (proc.status === "in_progress" || proc.status === "completed")
) ) {
breakdown.push({ id: proc.id, wo_id: proc.wo_id, process_code: proc.process_code, status: proc.status, kind, is_rework: proc.is_rework || "", bucket: "★SKIPPED" });
continue; continue;
if (proc.status === "acceptable") counts.acceptable++; }
else if (proc.status === "in_progress" && (!isMaster || isRw)) let bucket: string;
counts.in_progress++; if (proc.status === "acceptable") { counts.acceptable++; bucket = "acceptable"; }
else if (proc.status === "completed" && (!isMaster || isRw)) else if (proc.status === "in_progress" && (!isMaster || isRw)) { counts.in_progress++; bucket = "in_progress"; }
counts.completed++; else if (proc.status === "completed" && (!isMaster || isRw)) { counts.completed++; bucket = "completed"; }
else counts.waiting++; else { counts.waiting++; bucket = "waiting"; }
breakdown.push({ id: proc.id, wo_id: proc.wo_id, process_code: proc.process_code, status: proc.status, kind, is_rework: proc.is_rework || "", bucket });
} }
console.log(`${__tag} 5) tabCounts 계산:`, {
selectedProcess,
selectedEquipment,
masterProcesses_total: masterProcesses.length,
preFiltered_total: preFiltered.length,
counts,
});
console.log(`${__tag} 5-1) 카드별 분류 breakdown (${breakdown.length}개):`);
console.table(breakdown);
return counts; return counts;
}, [ }, [
masterProcesses, masterProcesses,

View File

@@ -77,25 +77,53 @@ export function useProcessData() {
if (inFlight.current) return inFlight.current; if (inFlight.current) return inFlight.current;
const task = (async () => { const task = (async () => {
const __tag = `[POP-DEBUG][useProcessData][${new Date().toISOString()}]`;
console.log(`${__tag} ▶ fetchData 시작`, { withSync: !!opts?.withSync });
setLoading(true); setLoading(true);
if (opts?.withSync) setSyncing(true); if (opts?.withSync) setSyncing(true);
try { try {
if (opts?.withSync) { if (opts?.withSync) {
try { try {
await apiClient.post("/pop/production/sync-work-instructions"); const __t0 = Date.now();
} catch { const syncRes = await apiClient.post("/pop/production/sync-work-instructions");
console.log(`${__tag} 0) POST /sync-work-instructions (${Date.now() - __t0}ms)`, syncRes.data);
} catch (e) {
console.warn(`${__tag} 0) sync 실패`, e);
toast.warning( toast.warning(
"동기화 실패 — 최신 작업지시가 반영되지 않을 수 있습니다", "동기화 실패 — 최신 작업지시가 반영되지 않을 수 있습니다",
); );
} }
} }
const __t1 = Date.now();
const [wiRes, procRes, pmRes, eqRes] = await Promise.all([ const [wiRes, procRes, pmRes, eqRes] = await Promise.all([
apiClient.get("/work-instruction/list"), apiClient.get("/work-instruction/list"),
apiClient.get("/pop/production/processes"), apiClient.get("/pop/production/processes"),
dataApi.getTableData("process_mng", { size: 500 }), dataApi.getTableData("process_mng", { size: 500 }),
dataApi.getTableData("equipment_mng", { size: 500 }), dataApi.getTableData("equipment_mng", { size: 500 }),
]); ]);
console.log(`${__tag} 1) 4개 API 병렬 호출 완료 (${Date.now() - __t1}ms)`);
const __wiArr = (Array.isArray(wiRes.data?.data) ? wiRes.data.data : wiRes.data?.data?.rows ?? []) as any[];
console.log(`${__tag} 1-1) /work-instruction/list 응답:`, {
raw_length: __wiArr.length,
unique_wi_ids: new Set(__wiArr.map((r: any) => r.wi_id || r.id)).size,
sample: __wiArr.slice(0, 3),
});
console.log(`${__tag} 1-2) /pop/production/processes 응답:`, {
raw_length: procRes.data?.data?.length ?? 0,
by_process_code: (procRes.data?.data ?? []).reduce((m: Record<string, number>, d: any) => { m[d.process_code] = (m[d.process_code] || 0) + 1; return m; }, {}),
});
console.log(`${__tag} 1-3) /pop/production/processes — P001 카드 RAW:`);
console.table((procRes.data?.data ?? [])
.filter((d: any) => d.process_code === "P001")
.map((d: any) => ({
id: d.id,
seq_no: d.seq_no,
parent: d.parent_process_id || "",
master_status: d.status,
wopr_count: d.accepted_results?.length ?? 0,
wopr_statuses: (d.accepted_results || []).map((r: any) => `${r.seq}:${r.status}${r.is_rework === "Y" ? "(R)" : ""}`).join("|"),
})));
// work-instruction: header+detail 조인이라 id 중복 → 첫 행만 취함 // work-instruction: header+detail 조인이라 id 중복 → 첫 행만 취함
let wiRaw: Record<string, unknown>[] = []; let wiRaw: Record<string, unknown>[] = [];
@@ -175,6 +203,55 @@ export function useProcessData() {
setAllProcesses(flat); setAllProcesses(flat);
setProcessList((pmRes.data ?? []) as ProcessMng[]); setProcessList((pmRes.data ?? []) as ProcessMng[]);
setEquipmentList((eqRes.data ?? []) as EquipmentMng[]); setEquipmentList((eqRes.data ?? []) as EquipmentMng[]);
// 2) flat 펼친 후 분포 — useProcessData가 master + 모든 wopr를 split row로 펼친 결과
const allDist: Record<string, number> = {};
const p001Dist: Record<string, number> = {};
const reworkDist: Record<string, number> = {};
for (const p of flat) {
const kind = p.parent_process_id ? "split" : "master";
const key = `${kind}:${p.status}`;
allDist[key] = (allDist[key] || 0) + 1;
if (p.process_code === "P001") {
p001Dist[key] = (p001Dist[key] || 0) + 1;
}
if (p.is_rework === "Y") {
reworkDist[`${p.process_code}:${kind}:${p.status}`] =
(reworkDist[`${p.process_code}:${kind}:${p.status}`] || 0) + 1;
}
}
console.log(`${__tag} 2) flat 펼침 완료:`, {
raw_wop_count: rawRows.length,
flat_count: flat.length,
expand_factor: flat.length / Math.max(1, rawRows.length),
all_kind_status_dist: allDist,
p001_kind_status_dist: p001Dist,
rework_dist: reworkDist,
});
console.log(`${__tag} 2-1) flat — P001 row 상세:`);
console.table(flat
.filter((p) => p.process_code === "P001")
.map((p) => ({
id: p.id,
wo_id: p.wo_id,
seq_no: p.seq_no,
kind: p.parent_process_id ? "split" : "master",
status: p.status,
is_rework: p.is_rework,
split_no: p.split_no ?? "",
})));
console.log(`${__tag} 2-2) flat — 모든 리워크 row:`);
console.table(flat
.filter((p) => p.is_rework === "Y")
.map((p) => ({
id: p.id,
wo_id: p.wo_id,
process_code: p.process_code,
seq_no: p.seq_no,
kind: p.parent_process_id ? "split" : "master",
status: p.status,
})));
console.log(`${__tag} ◀ fetchData 종료`);
} catch { } catch {
toast.error("데이터 조회 실패"); toast.error("데이터 조회 실패");
} finally { } finally {