- Added a new endpoint `listOrderStatus` in the `outsourcePurchaseController` to retrieve integrated order status information, including filtering options for source type, order status, and date range. - Updated the `outsourcePurchaseService` to handle the new order status retrieval logic, ensuring proper filtering and data aggregation. - Introduced a new route for accessing the order status information in `outsourcePurchaseRoutes`. - Created a detailed modal for viewing outsourcing purchase order details, enhancing the user interface for better data presentation. - Developed a registration modal for creating and editing outsourcing purchase orders, featuring a tabbed interface for improved user experience. (TASK: ERP-025, ERP-019)
272 lines
13 KiB
TypeScript
272 lines
13 KiB
TypeScript
/**
|
|
* 외주발주관리 컨트롤러 (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);
|
|
}
|
|
}
|