feat: 배치(로트) 추적 — batch_id 컬럼 + 카드 진행률 표시

- work_order_process에 batch_id 컬럼 자동 추가 (ALTER TABLE IF NOT EXISTS)
- 접수 시 batch_id 자동 생성 (BATCH-timestamp-random)
- 다음 공정 접수 시 batch_id 전달 가능 (이어받기)
- 완료 카드: 같은 batch_id의 SPLIT 추적으로 정확한 진행률 표시
- 예: seq1만 완료 → ✓○○ (1/3), 전체 완료 → ✓✓✓ (전체 완료)
This commit is contained in:
SeongHyun Kim
2026-04-06 12:08:35 +09:00
parent 32a372a9b3
commit 32f99fba63
2 changed files with 46 additions and 10 deletions

View File

@@ -292,11 +292,15 @@ function CompressedProcessSteps({
currentSeqNo,
status,
onClick,
batchId,
allProcesses,
}: {
processes: WorkOrderProcess[];
currentSeqNo: string;
status: string;
onClick?: () => void;
batchId?: string;
allProcesses?: WorkOrderProcess[];
}) {
const sorted = [...processes]
.filter((p) => !p.parent_process_id)
@@ -307,10 +311,24 @@ function CompressedProcessSteps({
const currentIdx = sorted.findIndex((p) => p.seq_no === currentSeqNo);
if (currentIdx < 0) return null;
// For completed status: show progress up to current card's seq position
// For completed status: batch_id 기반 진행률 표시
if (status === "completed") {
const currentSeqNum = parseInt(currentSeqNo, 10);
const allDone = currentIdx === sorted.length - 1; // 마지막 공정이면 전체 완료
// 같은 batch_id를 가진 SPLIT들이 어느 seq까지 완료했는지 추적
let maxCompletedSeq = parseInt(currentSeqNo, 10); // 최소한 현재 seq까지는 완료
if (batchId && allProcesses) {
const batchSplits = allProcesses.filter(
(p) => (p as Record<string, unknown>).batch_id === batchId && p.parent_process_id && p.status === "completed"
);
for (const s of batchSplits) {
const sSeq = parseInt(s.seq_no, 10);
if (sSeq > maxCompletedSeq) maxCompletedSeq = sSeq;
}
}
const completedCount = sorted.filter((p) => parseInt(p.seq_no, 10) <= maxCompletedSeq).length;
const allDone = completedCount === sorted.length;
return (
<div
className="flex items-center justify-center gap-0.5 mb-3 py-2 px-3 bg-green-50 rounded-xl cursor-pointer hover:bg-green-100 transition"
@@ -318,7 +336,7 @@ function CompressedProcessSteps({
>
{sorted.map((proc, idx) => {
const seqNum = parseInt(proc.seq_no, 10);
const isDone = seqNum <= currentSeqNum; // 현재 카드 seq 이하만 완료
const isDone = seqNum <= maxCompletedSeq;
return (
<React.Fragment key={proc.id}>
{idx > 0 && <span className={`w-3 h-0.5 ${isDone ? "bg-green-400" : "bg-gray-300"} shrink-0`} />}
@@ -336,7 +354,7 @@ function CompressedProcessSteps({
);
})}
<span className="text-[10px] text-green-600 font-bold ml-2">
{allDone ? "전체 완료" : `${currentIdx + 1}/${sorted.length} 완료`}
{allDone ? "전체 완료" : `${completedCount}/${sorted.length} 완료`}
</span>
</div>
);
@@ -1305,6 +1323,8 @@ export function WorkOrderList() {
currentSeqNo={proc.seq_no}
status={proc.status}
onClick={() => openDetailModal(proc)}
batchId={(proc as Record<string, unknown>).batch_id as string | undefined}
allProcesses={allProcesses}
/>
)}