매미킴

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,
) => {
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 {
const companyCode = req.user!.companyCode;
@@ -3606,8 +3611,15 @@ export const getProcessList = async (
[companyCode],
);
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) {
console.log(`${__debugTag} ◀ EXIT (no wop)`);
return res.json({ success: true, data: [] });
}
@@ -3624,6 +3636,11 @@ export const getProcessList = async (
ORDER BY wop_id, seq ASC`,
[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[]>();
for (const r of resRes.rows) {
@@ -3631,6 +3648,12 @@ export const getProcessList = async (
arr.push(r);
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도 별도 순회)
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 });
} catch (error: any) {
logger.error("[pop/production] processes 조회 오류:", error);

View File

@@ -831,7 +831,7 @@ export function WorkOrderList(props: WorkOrderListProps) {
const masterProcesses = useMemo(() => {
// 마스터 행 + 분할 행(진행중/완료/리워크) — 중복 제거
const seen = new Set<string>();
return allProcesses.filter((p) => {
const result = allProcesses.filter((p) => {
if (seen.has(p.id)) return false;
const include =
!p.parent_process_id || // 마스터 행
@@ -841,6 +841,32 @@ export function WorkOrderList(props: WorkOrderListProps) {
if (include) seen.add(p.id);
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]);
const equipmentMap = useMemo(() => {
@@ -857,36 +883,56 @@ export function WorkOrderList(props: WorkOrderListProps) {
/* ---- Filtered processes ---- */
const filteredProcesses = useMemo(() => {
if (selectedProcess === "__all__") return []; // 공정 미선택 시 빈 목록
return masterProcesses.filter((proc) => {
const __tag = `[POP-DEBUG][WorkOrderList][${new Date().toISOString()}]`;
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 isMaster = !proc.parent_process_id;
// 완료/진행중 탭에서는 SPLIT만 표시 (마스터 제외)
if (
isMaster &&
!isRework &&
(activeTab === "completed" || activeTab === "in_progress")
)
if (isMaster && !isRework && (activeTab === "completed" || activeTab === "in_progress")) {
__excluded.push({ id: proc.id, reason: "master_in_completed_or_in_progress_tab", status: proc.status, process_code: proc.process_code });
return false;
// 리워크 마스터가 in_progress/completed면 SPLIT이 생성된 것 → 리워크 마스터 숨김 (SPLIT은 표시)
if (
isRework &&
!proc.parent_process_id &&
(proc.status === "in_progress" || proc.status === "completed")
)
}
if (isRework && !proc.parent_process_id && (proc.status === "in_progress" || proc.status === "completed")) {
__excluded.push({ id: proc.id, reason: "rework_master_with_split", status: proc.status, process_code: proc.process_code });
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__") {
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 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;
});
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,
selectedProcess,
@@ -923,23 +969,36 @@ export function WorkOrderList(props: WorkOrderListProps) {
waiting: 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) {
const isMaster = !proc.parent_process_id;
const isRw = isReworkProcess(proc);
// 리워크 마스터가 in_progress/completed면 SPLIT이 있으므로 카운트 제외
const kind = isMaster ? "master" : "split";
if (
isRw &&
!proc.parent_process_id &&
(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;
if (proc.status === "acceptable") counts.acceptable++;
else if (proc.status === "in_progress" && (!isMaster || isRw))
counts.in_progress++;
else if (proc.status === "completed" && (!isMaster || isRw))
counts.completed++;
else counts.waiting++;
}
let bucket: string;
if (proc.status === "acceptable") { counts.acceptable++; bucket = "acceptable"; }
else if (proc.status === "in_progress" && (!isMaster || isRw)) { counts.in_progress++; bucket = "in_progress"; }
else if (proc.status === "completed" && (!isMaster || isRw)) { counts.completed++; bucket = "completed"; }
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;
}, [
masterProcesses,

View File

@@ -77,25 +77,53 @@ export function useProcessData() {
if (inFlight.current) return inFlight.current;
const task = (async () => {
const __tag = `[POP-DEBUG][useProcessData][${new Date().toISOString()}]`;
console.log(`${__tag} ▶ fetchData 시작`, { withSync: !!opts?.withSync });
setLoading(true);
if (opts?.withSync) setSyncing(true);
try {
if (opts?.withSync) {
try {
await apiClient.post("/pop/production/sync-work-instructions");
} catch {
const __t0 = Date.now();
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(
"동기화 실패 — 최신 작업지시가 반영되지 않을 수 있습니다",
);
}
}
const __t1 = Date.now();
const [wiRes, procRes, pmRes, eqRes] = await Promise.all([
apiClient.get("/work-instruction/list"),
apiClient.get("/pop/production/processes"),
dataApi.getTableData("process_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 중복 → 첫 행만 취함
let wiRaw: Record<string, unknown>[] = [];
@@ -175,6 +203,55 @@ export function useProcessData() {
setAllProcesses(flat);
setProcessList((pmRes.data ?? []) as ProcessMng[]);
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 {
toast.error("데이터 조회 실패");
} finally {