/** * 외주발주관리 컨트롤러 (TASK:ERP-019) * * 라우트: * GET /outsource-purchase 목록 * GET /outsource-purchase/auto-processes 공정 자동표기 헬퍼 * GET /outsource-purchase/:id 상세 * POST /outsource-purchase 등록 * PUT /outsource-purchase/:id 수정 * DELETE /outsource-purchase/:id 삭제 * POST /outsource-purchase/release-request 사급자재 출고요청 */ import { Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; import * as svc from "../services/outsourcePurchaseService"; import { logger } from "../utils/logger"; function ok(res: Response, data: any, message = "성공") { return res.json({ success: true, data, message }); } function fail(res: Response, status: number, message: string, error?: any) { if (error) logger.error(message, { error: error?.message }); return res.status(status).json({ success: false, message }); } // ───────────────────────────────────────────────────────────────────────────── // 목록 // ───────────────────────────────────────────────────────────────────────────── export async function listOutsourceOrders( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const opts: svc.ListFilter = { keyword: req.query.keyword as string | undefined, status: req.query.status as string | undefined, source_type: req.query.source_type as string | undefined, date_from: req.query.date_from as string | undefined, date_to: req.query.date_to as string | undefined, page: req.query.page ? parseInt(req.query.page as string, 10) : 1, size: req.query.size !== undefined ? parseInt(req.query.size as string, 10) : 50, sort: req.query.sort as string | undefined, }; const data = await svc.listOrders(companyCode, opts); return ok(res, data); } catch (e: any) { return fail(res, 500, e?.message || "외주발주 목록 조회 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 상세 // ───────────────────────────────────────────────────────────────────────────── export async function getOutsourceOrderDetail( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const id = req.params.id; if (!id) return fail(res, 400, "id 파라미터가 필요합니다"); const data = await svc.getOrderDetail(companyCode, id); if (!data) return fail(res, 404, "외주발주를 찾을 수 없습니다"); return ok(res, data); } catch (e: any) { return fail(res, 500, e?.message || "외주발주 상세 조회 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 등록 // ───────────────────────────────────────────────────────────────────────────── export async function createOutsourceOrder( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const userId = req.user!.userId || "system"; const payload = req.body as svc.OPOInput; if (!payload || !payload.source_type) { return fail(res, 400, "source_type은 필수입니다"); } const data = await svc.createOrder(companyCode, userId, payload); return ok(res, data, "외주발주 등록 완료"); } catch (e: any) { return fail(res, 500, e?.message || "외주발주 등록 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 수정 // ───────────────────────────────────────────────────────────────────────────── export async function updateOutsourceOrder( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const userId = req.user!.userId || "system"; const id = req.params.id; if (!id) return fail(res, 400, "id 파라미터가 필요합니다"); const data = await svc.updateOrder( companyCode, userId, id, req.body as svc.OPOInput ); return ok(res, data, "외주발주 수정 완료"); } catch (e: any) { return fail(res, 500, e?.message || "외주발주 수정 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 삭제 // ───────────────────────────────────────────────────────────────────────────── export async function deleteOutsourceOrder( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const id = req.params.id; if (!id) return fail(res, 400, "id 파라미터가 필요합니다"); const data = await svc.deleteOrder(companyCode, id); return ok(res, data, "외주발주 삭제 완료"); } catch (e: any) { return fail(res, 400, e?.message || "외주발주 삭제 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 사급자재 출고요청 // ───────────────────────────────────────────────────────────────────────────── export async function requestRelease( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const userId = req.user!.userId || "system"; const payload = req.body as svc.ReleaseRequestPayload; if ( !payload || !Array.isArray(payload.material_ids) || payload.material_ids.length === 0 ) { return fail(res, 400, "material_ids 배열이 필요합니다"); } const data = await svc.requestRelease(companyCode, userId, payload); return ok(res, data, "사급자재 출고요청 완료"); } catch (e: any) { return fail(res, 400, e?.message || "사급자재 출고요청 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 공정 자동표기 (헬퍼) // (TASK:ERP-019 재구현 — work_order_id 우선, item_code는 폴백) // ───────────────────────────────────────────────────────────────────────────── export async function autoProcesses( req: AuthenticatedRequest, res: Response ) { try { const companyCode = req.user!.companyCode; const itemCode = req.query.item_code as string | undefined; const workOrderId = req.query.work_order_id as string | undefined; if (!workOrderId && !itemCode) { return fail(res, 400, "work_order_id 또는 item_code 중 하나는 필수입니다"); } const data = await svc.autoSelectProcesses(companyCode, workOrderId, itemCode); return ok(res, data); } catch (e: any) { return fail(res, 500, e?.message || "공정 자동표기 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 외주발주번호 미리보기 — 모달 진입 시 input 자동 채움용 (시퀀스 증가 없음) // ───────────────────────────────────────────────────────────────────────────── export async function previewOrderNo( req: AuthenticatedRequest, res: Response, ) { try { const companyCode = req.user!.companyCode; const code = await svc.previewOrderNo(companyCode); return ok(res, { generatedCode: code || "" }); } catch (e: any) { return fail(res, 500, e?.message || "외주발주번호 미리보기 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 공정 자재투입(material_input) 자동 채움 // — 사급자재 체크 시 해당 공정의 자재 목록을 외주발주 자재 형식으로 반환 // ───────────────────────────────────────────────────────────────────────────── export async function getProcessMaterials( req: AuthenticatedRequest, res: Response, ) { try { const companyCode = req.user!.companyCode; const workOrderId = req.query.work_order_id as string | undefined; const routingDetailId = req.query.routing_detail_id as string | undefined; if (!routingDetailId) { return fail(res, 400, "routing_detail_id는 필수입니다"); } const data = await svc.getProcessMaterialInputs( companyCode, workOrderId, routingDetailId, ); return ok(res, data); } catch (e: any) { return fail(res, 500, e?.message || "공정 자재투입 조회 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 외주발주현황 통합 조회 (TASK:ERP-025) // ───────────────────────────────────────────────────────────────────────────── export async function listOrderStatus( req: AuthenticatedRequest, res: Response, ) { try { const companyCode = req.user!.companyCode; const opts: svc.OrderStatusFilter = { keyword: req.query.keyword as string | undefined, source_type: req.query.source_type as string | undefined, order_status: req.query.order_status as string | undefined, release_status: req.query.release_status as string | undefined, date_from: req.query.date_from as string | undefined, date_to: req.query.date_to as string | undefined, }; const data = await svc.listOrderStatus(companyCode, opts); return ok(res, data); } catch (e: any) { return fail(res, 500, e?.message || "외주발주현황 조회 실패", e); } } // ───────────────────────────────────────────────────────────────────────────── // 외주발주 가능 작업지시 목록 // (TASK:ERP-019 재구현 — 좌측 리스트 필터) // ───────────────────────────────────────────────────────────────────────────── export async function listOutsourceableWorkOrders( req: AuthenticatedRequest, res: Response, ) { try { const companyCode = req.user!.companyCode; const opts: svc.OutsourceableListFilter = { keyword: req.query.keyword as string | undefined, page: req.query.page ? parseInt(req.query.page as string, 10) : 1, size: req.query.size !== undefined ? parseInt(req.query.size as string, 10) : 50, }; const data = await svc.listOutsourceableWorkOrders(companyCode, opts); return ok(res, data); } catch (e: any) { return fail(res, 500, e?.message || "외주발주 가능 작업지시 조회 실패", e); } }