fix: MES 체크리스트 자동 복사 + 구조적 버그 3건 수정
분할 접수/재작업 카드 생성 시 체크리스트가 복사되지 않던 문제를 해결하고, 공정 흐름 모달과 카드 클릭 이벤트 격리 버그를 수정한다. [체크리스트 자동 복사] - copyChecklistToSplit 공통 헬퍼 함수 추출 (createWorkProcesses/acceptProcess/saveResult 3곳에서 통합 사용) - routing_detail_id 존재 시: process_work_item 템플릿에서 복사 - routing_detail_id 부재 시: 마스터 process_work_result에서 구조만 복사 [ChecklistItem 렌더링 수정] - 레거시 detail_type(inspect_numeric/inspect_ox 등)을 정규화하여 detail_type=inspect + input_type 자동 파싱으로 InspectRouter 라우팅 - info 타입 렌더러 추가 [공정 흐름 모달 이벤트 격리] - Dialog 래퍼에 onClick/onPointerDown stopPropagation 추가 (모달 닫기 시 부모 카드 onClick 전파 차단) [카드 클릭 진행 탭 제한] - handleCardSelect에 VIRTUAL_SUB_STATUS 코드 가드 추가 (in_progress가 아닌 카드는 작업상세 모달 열림 차단) - 재작업 카드 포함 접수가능 탭에서 상세 진입 방지
This commit is contained in:
@@ -11,6 +11,79 @@ interface DefectDetailItem {
|
||||
disposition: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 체크리스트 복사 공통 함수
|
||||
* 분할 행/재작업 카드 생성 시 마스터의 체크리스트를 새 행에 복사한다.
|
||||
*
|
||||
* 전략: routingDetailId가 있으면 원본 템플릿에서, 없으면 마스터의 기존 결과에서 복사
|
||||
*/
|
||||
async function copyChecklistToSplit(
|
||||
client: { query: (text: string, values?: any[]) => Promise<any> },
|
||||
masterProcessId: string,
|
||||
newProcessId: string,
|
||||
routingDetailId: string | null,
|
||||
companyCode: string,
|
||||
userId: string
|
||||
): Promise<number> {
|
||||
// A. routing_detail_id가 있으면 원본 템플릿(process_work_item + detail)에서 복사
|
||||
if (routingDetailId) {
|
||||
const result = await client.query(
|
||||
`INSERT INTO process_work_result (
|
||||
company_code, work_order_process_id,
|
||||
source_work_item_id, source_detail_id,
|
||||
work_phase, item_title, item_sort_order,
|
||||
detail_content, detail_type, detail_sort_order, is_required,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
input_type, lookup_target, display_fields, duration_minutes,
|
||||
status, writer
|
||||
)
|
||||
SELECT
|
||||
pwi.company_code, $1,
|
||||
pwi.id, pwd.id,
|
||||
pwi.work_phase, pwi.title, pwi.sort_order::text,
|
||||
pwd.content, pwd.detail_type, pwd.sort_order::text, pwd.is_required,
|
||||
pwd.inspection_code, pwd.inspection_method, pwd.unit, pwd.lower_limit, pwd.upper_limit,
|
||||
pwd.input_type, pwd.lookup_target, pwd.display_fields, pwd.duration_minutes::text,
|
||||
'pending', $2
|
||||
FROM process_work_item pwi
|
||||
JOIN process_work_item_detail pwd ON pwd.work_item_id = pwi.id
|
||||
AND pwd.company_code = pwi.company_code
|
||||
WHERE pwi.routing_detail_id = $3
|
||||
AND pwi.company_code = $4
|
||||
ORDER BY pwi.sort_order, pwd.sort_order`,
|
||||
[newProcessId, userId, routingDetailId, companyCode]
|
||||
);
|
||||
return result.rowCount ?? 0;
|
||||
}
|
||||
|
||||
// B. routing_detail_id가 없으면 마스터 행의 process_work_result에서 구조만 복사 (타이머/결과값 초기화)
|
||||
const result = await client.query(
|
||||
`INSERT INTO process_work_result (
|
||||
company_code, work_order_process_id,
|
||||
source_work_item_id, source_detail_id,
|
||||
work_phase, item_title, item_sort_order,
|
||||
detail_content, detail_type, detail_sort_order, is_required,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
input_type, lookup_target, display_fields, duration_minutes,
|
||||
status, writer
|
||||
)
|
||||
SELECT
|
||||
company_code, $1,
|
||||
source_work_item_id, source_detail_id,
|
||||
work_phase, item_title, item_sort_order,
|
||||
detail_content, detail_type, detail_sort_order, is_required,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
input_type, lookup_target, display_fields, duration_minutes,
|
||||
'pending', $2
|
||||
FROM process_work_result
|
||||
WHERE work_order_process_id = $3
|
||||
AND company_code = $4
|
||||
ORDER BY item_sort_order, detail_sort_order`,
|
||||
[newProcessId, userId, masterProcessId, companyCode]
|
||||
);
|
||||
return result.rowCount ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* D-BE1: 작업지시 공정 일괄 생성
|
||||
* PC에서 작업지시 생성 후 호출. 1 트랜잭션으로 work_order_process + process_work_result 일괄 생성.
|
||||
@@ -117,36 +190,10 @@ export const createWorkProcesses = async (
|
||||
);
|
||||
const wopId = wopResult.rows[0].id;
|
||||
|
||||
// 3. process_work_result INSERT (스냅샷 복사)
|
||||
// process_work_item + process_work_item_detail에서 해당 routing_detail의 항목 조회 후 복사
|
||||
const snapshotResult = await client.query(
|
||||
`INSERT INTO process_work_result (
|
||||
company_code, work_order_process_id,
|
||||
source_work_item_id, source_detail_id,
|
||||
work_phase, item_title, item_sort_order,
|
||||
detail_content, detail_type, detail_sort_order, is_required,
|
||||
inspection_code, inspection_method, unit, lower_limit, upper_limit,
|
||||
input_type, lookup_target, display_fields, duration_minutes,
|
||||
status, writer
|
||||
)
|
||||
SELECT
|
||||
pwi.company_code, $1,
|
||||
pwi.id, pwd.id,
|
||||
pwi.work_phase, pwi.title, pwi.sort_order::text,
|
||||
pwd.content, pwd.detail_type, pwd.sort_order::text, pwd.is_required,
|
||||
pwd.inspection_code, pwd.inspection_method, pwd.unit, pwd.lower_limit, pwd.upper_limit,
|
||||
pwd.input_type, pwd.lookup_target, pwd.display_fields, pwd.duration_minutes::text,
|
||||
'pending', $2
|
||||
FROM process_work_item pwi
|
||||
JOIN process_work_item_detail pwd ON pwd.work_item_id = pwi.id
|
||||
AND pwd.company_code = pwi.company_code
|
||||
WHERE pwi.routing_detail_id = $3
|
||||
AND pwi.company_code = $4
|
||||
ORDER BY pwi.sort_order, pwd.sort_order`,
|
||||
[wopId, userId, rd.id, companyCode]
|
||||
// 3. process_work_result INSERT (공통 함수로 체크리스트 복사)
|
||||
const checklistCount = await copyChecklistToSplit(
|
||||
client, wopId, wopId, rd.id, companyCode, userId
|
||||
);
|
||||
|
||||
const checklistCount = snapshotResult.rowCount ?? 0;
|
||||
totalChecklists += checklistCount;
|
||||
|
||||
processes.push({
|
||||
@@ -750,11 +797,19 @@ export const saveResult = async (
|
||||
masterId, companyCode, userId,
|
||||
]
|
||||
);
|
||||
logger.info("[pop/production] 재작업 카드 자동 생성", {
|
||||
reworkId: reworkInsert.rows[0]?.id,
|
||||
sourceId: work_order_process_id,
|
||||
reworkQty: totalReworkQty,
|
||||
});
|
||||
// 재작업 카드에 체크리스트 복사
|
||||
const reworkId = reworkInsert.rows[0]?.id;
|
||||
if (reworkId) {
|
||||
const reworkChecklistCount = await copyChecklistToSplit(
|
||||
pool, masterId, reworkId, proc.routing_detail_id, companyCode, userId
|
||||
);
|
||||
logger.info("[pop/production] 재작업 카드 자동 생성", {
|
||||
reworkId,
|
||||
sourceId: work_order_process_id,
|
||||
reworkQty: totalReworkQty,
|
||||
checklistCount: reworkChecklistCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1406,16 +1461,22 @@ export const acceptProcess = async (req: AuthenticatedRequest, res: Response) =>
|
||||
]
|
||||
);
|
||||
|
||||
// 분할 행에 체크리스트 복사 (마스터의 routing_detail_id 또는 마스터의 기존 체크리스트에서)
|
||||
const splitId = result.rows[0].id;
|
||||
const checklistCount = await copyChecklistToSplit(
|
||||
pool, masterId, splitId, row.routing_detail_id, companyCode, userId
|
||||
);
|
||||
|
||||
// 마스터는 항상 acceptable 유지 (completed 전환은 saveResult에서 처리)
|
||||
// availableQty=0이면 프론트에서 접수 버튼이 숨겨지므로 상태 변경 불필요
|
||||
const newTotalInput = currentTotalInput + qty;
|
||||
|
||||
logger.info("[pop/production] accept-process 분할 접수 완료", {
|
||||
companyCode, userId, masterId,
|
||||
splitId: result.rows[0].id,
|
||||
splitId,
|
||||
acceptedQty: qty,
|
||||
totalAccepted: newTotalInput,
|
||||
prevGoodQty,
|
||||
checklistCount,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
|
||||
Reference in New Issue
Block a user