/** * 생산계획 컨트롤러 */ import { Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; import * as productionService from "../services/productionPlanService"; import { logger } from "../utils/logger"; // ─── 수주 데이터 조회 (품목별 그룹핑) ─── export async function getOrderSummary(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const { excludePlanned, itemCode, itemName } = req.query; const data = await productionService.getOrderSummary(companyCode, { excludePlanned: excludePlanned === "true", itemCode: itemCode as string, itemName: itemName as string, }); return res.json({ success: true, data }); } catch (error: any) { logger.error("수주 데이터 조회 실패", { error: error.message }); return res.status(500).json({ success: false, message: error.message }); } } // ─── 안전재고 부족분 조회 ─── export async function getStockShortage(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const data = await productionService.getStockShortage(companyCode); return res.json({ success: true, data }); } catch (error: any) { logger.error("안전재고 부족분 조회 실패", { error: error.message }); return res.status(500).json({ success: false, message: error.message }); } } // ─── 생산계획 상세 조회 ─── export async function getPlanById(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const planId = parseInt(req.params.id, 10); const data = await productionService.getPlanById(companyCode, planId); if (!data) { return res.status(404).json({ success: false, message: "생산계획을 찾을 수 없습니다" }); } return res.json({ success: true, data }); } catch (error: any) { logger.error("생산계획 조회 실패", { error: error.message }); return res.status(500).json({ success: false, message: error.message }); } } // ─── 생산계획 수정 ─── export async function updatePlan(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const planId = parseInt(req.params.id, 10); const updatedBy = req.user!.userId; const data = await productionService.updatePlan(companyCode, planId, req.body, updatedBy); return res.json({ success: true, data }); } catch (error: any) { logger.error("생산계획 수정 실패", { error: error.message }); return res.status(error.message.includes("찾을 수 없") ? 404 : 500).json({ success: false, message: error.message, }); } } // ─── 생산계획 삭제 ─── export async function deletePlan(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const planId = parseInt(req.params.id, 10); await productionService.deletePlan(companyCode, planId); return res.json({ success: true, message: "삭제되었습니다" }); } catch (error: any) { logger.error("생산계획 삭제 실패", { error: error.message }); return res.status(error.message.includes("찾을 수 없") ? 404 : 500).json({ success: false, message: error.message, }); } } // ─── 자동 스케줄 생성 ─── export async function generateSchedule(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const createdBy = req.user!.userId; const { items, options } = req.body; if (!items || !Array.isArray(items) || items.length === 0) { return res.status(400).json({ success: false, message: "품목 정보가 필요합니다" }); } const data = await productionService.generateSchedule(companyCode, items, options || {}, createdBy); return res.json({ success: true, data }); } catch (error: any) { logger.error("자동 스케줄 생성 실패", { error: error.message }); return res.status(500).json({ success: false, message: error.message }); } } // ─── 스케줄 병합 ─── export async function mergeSchedules(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const mergedBy = req.user!.userId; const { schedule_ids, product_type } = req.body; if (!schedule_ids || !Array.isArray(schedule_ids) || schedule_ids.length < 2) { return res.status(400).json({ success: false, message: "2개 이상의 스케줄을 선택해주세요" }); } const data = await productionService.mergeSchedules( companyCode, schedule_ids, product_type || "완제품", mergedBy ); return res.json({ success: true, data }); } catch (error: any) { logger.error("스케줄 병합 실패", { error: error.message }); const status = error.message.includes("동일 품목") || error.message.includes("찾을 수 없") ? 400 : 500; return res.status(status).json({ success: false, message: error.message }); } } // ─── 반제품 계획 자동 생성 ─── export async function generateSemiSchedule(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const createdBy = req.user!.userId; const { plan_ids, options } = req.body; if (!plan_ids || !Array.isArray(plan_ids) || plan_ids.length === 0) { return res.status(400).json({ success: false, message: "완제품 계획을 선택해주세요" }); } const data = await productionService.generateSemiSchedule( companyCode, plan_ids, options || {}, createdBy ); return res.json({ success: true, data }); } catch (error: any) { logger.error("반제품 계획 생성 실패", { error: error.message }); return res.status(500).json({ success: false, message: error.message }); } } // ─── 스케줄 분할 ─── export async function splitSchedule(req: AuthenticatedRequest, res: Response) { try { const companyCode = req.user!.companyCode; const splitBy = req.user!.userId; const planId = parseInt(req.params.id, 10); const { split_qty } = req.body; if (!split_qty || split_qty <= 0) { return res.status(400).json({ success: false, message: "분할 수량을 입력해주세요" }); } const data = await productionService.splitSchedule(companyCode, planId, split_qty, splitBy); return res.json({ success: true, data }); } catch (error: any) { logger.error("스케줄 분할 실패", { error: error.message }); return res.status(error.message.includes("찾을 수 없") ? 404 : 400).json({ success: false, message: error.message, }); } }