feat(pop): implement process list and result retrieval endpoints

- Added `getProcessList` to retrieve the list of work order processes along with their results, grouped by process ID.
- Implemented `getProcessResult` to fetch details of a specific work order process result by its ID.
- Updated `popProductionRoutes` to enable the new endpoints for accessing process data.

These additions enhance the functionality of the POP production module, allowing for better tracking and management of work order processes and their results.
This commit is contained in:
kjs
2026-04-24 18:48:43 +09:00
parent 5518288f18
commit 5f4838cb19
2 changed files with 169 additions and 4 deletions

View File

@@ -3572,3 +3572,168 @@ export const getChecklistItems = async (
return res.status(500).json({ success: false, message: error.message });
}
};
/**
* 작업공정 목록 조회 (POP 공정실행 화면)
* GET /api/pop/production/processes
*
* 응답: WorkOrderProcessRaw[]
* - work_order_process (기준정보) + work_order_process_result (실적 배열)
* - plan_qty는 work_instruction_detail.qty에서 가져옴
*/
export const getProcessList = async (
req: AuthenticatedRequest,
res: Response,
) => {
const pool = getPool();
try {
const companyCode = req.user!.companyCode;
// 1) 작업공정 기준 목록
const wopRes = await pool.query(
`SELECT
p.id, p.wo_id, p.seq_no, p.process_code, p.process_name,
p.parent_process_id, p.routing_detail_id, p.batch_id,
p.target_warehouse_id, p.target_location_code, p.use_default_warehouse,
p.standard_time, p.is_required, p.is_fixed_order, p.remark,
p.created_date,
COALESCE(NULLIF(p.plan_qty,''), NULL)::numeric AS plan_qty_base,
wid.qty AS wi_detail_qty
FROM work_order_process p
LEFT JOIN work_instruction_detail wid ON wid.id = p.wo_id
WHERE p.company_code = $1
ORDER BY p.created_date DESC, p.seq_no ASC`,
[companyCode],
);
const wops = wopRes.rows;
if (wops.length === 0) {
return res.json({ success: true, data: [] });
}
// 2) 실적(wop_result) 조회 — accepted_results 배열로 그룹핑
const wopIds = wops.map((w: any) => w.id);
const resRes = await pool.query(
`SELECT
id, wop_id, seq, status, result_status,
input_qty, good_qty, defect_qty, concession_qty, total_production_qty,
is_rework, rework_source_id, accepted_by, accepted_at,
started_at, completed_at, equipment_code, batch_id
FROM work_order_process_result
WHERE wop_id = ANY($1::text[])
ORDER BY wop_id, seq ASC`,
[wopIds],
);
const resultsByWop = new Map<string, any[]>();
for (const r of resRes.rows) {
const arr = resultsByWop.get(r.wop_id) || [];
arr.push(r);
resultsByWop.set(r.wop_id, arr);
}
// 3) 대표 상태/수량은 첫 실적 기준으로 매핑 (프론트는 accepted_results도 별도 순회)
const data = wops.map((w: any) => {
const results = resultsByWop.get(w.id) || [];
const first = results[0] || {};
return {
id: w.id,
wo_id: w.wo_id,
seq_no: w.seq_no,
process_code: w.process_code,
process_name: w.process_name,
status: first.status || "waiting",
result_status: first.result_status || null,
plan_qty: w.plan_qty_base ?? w.wi_detail_qty ?? null,
input_qty: first.input_qty ?? null,
good_qty: first.good_qty ?? null,
defect_qty: first.defect_qty ?? null,
concession_qty: first.concession_qty ?? null,
total_production_qty: first.total_production_qty ?? null,
parent_process_id: w.parent_process_id ?? null,
is_rework: first.is_rework ?? null,
rework_source_id: first.rework_source_id ?? null,
started_at: first.started_at ?? null,
completed_at: first.completed_at ?? null,
accepted_by: first.accepted_by ?? null,
accepted_at: first.accepted_at ?? null,
created_date: w.created_date ?? null,
batch_id: first.batch_id ?? w.batch_id ?? null,
equipment_code: first.equipment_code ?? null,
// Phase C 계산 필드는 별도 엔드포인트(available-qty 등)에서 제공
available_qty: null,
prev_good_qty: null,
my_input_qty: null,
rework_available_qty: null,
split_no: null,
split_total: null,
batch_count: results.length,
batch_list: null,
batch_index: null,
accepted_results: results.map((r: any) => ({
id: r.id,
seq: r.seq,
status: r.status,
result_status: r.result_status,
input_qty: r.input_qty,
good_qty: r.good_qty,
defect_qty: r.defect_qty,
concession_qty: r.concession_qty,
total_production_qty: r.total_production_qty,
is_rework: r.is_rework,
rework_source_id: r.rework_source_id,
accepted_by: r.accepted_by,
accepted_at: r.accepted_at,
started_at: r.started_at,
completed_at: r.completed_at,
equipment_code: r.equipment_code,
batch_id: r.batch_id,
})),
};
});
return res.json({ success: true, data });
} catch (error: any) {
logger.error("[pop/production] processes 조회 오류:", error);
return res.status(500).json({ success: false, message: error.message });
}
};
/**
* 단일 작업공정 결과 조회
* GET /api/pop/production/result/:id
* :id = work_order_process_result.id (wop_result row 식별자)
*/
export const getProcessResult = async (
req: AuthenticatedRequest,
res: Response,
) => {
const pool = getPool();
try {
const companyCode = req.user!.companyCode;
const { id } = req.params;
if (!id) {
return res.status(400).json({ success: false, message: "id는 필수입니다." });
}
const r = await pool.query(
`SELECT r.*, p.process_code, p.process_name, p.wo_id, p.seq_no
FROM work_order_process_result r
JOIN work_order_process p ON p.id = r.wop_id
WHERE r.id = $1 AND r.company_code = $2
LIMIT 1`,
[id, companyCode],
);
if (r.rowCount === 0) {
return res
.status(404)
.json({ success: false, message: "결과를 찾을 수 없습니다." });
}
return res.json({ success: true, data: r.rows[0] });
} catch (error: any) {
logger.error("[pop/production] result 조회 오류:", error);
return res.status(500).json({ success: false, message: error.message });
}
};