feat: BLOCK MES-HARDEN Phase 0~3 + 공정 흐름 스트립 필/칩 UI 개편
MES 불량 처분 체계(disposition 3종)를 구현하고, 공정 카드의 흐름 스트립을 현재 공정 중심 필/칩 윈도우로 전면 재설계한다. [Phase 0: 기반 안정화] - confirmResult: SUM 버그 수정 + 마스터 캐스케이드 완료 판정 - checkAndCompleteWorkInstruction: 헬퍼 함수 추출 (saveResult/confirmResult 양쪽에서 work_instruction 상태 갱신) - saveResult: 초과 생산 에러 -> 경고 로그로 변경 [Phase 1: UI 정리] - DISPOSITION_OPTIONS: 5종 -> 3종(폐기/재작업/특채) - 카드 수동 완료 버튼: in_progress + 생산 있음 + 미완료 시 표시 (__manualComplete -> confirmResult 호출) [Phase 2: 양품 계산 서버화] - concession_qty/is_rework/rework_source_id DB 컬럼 추가 - saveResult: defect_detail disposition별 서버 양품 계산 (addGood = addProduction - addDefect, addConcession 분리) - prevGoodQty 5곳: SUM(good_qty) + SUM(concession_qty) 통일 - 프론트 특채 표시: MesInProgressMetrics/MesCompletedMetrics [Phase 3: 재작업 카드] - saveResult: disposition=rework 시 동일 공정에 분할행 자동 INSERT (is_rework='Y', rework_source_id 연결, status='acceptable') - 프론트: amber "재작업" 배지 + MesAcceptableMetrics 재작업 전용 UI - 재작업 카드 접수가능 수량 버그 수정 (마스터 qty -> input_qty) [공정 흐름 스트립 UI 개편] - ProcessFlowStrip: 바 형태 -> 필/칩 5슬롯 윈도우 (+N/이전/현재/다음/+N, 현재 공정 항상 중앙) - 색상: 지나온=emerald(완료)/slate, 현재=primary, 완료=emerald, 대기=muted, 남은=amber
This commit is contained in:
@@ -565,7 +565,8 @@ export const saveResult = async (
|
||||
|
||||
const statusCheck = await pool.query(
|
||||
`SELECT wop.status, wop.result_status, wop.total_production_qty, wop.good_qty,
|
||||
wop.defect_qty, wop.input_qty, wop.parent_process_id, wop.wo_id, wop.seq_no
|
||||
wop.defect_qty, wop.concession_qty, wop.defect_detail,
|
||||
wop.input_qty, wop.parent_process_id, wop.wo_id, wop.seq_no
|
||||
FROM work_order_process wop
|
||||
WHERE wop.id = $1 AND wop.company_code = $2`,
|
||||
[work_order_process_id, companyCode]
|
||||
@@ -602,17 +603,23 @@ export const saveResult = async (
|
||||
});
|
||||
}
|
||||
|
||||
// 실적 누적이 접수량을 초과하지 않도록 검증
|
||||
// 초과 생산 경고 (차단하지 않음 - 현장 유연성)
|
||||
const prevTotal = parseInt(prev.total_production_qty, 10) || 0;
|
||||
const acceptedQty = parseInt(prev.input_qty, 10) || 0;
|
||||
const requestedQty = parseInt(production_qty, 10) || 0;
|
||||
if (acceptedQty > 0 && (prevTotal + requestedQty) > acceptedQty) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `실적 누적(${prevTotal + requestedQty})이 접수량(${acceptedQty})을 초과합니다. 추가 접수 후 등록해주세요.`,
|
||||
logger.warn("[pop/production] 초과 생산 감지", {
|
||||
work_order_process_id,
|
||||
prevTotal, requestedQty, acceptedQty,
|
||||
overAmount: (prevTotal + requestedQty) - acceptedQty,
|
||||
});
|
||||
}
|
||||
|
||||
// 서버 측 양품/불량/특채 계산 (클라이언트 good_qty는 참고만)
|
||||
const addProduction = parseInt(production_qty, 10) || 0;
|
||||
let addDefect = 0;
|
||||
let addConcession = 0;
|
||||
|
||||
let defectDetailStr: string | null = null;
|
||||
if (defect_detail && Array.isArray(defect_detail)) {
|
||||
const validated = defect_detail.map((item: DefectDetailItem) => ({
|
||||
@@ -622,15 +629,23 @@ export const saveResult = async (
|
||||
disposition: item.disposition || "scrap",
|
||||
}));
|
||||
defectDetailStr = JSON.stringify(validated);
|
||||
}
|
||||
|
||||
const addProduction = parseInt(production_qty, 10) || 0;
|
||||
const addGood = parseInt(good_qty, 10) || 0;
|
||||
const addDefect = parseInt(defect_qty, 10) || 0;
|
||||
for (const item of validated) {
|
||||
const itemQty = parseInt(item.qty, 10) || 0;
|
||||
addDefect += itemQty;
|
||||
if (item.disposition === "accept") {
|
||||
addConcession += itemQty;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addDefect = parseInt(defect_qty, 10) || 0;
|
||||
}
|
||||
const addGood = addProduction - addDefect;
|
||||
|
||||
const newTotal = (parseInt(prev.total_production_qty, 10) || 0) + addProduction;
|
||||
const newGood = (parseInt(prev.good_qty, 10) || 0) + addGood;
|
||||
const newDefect = (parseInt(prev.defect_qty, 10) || 0) + addDefect;
|
||||
const newConcession = (parseInt(prev.concession_qty, 10) || 0) + addConcession;
|
||||
|
||||
// 기존 defect_detail에 이번 차수 상세를 병합
|
||||
let mergedDefectDetail: string | null = null;
|
||||
@@ -640,7 +655,6 @@ export const saveResult = async (
|
||||
existingEntries = prev.defect_detail ? JSON.parse(prev.defect_detail) : [];
|
||||
} catch { /* 파싱 실패 시 빈 배열 */ }
|
||||
const newEntries: DefectDetailItem[] = JSON.parse(defectDetailStr);
|
||||
// 같은 불량코드+처리방법 조합은 수량 합산
|
||||
const merged = [...existingEntries];
|
||||
for (const ne of newEntries) {
|
||||
const existing = merged.find(
|
||||
@@ -662,6 +676,7 @@ export const saveResult = async (
|
||||
SET total_production_qty = $3,
|
||||
good_qty = $4,
|
||||
defect_qty = $5,
|
||||
concession_qty = $9,
|
||||
defect_detail = COALESCE($6, defect_detail),
|
||||
result_note = COALESCE($7, result_note),
|
||||
result_status = 'draft',
|
||||
@@ -669,7 +684,7 @@ export const saveResult = async (
|
||||
writer = $8,
|
||||
updated_date = NOW()
|
||||
WHERE id = $1 AND company_code = $2
|
||||
RETURNING id, total_production_qty, good_qty, defect_qty, defect_detail, result_note, result_status, status`,
|
||||
RETURNING id, total_production_qty, good_qty, defect_qty, concession_qty, defect_detail, result_note, result_status, status`,
|
||||
[
|
||||
work_order_process_id,
|
||||
companyCode,
|
||||
@@ -679,6 +694,7 @@ export const saveResult = async (
|
||||
mergedDefectDetail,
|
||||
result_note || null,
|
||||
userId,
|
||||
String(newConcession),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -692,7 +708,9 @@ export const saveResult = async (
|
||||
// 현재 분할 행의 공정 정보 조회
|
||||
const currentSeq = await pool.query(
|
||||
`SELECT wop.seq_no, wop.wo_id, wop.input_qty as current_input_qty,
|
||||
wop.parent_process_id,
|
||||
wop.parent_process_id, wop.process_code, wop.process_name,
|
||||
wop.is_required, wop.is_fixed_order, wop.standard_time,
|
||||
wop.equipment_code, wop.routing_detail_id,
|
||||
wi.qty as instruction_qty
|
||||
FROM work_order_process wop
|
||||
JOIN work_instruction wi ON wop.wo_id = wi.id AND wop.company_code = wi.company_code
|
||||
@@ -700,6 +718,46 @@ export const saveResult = async (
|
||||
[work_order_process_id, companyCode]
|
||||
);
|
||||
|
||||
// 재작업 카드 자동 생성 (disposition = 'rework' 항목이 있을 때)
|
||||
if (currentSeq.rowCount > 0 && defect_detail && Array.isArray(defect_detail)) {
|
||||
let totalReworkQty = 0;
|
||||
for (const item of defect_detail) {
|
||||
if (item.disposition === "rework") {
|
||||
totalReworkQty += parseInt(item.qty, 10) || 0;
|
||||
}
|
||||
}
|
||||
if (totalReworkQty > 0) {
|
||||
const proc = currentSeq.rows[0];
|
||||
const masterId = proc.parent_process_id || work_order_process_id;
|
||||
const reworkInsert = await pool.query(
|
||||
`INSERT INTO work_order_process (
|
||||
wo_id, seq_no, process_code, process_name, is_required, is_fixed_order,
|
||||
standard_time, equipment_code, routing_detail_id,
|
||||
status, input_qty, good_qty, defect_qty, concession_qty, total_production_qty,
|
||||
result_status, is_rework, rework_source_id,
|
||||
parent_process_id, company_code, writer
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9,
|
||||
'acceptable', $10, '0', '0', '0', '0',
|
||||
'draft', 'Y', $11,
|
||||
$12, $13, $14
|
||||
) RETURNING id`,
|
||||
[
|
||||
proc.wo_id, proc.seq_no, proc.process_code, proc.process_name,
|
||||
proc.is_required, proc.is_fixed_order, proc.standard_time,
|
||||
proc.equipment_code, proc.routing_detail_id,
|
||||
String(totalReworkQty), work_order_process_id,
|
||||
masterId, companyCode, userId,
|
||||
]
|
||||
);
|
||||
logger.info("[pop/production] 재작업 카드 자동 생성", {
|
||||
reworkId: reworkInsert.rows[0]?.id,
|
||||
sourceId: work_order_process_id,
|
||||
reworkQty: totalReworkQty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 다음 공정 활성화: 양품 발생 시 다음 seq_no의 원본(마스터) 행을 acceptable로
|
||||
// waiting -> acceptable (최초 활성화)
|
||||
// in_progress -> acceptable (앞공정 추가양품으로 접수가능량 복원)
|
||||
@@ -753,7 +811,7 @@ export const saveResult = async (
|
||||
if (seqNum > 1) {
|
||||
const prevSeq = String(seqNum - 1);
|
||||
const prevProcess = await pool.query(
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) as total_good
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) + COALESCE(SUM(concession_qty::int), 0) as total_good
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[wo_id, prevSeq, companyCode]
|
||||
@@ -799,6 +857,10 @@ export const saveResult = async (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 작업지시 전체 완료 판정
|
||||
const { wo_id: woIdForWi } = currentSeq.rows[0];
|
||||
await checkAndCompleteWorkInstruction(pool, woIdForWi, companyCode, userId);
|
||||
}
|
||||
|
||||
logger.info("[pop/production] save-result 완료 (누적)", {
|
||||
@@ -810,7 +872,7 @@ export const saveResult = async (
|
||||
|
||||
// 자동 완료 후 최신 데이터 반환 (status가 변경되었을 수 있음)
|
||||
const latestData = await pool.query(
|
||||
`SELECT id, total_production_qty, good_qty, defect_qty, defect_detail, result_note, result_status, status, input_qty
|
||||
`SELECT id, total_production_qty, good_qty, defect_qty, concession_qty, defect_detail, result_note, result_status, status, input_qty
|
||||
FROM work_order_process WHERE id = $1 AND company_code = $2`,
|
||||
[work_order_process_id, companyCode]
|
||||
);
|
||||
@@ -828,6 +890,63 @@ export const saveResult = async (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업지시(work_instruction) 전체 완료 판정
|
||||
* 마지막 공정의 모든 행이 completed이면 작업지시도 완료 처리
|
||||
*/
|
||||
const checkAndCompleteWorkInstruction = async (
|
||||
pool: any,
|
||||
woId: string,
|
||||
companyCode: string,
|
||||
userId: string
|
||||
) => {
|
||||
const maxSeqResult = await pool.query(
|
||||
`SELECT MAX(seq_no::int) as max_seq
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND company_code = $2`,
|
||||
[woId, companyCode]
|
||||
);
|
||||
|
||||
if (maxSeqResult.rowCount === 0 || !maxSeqResult.rows[0].max_seq) return;
|
||||
|
||||
const maxSeq = String(maxSeqResult.rows[0].max_seq);
|
||||
|
||||
const incompleteCheck = await pool.query(
|
||||
`SELECT COUNT(*) as cnt
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3
|
||||
AND status != 'completed'`,
|
||||
[woId, maxSeq, companyCode]
|
||||
);
|
||||
|
||||
if (parseInt(incompleteCheck.rows[0].cnt, 10) > 0) return;
|
||||
|
||||
const totalGoodResult = await pool.query(
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) + COALESCE(SUM(concession_qty::int), 0) as total_good
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[woId, maxSeq, companyCode]
|
||||
);
|
||||
|
||||
const completedQty = totalGoodResult.rows[0].total_good;
|
||||
|
||||
await pool.query(
|
||||
`UPDATE work_instruction
|
||||
SET status = 'completed',
|
||||
progress_status = 'completed',
|
||||
completed_qty = $3,
|
||||
writer = $4,
|
||||
updated_date = NOW()
|
||||
WHERE id = $1 AND company_code = $2
|
||||
AND status != 'completed'`,
|
||||
[woId, companyCode, String(completedQty), userId]
|
||||
);
|
||||
|
||||
logger.info("[pop/production] 작업지시 전체 완료", {
|
||||
woId, completedQty, companyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 실적 확정은 더 이상 단일 확정이 아님.
|
||||
* 마지막 등록 확인 용도로 유지. 실적은 save-result에서 차수별로 쌓임.
|
||||
@@ -874,58 +993,18 @@ export const confirmResult = async (
|
||||
});
|
||||
}
|
||||
|
||||
// 잔여 접수가능량 계산하여 completed 여부 결정
|
||||
const seqCheck = await pool.query(
|
||||
`SELECT wop.seq_no, wop.wo_id, wop.input_qty,
|
||||
wi.qty as instruction_qty
|
||||
FROM work_order_process wop
|
||||
JOIN work_instruction wi ON wop.wo_id = wi.id AND wop.company_code = wi.company_code
|
||||
WHERE wop.id = $1 AND wop.company_code = $2`,
|
||||
[work_order_process_id, companyCode]
|
||||
);
|
||||
|
||||
let shouldComplete = false;
|
||||
if (seqCheck.rowCount > 0) {
|
||||
const { seq_no, wo_id, input_qty: currentInputQty, instruction_qty } = seqCheck.rows[0];
|
||||
const seqNum = parseInt(seq_no, 10);
|
||||
const myInputQty = parseInt(currentInputQty, 10) || 0;
|
||||
const instrQty = parseInt(instruction_qty, 10) || 0;
|
||||
|
||||
let prevGoodQty = instrQty;
|
||||
if (seqNum > 1) {
|
||||
const prevSeq = String(seqNum - 1);
|
||||
const prevProcess = await pool.query(
|
||||
`SELECT COALESCE(good_qty::int, 0) as good_qty
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[wo_id, prevSeq, companyCode]
|
||||
);
|
||||
if (prevProcess.rowCount > 0) {
|
||||
prevGoodQty = prevProcess.rows[0].good_qty;
|
||||
}
|
||||
}
|
||||
|
||||
const remainingAcceptable = prevGoodQty - myInputQty;
|
||||
const totalProduced = parseInt(currentProcess.total_production_qty, 10) || 0;
|
||||
shouldComplete = remainingAcceptable <= 0 && totalProduced >= myInputQty && myInputQty > 0;
|
||||
}
|
||||
|
||||
// 수동 확정: shouldComplete 여부와 관계없이 completed 처리
|
||||
// 자동 완료가 안 된 경우 관리자가 강제 완료할 때 사용
|
||||
const newStatus = "completed";
|
||||
|
||||
const isCompleted = shouldComplete;
|
||||
// 수동 확정: 무조건 completed 처리 (수동 완료 용도)
|
||||
const result = await pool.query(
|
||||
`UPDATE work_order_process
|
||||
SET result_status = 'confirmed',
|
||||
status = $4,
|
||||
completed_at = CASE WHEN $5 THEN NOW()::text ELSE completed_at END,
|
||||
completed_by = CASE WHEN $5 THEN $3 ELSE completed_by END,
|
||||
status = 'completed',
|
||||
completed_at = NOW()::text,
|
||||
completed_by = $3,
|
||||
writer = $3,
|
||||
updated_date = NOW()
|
||||
WHERE id = $1 AND company_code = $2
|
||||
RETURNING id, status, result_status, total_production_qty, good_qty, defect_qty`,
|
||||
[work_order_process_id, companyCode, userId, newStatus, isCompleted]
|
||||
[work_order_process_id, companyCode, userId]
|
||||
);
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
@@ -935,25 +1014,92 @@ export const confirmResult = async (
|
||||
});
|
||||
}
|
||||
|
||||
// completed로 전환된 경우에만 다음 공정 활성화
|
||||
if (shouldComplete && seqCheck.rowCount > 0) {
|
||||
const { seq_no, wo_id } = seqCheck.rows[0];
|
||||
const nextSeq = String(parseInt(seq_no, 10) + 1);
|
||||
await pool.query(
|
||||
`UPDATE work_order_process
|
||||
SET status = CASE WHEN status = 'waiting' THEN 'acceptable' ELSE status END,
|
||||
updated_date = NOW()
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[wo_id, nextSeq, companyCode]
|
||||
);
|
||||
// 공정 정보 조회 (다음 공정 활성화 + 마스터 캐스케이드용)
|
||||
const seqCheck = await pool.query(
|
||||
`SELECT wop.seq_no, wop.wo_id, wop.parent_process_id,
|
||||
wi.qty as instruction_qty
|
||||
FROM work_order_process wop
|
||||
JOIN work_instruction wi ON wop.wo_id = wi.id AND wop.company_code = wi.company_code
|
||||
WHERE wop.id = $1 AND wop.company_code = $2`,
|
||||
[work_order_process_id, companyCode]
|
||||
);
|
||||
|
||||
if (seqCheck.rowCount > 0) {
|
||||
const { seq_no, wo_id, parent_process_id, instruction_qty } = seqCheck.rows[0];
|
||||
const seqNum = parseInt(seq_no, 10);
|
||||
const instrQty = parseInt(instruction_qty, 10) || 0;
|
||||
|
||||
// 다음 공정 활성화 (양품이 있으면)
|
||||
const goodQty = parseInt(result.rows[0].good_qty, 10) || 0;
|
||||
if (goodQty > 0) {
|
||||
const nextSeq = String(seqNum + 1);
|
||||
await pool.query(
|
||||
`UPDATE work_order_process
|
||||
SET status = CASE WHEN status IN ('waiting', 'in_progress') THEN 'acceptable' ELSE status END,
|
||||
updated_date = NOW()
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3
|
||||
AND parent_process_id IS NULL
|
||||
AND status != 'completed'`,
|
||||
[wo_id, nextSeq, companyCode]
|
||||
);
|
||||
}
|
||||
|
||||
// 마스터 자동완료 캐스케이드 (분할 행인 경우)
|
||||
if (parent_process_id) {
|
||||
let prevGoodQty = instrQty;
|
||||
if (seqNum > 1) {
|
||||
const prevSeq = String(seqNum - 1);
|
||||
const prevProcess = await pool.query(
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) + COALESCE(SUM(concession_qty::int), 0) as total_good
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[wo_id, prevSeq, companyCode]
|
||||
);
|
||||
if (prevProcess.rowCount > 0) {
|
||||
prevGoodQty = prevProcess.rows[0].total_good;
|
||||
}
|
||||
}
|
||||
|
||||
const siblingCheck = await pool.query(
|
||||
`SELECT
|
||||
COALESCE(SUM(input_qty::int), 0) as total_input,
|
||||
COUNT(*) FILTER (WHERE status != 'completed') as incomplete_count
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3
|
||||
AND parent_process_id IS NOT NULL`,
|
||||
[wo_id, seq_no, companyCode]
|
||||
);
|
||||
|
||||
const totalInput = siblingCheck.rows[0].total_input;
|
||||
const incompleteCount = parseInt(siblingCheck.rows[0].incomplete_count, 10);
|
||||
const remainingAcceptable = prevGoodQty - totalInput;
|
||||
|
||||
if (incompleteCount === 0 && remainingAcceptable <= 0) {
|
||||
await pool.query(
|
||||
`UPDATE work_order_process
|
||||
SET status = 'completed',
|
||||
result_status = 'confirmed',
|
||||
completed_at = NOW()::text,
|
||||
completed_by = $3,
|
||||
updated_date = NOW()
|
||||
WHERE id = $1 AND company_code = $2
|
||||
AND status != 'completed'`,
|
||||
[parent_process_id, companyCode, userId]
|
||||
);
|
||||
logger.info("[pop/production] confirmResult: 마스터 자동 완료", {
|
||||
masterId: parent_process_id, totalInput, prevGoodQty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 작업지시 전체 완료 판정
|
||||
await checkAndCompleteWorkInstruction(pool, wo_id, companyCode, userId);
|
||||
}
|
||||
|
||||
logger.info("[pop/production] confirm-result 완료", {
|
||||
companyCode,
|
||||
work_order_process_id,
|
||||
userId,
|
||||
shouldComplete,
|
||||
newStatus,
|
||||
finalStatus: result.rows[0].status,
|
||||
});
|
||||
|
||||
@@ -1105,12 +1251,12 @@ export const getAvailableQty = async (req: AuthenticatedRequest, res: Response)
|
||||
);
|
||||
const myInputQty = totalAccepted.rows[0].total_input;
|
||||
|
||||
// 앞공정 양품 합산
|
||||
// 앞공정 양품+특채 합산
|
||||
let prevGoodQty = instrQty;
|
||||
if (seqNum > 1) {
|
||||
const prevSeq = String(seqNum - 1);
|
||||
const prevProcess = await pool.query(
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) as total_good
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) + COALESCE(SUM(concession_qty::int), 0) as total_good
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[wo_id, prevSeq, companyCode]
|
||||
@@ -1215,12 +1361,12 @@ export const acceptProcess = async (req: AuthenticatedRequest, res: Response) =>
|
||||
);
|
||||
const currentTotalInput = totalAccepted.rows[0].total_input;
|
||||
|
||||
// 앞공정 양품 합산
|
||||
// 앞공정 양품+특채 합산
|
||||
let prevGoodQty = instrQty;
|
||||
if (seqNum > 1) {
|
||||
const prevSeq = String(seqNum - 1);
|
||||
const prevProcess = await pool.query(
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) as total_good
|
||||
`SELECT COALESCE(SUM(good_qty::int), 0) + COALESCE(SUM(concession_qty::int), 0) as total_good
|
||||
FROM work_order_process
|
||||
WHERE wo_id = $1 AND seq_no = $2 AND company_code = $3`,
|
||||
[row.wo_id, prevSeq, companyCode]
|
||||
|
||||
Reference in New Issue
Block a user