From 42b0a5778eea796c7097ce8a8c6156075039ad9b Mon Sep 17 00:00:00 2001 From: kent Date: Tue, 13 Jan 2026 19:48:03 +0900 Subject: [PATCH] =?UTF-8?q?fix(WEB):=20=EC=98=81=EC=97=85=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B0=8F=20=EA=B2=AC=EC=A0=81=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 생산지시 페이지 에러 처리 개선 - 견적관리 상세 페이지 개선 - quotes types 수정 --- .../[id]/production-order/page.tsx | 49 +++++++++-- .../sales/quote-management/[id]/page.tsx | 84 +++++++++++++++++++ src/components/quotes/types.ts | 4 +- 3 files changed, 128 insertions(+), 9 deletions(-) diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx index c470f295..aac8ee92 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx @@ -340,8 +340,7 @@ export default function ProductionOrderCreatePage() { // 성공 다이얼로그 상태 const [showSuccessDialog, setShowSuccessDialog] = useState(false); - const [generatedOrderNumber, setGeneratedOrderNumber] = useState(""); - const [generatedWorkOrderId, setGeneratedWorkOrderId] = useState(null); + const [generatedWorkOrders, setGeneratedWorkOrders] = useState>([]); // 수주 데이터 및 공정 목록 로드 const fetchData = useCallback(async () => { @@ -388,16 +387,39 @@ export default function ProductionOrderCreatePage() { setIsSubmitting(true); setError(null); try { + // 공정별 품목 그룹핑 (processIds 추출을 위해) + const groups = groupItemsByProcess( + (order.items || []).map((item) => ({ + itemName: item.itemName, + itemCode: item.itemCode, + quantity: item.quantity, + })), + processes + ); + + // 모든 매칭된 공정 ID 추출 (공정별로 각각 작업지시 생성) + const matchedGroups = groups.filter((g) => g.process.id !== "unmatched"); + const allProcessIds = matchedGroups + .map((g) => parseInt(g.process.id, 10)) + .filter((id) => !isNaN(id)); + const productionData: CreateProductionOrderData = { priority: selectedPriority, memo: memo || undefined, + processIds: allProcessIds.length > 0 ? allProcessIds : undefined, }; const result = await createProductionOrder(orderId, productionData); if (result.success && result.data) { - setGeneratedOrderNumber(result.data.workOrder.workOrderNo); - setGeneratedWorkOrderId(result.data.workOrder.id); + // 다중 작업지시 응답 처리 + const workOrders = result.data.workOrders || (result.data.workOrder ? [result.data.workOrder] : []); + setGeneratedWorkOrders( + workOrders.map((wo: { workOrderNo: string; process?: { processName: string } }) => ({ + workOrderNo: wo.workOrderNo, + processName: wo.process?.processName, + })) + ); setShowSuccessDialog(true); } else { setError(result.error || "생산지시 생성에 실패했습니다."); @@ -911,16 +933,27 @@ export default function ProductionOrderCreatePage() { - 생산지시가 생성되었습니다. + 작업지시가 {generatedWorkOrders.length}건 생성되었습니다.
-

생산지시번호:

-

{generatedOrderNumber}

+

생성된 작업지시:

+
    + {generatedWorkOrders.map((wo, idx) => ( +
  • + {wo.workOrderNo} + {wo.processName && ( + + {wo.processName} + + )} +
  • + ))} +

- 생산관리 > 생산지시 관리에서 작업지시서를 생성하세요. + 생산관리 > 작업지시 관리에서 확인하세요.

diff --git a/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx b/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx index f13b97c2..c688a319 100644 --- a/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx +++ b/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx @@ -47,6 +47,9 @@ import { MessageCircle, X, FileCheck, + Package, + ChevronDown, + ChevronUp, } from "lucide-react"; import { ContentLoadingSpinner } from "@/components/ui/loading-spinner"; @@ -69,6 +72,9 @@ export default function QuoteDetailPage() { const [showDetailedBreakdown, setShowDetailedBreakdown] = useState(true); const [showMaterialList, setShowMaterialList] = useState(true); + // BOM 자재 상세 펼침/접힘 상태 + const [isBomExpanded, setIsBomExpanded] = useState(true); + // 견적 데이터 조회 const fetchQuote = useCallback(async () => { setIsLoading(true); @@ -464,6 +470,84 @@ export default function QuoteDetailPage() { + {/* BOM 자재 상세 */} + {quote.bomMaterials && quote.bomMaterials.length > 0 && ( + + +
+ + + BOM 자재 상세 + + {quote.bomMaterials.length}개 품목 + + + +
+
+ {isBomExpanded && ( + +
+ + + + + + + + + + + + + + + + {quote.bomMaterials.map((material, index) => ( + + + + + + + + + + + + ))} + + + + + + + +
No품목코드품목명유형규격단위수량단가금액
{index + 1}{material.itemCode}{material.itemName} + + {material.itemType === 'RM' ? '원자재' : + material.itemType === 'SM' ? '부자재' : + material.itemType === 'CS' ? '소모품' : material.itemType} + + {material.specification || '-'}{material.unit}{material.quantity.toLocaleString()}₩{material.unitPrice.toLocaleString()}₩{material.totalPrice.toLocaleString()}
합계 + ₩{quote.bomMaterials.reduce((sum, m) => sum + m.totalPrice, 0).toLocaleString()} +
+
+
+ )} +
+ )} + {/* 견적서 다이얼로그 */} diff --git a/src/components/quotes/types.ts b/src/components/quotes/types.ts index 3de2efeb..c0c11f51 100644 --- a/src/components/quotes/types.ts +++ b/src/components/quotes/types.ts @@ -69,6 +69,7 @@ export interface QuoteItem { id: string; quoteId: string; productId?: string; + itemCode?: string; // 품목코드 (item_code) productName: string; specification?: string; unit?: string; @@ -298,6 +299,7 @@ export function transformItemApiToFrontend(apiData: QuoteItemApiData): QuoteItem id: String(apiData.id), quoteId: String(apiData.quote_id), productId: apiData.item_id ? String(apiData.item_id) : (apiData.product_id ? String(apiData.product_id) : undefined), + itemCode: apiData.item_code || undefined, // 품목코드 productName, specification: apiData.specification || undefined, unit: apiData.unit || undefined, @@ -545,7 +547,7 @@ export function transformQuoteToFormData(quote: Quote): QuoteFormData { ? quote.items.map((item, index) => ({ itemIndex: index, finishedGoodsCode: '', - itemCode: item.productId || item.id || '', + itemCode: item.itemCode || '', // 품목코드 사용 itemName: item.productName, itemType: '', itemCategory: '',