From 0e09b9e686244210e41f7973c6f45976c75c2904 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 16 Apr 2026 11:31:41 +0900 Subject: [PATCH] feat: Implement duplicate inbound registration check and enhance receiving page - Added a check in the receiving controller to prevent duplicate inbound registrations based on the inbound number, ensuring idempotency. - Updated the receiving page to maintain selected items across different inbound types, improving user experience. - Enhanced the item mapping logic to utilize inventory unit codes, ensuring accurate data representation. - Adjusted the layout to include a new column for inbound type in the receiving table, providing better visibility of item classifications. --- .../src/controllers/receivingController.ts | 11 +++ .../logistics/inbound-outbound/page.tsx | 6 +- .../COMPANY_10/logistics/receiving/page.tsx | 25 +++--- .../logistics/inbound-outbound/page.tsx | 6 +- .../COMPANY_16/logistics/receiving/page.tsx | 33 ++++---- .../COMPANY_16/purchase/supplier/page.tsx | 6 ++ .../(main)/COMPANY_16/sales/customer/page.tsx | 6 ++ .../logistics/inbound-outbound/page.tsx | 6 +- .../COMPANY_29/logistics/receiving/page.tsx | 25 +++--- .../logistics/inbound-outbound/page.tsx | 25 ++---- .../COMPANY_30/logistics/receiving/page.tsx | 81 +++++-------------- .../logistics/inbound-outbound/page.tsx | 6 +- .../COMPANY_7/logistics/receiving/page.tsx | 25 +++--- .../logistics/inbound-outbound/page.tsx | 6 +- .../COMPANY_9/logistics/outbound/page.tsx | 56 +++++++++++-- .../COMPANY_9/logistics/receiving/page.tsx | 25 +++--- .../COMPANY_9/master-data/item-info/page.tsx | 14 +++- .../(main)/COMPANY_9/purchase/order/page.tsx | 26 +++++- .../COMPANY_9/purchase/purchase-item/page.tsx | 6 ++ .../COMPANY_9/sales/sales-item/page.tsx | 9 +++ 20 files changed, 243 insertions(+), 160 deletions(-) diff --git a/backend-node/src/controllers/receivingController.ts b/backend-node/src/controllers/receivingController.ts index b2d920b5..aaf3f81e 100644 --- a/backend-node/src/controllers/receivingController.ts +++ b/backend-node/src/controllers/receivingController.ts @@ -198,6 +198,17 @@ export async function create(req: AuthenticatedRequest, res: Response) { } const insertedDetails: any[] = []; + // 기존 디테일이 있으면 스킵 (멱등성 — 같은 inbound_number로 2번 호출 방지) + const existingDetails = await client.query( + `SELECT COUNT(*) AS cnt FROM inbound_detail WHERE company_code = $1 AND inbound_id = $2`, + [companyCode, inboundNumber] + ); + if (parseInt(existingDetails.rows[0].cnt, 10) > 0) { + await client.query("COMMIT"); + client.release(); + return res.json({ success: true, data: [], message: "이미 등록된 입고입니다." }); + } + // 2. 디테일 INSERT (inbound_detail) + 재고/발주 업데이트 for (let i = 0; i < items.length; i++) { const item = items[i]; diff --git a/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx index 587940ce..bbad370b 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/inbound-outbound/page.tsx @@ -104,10 +104,10 @@ export default function InboundOutboundPage() { const itemCodes = [...new Set(rows.map((r: any) => r.item_code).filter(Boolean))]; if (itemCodes.length > 0) { try { - // 단위 카테고리 코드→라벨 매핑 로드 + // 재고단위 카테고리 코드→라벨 매핑 로드 let unitLabelMap: Record = {}; try { - const catRes = await apiClient.get("/table-categories/item_info/unit/values"); + const catRes = await apiClient.get("/table-categories/item_info/inventory_unit/values"); if (catRes.data?.success && catRes.data.data?.length > 0) { const flatten = (vals: any[]) => { for (const v of vals) { @@ -127,7 +127,7 @@ export default function InboundOutboundPage() { const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || []; const map: Record = {}; for (const i of items) { - const rawUnit = i.unit || ""; + const rawUnit = i.inventory_unit || ""; if (!map[i.item_number]) map[i.item_number] = { item_name: i.item_name || "", unit: unitLabelMap[rawUnit] || rawUnit }; } setItemMap(map); diff --git a/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx index a9825af5..602afeed 100644 --- a/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_10/logistics/receiving/page.tsx @@ -54,6 +54,7 @@ import { } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; +import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; @@ -388,7 +389,7 @@ export default function ReceivingPage() { const flatRows = useMemo(() => { return data.map((row) => ({ ...row, - inbound_type: resolveInboundType(row.inbound_type), + inbound_type: resolveInboundType((row as any).detail_inbound_type || row.inbound_type), source_type: row.source_table ? (SOURCE_TABLE_LABEL[row.source_table] || row.source_table) : (row as any).source_type || "", })); }, [data]); @@ -595,7 +596,7 @@ export default function ReceivingPage() { setSelectedItems( grouped.map((g) => ({ key: g.id, - inbound_type: g.inbound_type || "", + inbound_type: (g as any).detail_inbound_type || g.inbound_type || "", reference_number: g.reference_number || "", supplier_code: (g as any).supplier_code || "", supplier_name: g.supplier_name || "", @@ -635,7 +636,7 @@ export default function ReceivingPage() { setPurchaseOrders([]); setShipments([]); setItems([]); - setSelectedItems([]); + // 선택 품목은 유지 (여러 유형 혼합 가능) setSourcePage(1); setSourceTotalCount(0); loadSourceData(type, undefined, 1); @@ -651,7 +652,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "구매입고", + inbound_type: modalInboundType, reference_number: po.purchase_no, supplier_code: po.supplier_code, supplier_name: po.supplier_name, @@ -677,7 +678,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "반품입고", + inbound_type: modalInboundType, reference_number: sh.instruction_no, supplier_code: "", supplier_name: sh.partner_id, @@ -695,15 +696,15 @@ export default function ReceivingPage() { ]); }; - // 품목 추가 + // 품목 추가 (현재 선택된 입고유형 사용) const addItem = (item: ItemSource) => { - const key = `item-${item.id}`; + const key = `item-${item.id}-${modalInboundType}`; if (selectedItems.some((s) => s.key === key)) return; setSelectedItems((prev) => [ ...prev, { key, - inbound_type: "기타입고", + inbound_type: modalInboundType, reference_number: item.item_number, supplier_code: "", supplier_name: "", @@ -1009,11 +1010,11 @@ export default function ReceivingPage() { ) : ( - paginatedRows.map((row) => { + paginatedRows.map((row, idx) => { const isChecked = checkedIds.includes(row.id); return ( No + 입고유형 품목명 참조번호 @@ -1421,6 +1423,9 @@ export default function ReceivingPage() { {idx + 1} + + {item.inbound_type || modalInboundType} +
diff --git a/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx index 587940ce..bbad370b 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/inbound-outbound/page.tsx @@ -104,10 +104,10 @@ export default function InboundOutboundPage() { const itemCodes = [...new Set(rows.map((r: any) => r.item_code).filter(Boolean))]; if (itemCodes.length > 0) { try { - // 단위 카테고리 코드→라벨 매핑 로드 + // 재고단위 카테고리 코드→라벨 매핑 로드 let unitLabelMap: Record = {}; try { - const catRes = await apiClient.get("/table-categories/item_info/unit/values"); + const catRes = await apiClient.get("/table-categories/item_info/inventory_unit/values"); if (catRes.data?.success && catRes.data.data?.length > 0) { const flatten = (vals: any[]) => { for (const v of vals) { @@ -127,7 +127,7 @@ export default function InboundOutboundPage() { const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || []; const map: Record = {}; for (const i of items) { - const rawUnit = i.unit || ""; + const rawUnit = i.inventory_unit || ""; if (!map[i.item_number]) map[i.item_number] = { item_name: i.item_name || "", unit: unitLabelMap[rawUnit] || rawUnit }; } setItemMap(map); diff --git a/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx index 0ce3b0ec..602afeed 100644 --- a/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_16/logistics/receiving/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -54,8 +54,8 @@ import { } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; -import { apiClient } from "@/lib/api/client"; import { toast } from "sonner"; +import { apiClient } from "@/lib/api/client"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; @@ -284,7 +284,6 @@ export default function ReceivingPage() { const [modalMemo, setModalMemo] = useState(""); const [selectedItems, setSelectedItems] = useState([]); const [saving, setSaving] = useState(false); - const savingLockRef = useRef(false); // 수정 모드 const [editMode, setEditMode] = useState(false); @@ -390,7 +389,7 @@ export default function ReceivingPage() { const flatRows = useMemo(() => { return data.map((row) => ({ ...row, - inbound_type: resolveInboundType(row.inbound_type), + inbound_type: resolveInboundType((row as any).detail_inbound_type || row.inbound_type), source_type: row.source_table ? (SOURCE_TABLE_LABEL[row.source_table] || row.source_table) : (row as any).source_type || "", })); }, [data]); @@ -597,7 +596,7 @@ export default function ReceivingPage() { setSelectedItems( grouped.map((g) => ({ key: g.id, - inbound_type: g.inbound_type || "", + inbound_type: (g as any).detail_inbound_type || g.inbound_type || "", reference_number: g.reference_number || "", supplier_code: (g as any).supplier_code || "", supplier_name: g.supplier_name || "", @@ -637,7 +636,7 @@ export default function ReceivingPage() { setPurchaseOrders([]); setShipments([]); setItems([]); - setSelectedItems([]); + // 선택 품목은 유지 (여러 유형 혼합 가능) setSourcePage(1); setSourceTotalCount(0); loadSourceData(type, undefined, 1); @@ -653,7 +652,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "구매입고", + inbound_type: modalInboundType, reference_number: po.purchase_no, supplier_code: po.supplier_code, supplier_name: po.supplier_name, @@ -679,7 +678,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "반품입고", + inbound_type: modalInboundType, reference_number: sh.instruction_no, supplier_code: "", supplier_name: sh.partner_id, @@ -697,15 +696,15 @@ export default function ReceivingPage() { ]); }; - // 품목 추가 + // 품목 추가 (현재 선택된 입고유형 사용) const addItem = (item: ItemSource) => { - const key = `item-${item.id}`; + const key = `item-${item.id}-${modalInboundType}`; if (selectedItems.some((s) => s.key === key)) return; setSelectedItems((prev) => [ ...prev, { key, - inbound_type: "기타입고", + inbound_type: modalInboundType, reference_number: item.item_number, supplier_code: "", supplier_name: "", @@ -752,7 +751,6 @@ export default function ReceivingPage() { // 저장 const handleSave = async () => { - if (savingLockRef.current) return; if (selectedItems.length === 0) { alert("입고할 품목을 선택해주세요."); return; @@ -772,7 +770,6 @@ export default function ReceivingPage() { toast.error("창고를 선택해주세요."); return; } - savingLockRef.current = true; setSaving(true); try { if (editMode) { @@ -868,7 +865,6 @@ export default function ReceivingPage() { toast.error(msg); } finally { setSaving(false); - savingLockRef.current = false; } }; @@ -1014,12 +1010,11 @@ export default function ReceivingPage() { ) : ( - paginatedRows.map((row) => { + paginatedRows.map((row, idx) => { const isChecked = checkedIds.includes(row.id); - const rowKey = (row as any).detail_id ? `${row.id}-${(row as any).detail_id}` : row.id; return ( No + 입고유형 품목명 참조번호 @@ -1427,6 +1423,9 @@ export default function ReceivingPage() { {idx + 1} + + {item.inbound_type || modalInboundType} +
diff --git a/frontend/app/(main)/COMPANY_16/purchase/supplier/page.tsx b/frontend/app/(main)/COMPANY_16/purchase/supplier/page.tsx index 08cb3af1..e72b223b 100644 --- a/frontend/app/(main)/COMPANY_16/purchase/supplier/page.tsx +++ b/frontend/app/(main)/COMPANY_16/purchase/supplier/page.tsx @@ -62,6 +62,9 @@ const SUPPLIER_GRID_COLUMNS = [ { key: "contact_person", label: "담당자" }, { key: "contact_phone", label: "전화번호" }, { key: "email", label: "이메일" }, + { key: "bank_name", label: "은행" }, + { key: "account_number", label: "계좌번호" }, + { key: "remark", label: "비고" }, { key: "business_number", label: "사업자번호" }, { key: "address", label: "주소" }, { key: "status", label: "상태" }, @@ -1291,6 +1294,9 @@ export default function SupplierManagementPage() { email: { width: "w-[160px]" }, business_number: { width: "w-[120px]" }, address: { minWidth: "min-w-[150px]" }, + bank_name: { width: "w-[100px]" }, + account_number: { width: "w-[140px]" }, + remark: { minWidth: "min-w-[120px]" }, status: { width: "w-[70px]", render: (val: any) => diff --git a/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx b/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx index cc8fb2ce..55473eae 100644 --- a/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx @@ -62,6 +62,9 @@ const CUSTOMER_GRID_COLUMNS = [ { key: "contact_person", label: "담당자" }, { key: "contact_phone", label: "전화번호" }, { key: "email", label: "이메일" }, + { key: "bank_name", label: "은행" }, + { key: "account_number", label: "계좌번호" }, + { key: "remark", label: "비고" }, { key: "business_number", label: "사업자번호" }, { key: "address", label: "주소" }, { key: "status", label: "상태" }, @@ -1311,6 +1314,9 @@ export default function CustomerManagementPage() { email: { width: "w-[160px]" }, business_number: { width: "w-[120px]" }, address: { minWidth: "min-w-[150px]" }, + bank_name: { width: "w-[100px]" }, + account_number: { width: "w-[140px]" }, + remark: { minWidth: "min-w-[120px]" }, status: { width: "w-[70px]", render: (val: any) => diff --git a/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx index 587940ce..bbad370b 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/inbound-outbound/page.tsx @@ -104,10 +104,10 @@ export default function InboundOutboundPage() { const itemCodes = [...new Set(rows.map((r: any) => r.item_code).filter(Boolean))]; if (itemCodes.length > 0) { try { - // 단위 카테고리 코드→라벨 매핑 로드 + // 재고단위 카테고리 코드→라벨 매핑 로드 let unitLabelMap: Record = {}; try { - const catRes = await apiClient.get("/table-categories/item_info/unit/values"); + const catRes = await apiClient.get("/table-categories/item_info/inventory_unit/values"); if (catRes.data?.success && catRes.data.data?.length > 0) { const flatten = (vals: any[]) => { for (const v of vals) { @@ -127,7 +127,7 @@ export default function InboundOutboundPage() { const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || []; const map: Record = {}; for (const i of items) { - const rawUnit = i.unit || ""; + const rawUnit = i.inventory_unit || ""; if (!map[i.item_number]) map[i.item_number] = { item_name: i.item_name || "", unit: unitLabelMap[rawUnit] || rawUnit }; } setItemMap(map); diff --git a/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx index a9825af5..602afeed 100644 --- a/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_29/logistics/receiving/page.tsx @@ -54,6 +54,7 @@ import { } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; +import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; @@ -388,7 +389,7 @@ export default function ReceivingPage() { const flatRows = useMemo(() => { return data.map((row) => ({ ...row, - inbound_type: resolveInboundType(row.inbound_type), + inbound_type: resolveInboundType((row as any).detail_inbound_type || row.inbound_type), source_type: row.source_table ? (SOURCE_TABLE_LABEL[row.source_table] || row.source_table) : (row as any).source_type || "", })); }, [data]); @@ -595,7 +596,7 @@ export default function ReceivingPage() { setSelectedItems( grouped.map((g) => ({ key: g.id, - inbound_type: g.inbound_type || "", + inbound_type: (g as any).detail_inbound_type || g.inbound_type || "", reference_number: g.reference_number || "", supplier_code: (g as any).supplier_code || "", supplier_name: g.supplier_name || "", @@ -635,7 +636,7 @@ export default function ReceivingPage() { setPurchaseOrders([]); setShipments([]); setItems([]); - setSelectedItems([]); + // 선택 품목은 유지 (여러 유형 혼합 가능) setSourcePage(1); setSourceTotalCount(0); loadSourceData(type, undefined, 1); @@ -651,7 +652,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "구매입고", + inbound_type: modalInboundType, reference_number: po.purchase_no, supplier_code: po.supplier_code, supplier_name: po.supplier_name, @@ -677,7 +678,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "반품입고", + inbound_type: modalInboundType, reference_number: sh.instruction_no, supplier_code: "", supplier_name: sh.partner_id, @@ -695,15 +696,15 @@ export default function ReceivingPage() { ]); }; - // 품목 추가 + // 품목 추가 (현재 선택된 입고유형 사용) const addItem = (item: ItemSource) => { - const key = `item-${item.id}`; + const key = `item-${item.id}-${modalInboundType}`; if (selectedItems.some((s) => s.key === key)) return; setSelectedItems((prev) => [ ...prev, { key, - inbound_type: "기타입고", + inbound_type: modalInboundType, reference_number: item.item_number, supplier_code: "", supplier_name: "", @@ -1009,11 +1010,11 @@ export default function ReceivingPage() { ) : ( - paginatedRows.map((row) => { + paginatedRows.map((row, idx) => { const isChecked = checkedIds.includes(row.id); return ( No + 입고유형 품목명 참조번호 @@ -1421,6 +1423,9 @@ export default function ReceivingPage() { {idx + 1} + + {item.inbound_type || modalInboundType} +
diff --git a/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx index 3c00baca..bbad370b 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/inbound-outbound/page.tsx @@ -75,7 +75,7 @@ export default function InboundOutboundPage() { const [checkedIds, setCheckedIds] = useState>(new Set()); // 품목명/단위 캐시 - const [itemMap, setItemMap] = useState>({}); + const [itemMap, setItemMap] = useState>({}); const [warehouseMap, setWarehouseMap] = useState>({}); const [userMap, setUserMap] = useState>({}); @@ -104,10 +104,10 @@ export default function InboundOutboundPage() { const itemCodes = [...new Set(rows.map((r: any) => r.item_code).filter(Boolean))]; if (itemCodes.length > 0) { try { - // 단위 카테고리 코드→라벨 매핑 로드 + // 재고단위 카테고리 코드→라벨 매핑 로드 let unitLabelMap: Record = {}; try { - const catRes = await apiClient.get("/table-categories/item_info/unit/values"); + const catRes = await apiClient.get("/table-categories/item_info/inventory_unit/values"); if (catRes.data?.success && catRes.data.data?.length > 0) { const flatten = (vals: any[]) => { for (const v of vals) { @@ -125,16 +125,10 @@ export default function InboundOutboundPage() { autoFilter: true, }); const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || []; - const map: Record = {}; + const map: Record = {}; for (const i of items) { - const rawUnit = i.unit || ""; - if (!map[i.item_number]) map[i.item_number] = { - item_name: i.item_name || "", - unit: unitLabelMap[rawUnit] || rawUnit, - width: i.width || "", - height: i.height || "", - thickness: i.thickness || "", - }; + const rawUnit = i.inventory_unit || ""; + if (!map[i.item_number]) map[i.item_number] = { item_name: i.item_name || "", unit: unitLabelMap[rawUnit] || rawUnit }; } setItemMap(map); } catch { /* skip */ } @@ -347,9 +341,6 @@ export default function InboundOutboundPage() { 위치 품목코드 품목명 - 가로 - 세로 - 두께 수량 단위 로트번호 @@ -370,7 +361,6 @@ export default function InboundOutboundPage() { {row._count}건
- {fmtNum(row._totalQty)} @@ -414,9 +404,6 @@ export default function InboundOutboundPage() { {row.location_code || "-"} {row.item_code || "-"} {info?.item_name || "-"} - {info?.width || "-"} - {info?.height || "-"} - {info?.thickness || "-"} {isIn ? "+" : ""}{fmtNum(qty)} diff --git a/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx index 8cf7d653..602afeed 100644 --- a/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_30/logistics/receiving/page.tsx @@ -54,6 +54,7 @@ import { } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; +import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; @@ -86,9 +87,6 @@ const GRID_COLUMNS = [ { key: "supplier_name", label: "공급처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, - { key: "width", label: "가로" }, - { key: "height", label: "세로" }, - { key: "thickness", label: "두께" }, { key: "spec", label: "규격" }, { key: "inbound_qty", label: "입고수량" }, { key: "unit_price", label: "단가" }, @@ -98,8 +96,8 @@ const GRID_COLUMNS = [ { key: "remark", label: "비고" }, ]; -// 총 컬럼 수: 체크박스(1) + GRID_COLUMNS(18) = 19 -const TOTAL_COLS = 19; +// 총 컬럼 수: 체크박스(1) + GRID_COLUMNS(15) = 16 +const TOTAL_COLS = 16; // 헤더 필터 Popover function HeaderFilterPopover({ @@ -391,7 +389,7 @@ export default function ReceivingPage() { const flatRows = useMemo(() => { return data.map((row) => ({ ...row, - inbound_type: resolveInboundType(row.inbound_type), + inbound_type: resolveInboundType((row as any).detail_inbound_type || row.inbound_type), source_type: row.source_table ? (SOURCE_TABLE_LABEL[row.source_table] || row.source_table) : (row as any).source_type || "", })); }, [data]); @@ -598,7 +596,7 @@ export default function ReceivingPage() { setSelectedItems( grouped.map((g) => ({ key: g.id, - inbound_type: g.inbound_type || "", + inbound_type: (g as any).detail_inbound_type || g.inbound_type || "", reference_number: g.reference_number || "", supplier_code: (g as any).supplier_code || "", supplier_name: g.supplier_name || "", @@ -638,7 +636,7 @@ export default function ReceivingPage() { setPurchaseOrders([]); setShipments([]); setItems([]); - setSelectedItems([]); + // 선택 품목은 유지 (여러 유형 혼합 가능) setSourcePage(1); setSourceTotalCount(0); loadSourceData(type, undefined, 1); @@ -654,16 +652,13 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "구매입고", + inbound_type: modalInboundType, reference_number: po.purchase_no, supplier_code: po.supplier_code, supplier_name: po.supplier_name, item_number: po.item_code, item_name: po.item_name, spec: po.spec || "", - width: (po as any).width || "", - height: (po as any).height || "", - thickness: (po as any).thickness || "", material: po.material || "", unit: "EA", inbound_qty: po.remain_qty, @@ -683,16 +678,13 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "반품입고", + inbound_type: modalInboundType, reference_number: sh.instruction_no, supplier_code: "", supplier_name: sh.partner_id, item_number: sh.item_code, item_name: sh.item_name, spec: sh.spec || "", - width: (sh as any).width || "", - height: (sh as any).height || "", - thickness: (sh as any).thickness || "", material: sh.material || "", unit: "EA", inbound_qty: sh.ship_qty, @@ -704,24 +696,21 @@ export default function ReceivingPage() { ]); }; - // 품목 추가 + // 품목 추가 (현재 선택된 입고유형 사용) const addItem = (item: ItemSource) => { - const key = `item-${item.id}`; + const key = `item-${item.id}-${modalInboundType}`; if (selectedItems.some((s) => s.key === key)) return; setSelectedItems((prev) => [ ...prev, { key, - inbound_type: "기타입고", + inbound_type: modalInboundType, reference_number: item.item_number, supplier_code: "", supplier_name: "", item_number: item.item_number, item_name: item.item_name, spec: item.spec || "", - width: (item as any).width || "", - height: (item as any).height || "", - thickness: (item as any).thickness || "", material: item.material || "", unit: item.inventory_unit || "EA", inbound_qty: 0, @@ -951,9 +940,6 @@ export default function ReceivingPage() { - - - @@ -1024,11 +1010,11 @@ export default function ReceivingPage() {
) : ( - paginatedRows.map((row) => { + paginatedRows.map((row, idx) => { const isChecked = checkedIds.includes(row.id); return ( {row.supplier_name || ""} {row.item_number || ""} {row.item_name || ""} - {(row as any).width || "-"} - {(row as any).height || "-"} - {(row as any).thickness || "-"} {row.spec || ""} {row.inbound_qty ? Number(row.inbound_qty).toLocaleString() : ""} {row.unit_price ? Number(row.unit_price).toLocaleString() : ""} @@ -1419,6 +1402,7 @@ export default function ReceivingPage() { No + 입고유형 품목명 참조번호 @@ -1439,7 +1423,10 @@ export default function ReceivingPage() { {idx + 1} - + + {item.inbound_type || modalInboundType} + +
{item.item_name} @@ -1448,13 +1435,6 @@ export default function ReceivingPage() { {item.item_number} {item.spec ? ` | ${item.spec}` : ""} - {((item as any).width || (item as any).height || (item as any).thickness) && ( - - {(item as any).width && `W ${(item as any).width}`} - {(item as any).height && ` × H ${(item as any).height}`} - {(item as any).thickness && ` × T ${(item as any).thickness}`} - - )}
@@ -1620,20 +1600,13 @@ function SourcePurchaseOrderTable({ {po.purchase_no} {po.supplier_name} - +
{po.item_name} {po.item_code} {po.spec ? ` | ${po.spec}` : ""} - {((po as any).width || (po as any).height || (po as any).thickness) && ( - - {(po as any).width && `W ${(po as any).width}`} - {(po as any).height && ` × H ${(po as any).height}`} - {(po as any).thickness && ` × T ${(po as any).thickness}`} - - )}
@@ -1711,20 +1684,13 @@ function SourceShipmentTable({ : "-"} {sh.partner_id} - +
{sh.item_name} {sh.item_code} {sh.spec ? ` | ${sh.spec}` : ""} - {((sh as any).width || (sh as any).height || (sh as any).thickness) && ( - - {(sh as any).width && `W ${(sh as any).width}`} - {(sh as any).height && ` × H ${(sh as any).height}`} - {(sh as any).thickness && ` × T ${(sh as any).thickness}`} - - )}
@@ -1797,13 +1763,6 @@ function SourceItemTable({ {item.item_number} - {((item as any).width || (item as any).height || (item as any).thickness) && ( - - {(item as any).width && `W ${(item as any).width}`} - {(item as any).height && ` × H ${(item as any).height}`} - {(item as any).thickness && ` × T ${(item as any).thickness}`} - - )}
{item.spec || "-"} diff --git a/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx index 587940ce..bbad370b 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/inbound-outbound/page.tsx @@ -104,10 +104,10 @@ export default function InboundOutboundPage() { const itemCodes = [...new Set(rows.map((r: any) => r.item_code).filter(Boolean))]; if (itemCodes.length > 0) { try { - // 단위 카테고리 코드→라벨 매핑 로드 + // 재고단위 카테고리 코드→라벨 매핑 로드 let unitLabelMap: Record = {}; try { - const catRes = await apiClient.get("/table-categories/item_info/unit/values"); + const catRes = await apiClient.get("/table-categories/item_info/inventory_unit/values"); if (catRes.data?.success && catRes.data.data?.length > 0) { const flatten = (vals: any[]) => { for (const v of vals) { @@ -127,7 +127,7 @@ export default function InboundOutboundPage() { const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || []; const map: Record = {}; for (const i of items) { - const rawUnit = i.unit || ""; + const rawUnit = i.inventory_unit || ""; if (!map[i.item_number]) map[i.item_number] = { item_name: i.item_name || "", unit: unitLabelMap[rawUnit] || rawUnit }; } setItemMap(map); diff --git a/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx index a9825af5..602afeed 100644 --- a/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_7/logistics/receiving/page.tsx @@ -54,6 +54,7 @@ import { } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; +import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; @@ -388,7 +389,7 @@ export default function ReceivingPage() { const flatRows = useMemo(() => { return data.map((row) => ({ ...row, - inbound_type: resolveInboundType(row.inbound_type), + inbound_type: resolveInboundType((row as any).detail_inbound_type || row.inbound_type), source_type: row.source_table ? (SOURCE_TABLE_LABEL[row.source_table] || row.source_table) : (row as any).source_type || "", })); }, [data]); @@ -595,7 +596,7 @@ export default function ReceivingPage() { setSelectedItems( grouped.map((g) => ({ key: g.id, - inbound_type: g.inbound_type || "", + inbound_type: (g as any).detail_inbound_type || g.inbound_type || "", reference_number: g.reference_number || "", supplier_code: (g as any).supplier_code || "", supplier_name: g.supplier_name || "", @@ -635,7 +636,7 @@ export default function ReceivingPage() { setPurchaseOrders([]); setShipments([]); setItems([]); - setSelectedItems([]); + // 선택 품목은 유지 (여러 유형 혼합 가능) setSourcePage(1); setSourceTotalCount(0); loadSourceData(type, undefined, 1); @@ -651,7 +652,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "구매입고", + inbound_type: modalInboundType, reference_number: po.purchase_no, supplier_code: po.supplier_code, supplier_name: po.supplier_name, @@ -677,7 +678,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "반품입고", + inbound_type: modalInboundType, reference_number: sh.instruction_no, supplier_code: "", supplier_name: sh.partner_id, @@ -695,15 +696,15 @@ export default function ReceivingPage() { ]); }; - // 품목 추가 + // 품목 추가 (현재 선택된 입고유형 사용) const addItem = (item: ItemSource) => { - const key = `item-${item.id}`; + const key = `item-${item.id}-${modalInboundType}`; if (selectedItems.some((s) => s.key === key)) return; setSelectedItems((prev) => [ ...prev, { key, - inbound_type: "기타입고", + inbound_type: modalInboundType, reference_number: item.item_number, supplier_code: "", supplier_name: "", @@ -1009,11 +1010,11 @@ export default function ReceivingPage() {
) : ( - paginatedRows.map((row) => { + paginatedRows.map((row, idx) => { const isChecked = checkedIds.includes(row.id); return ( No + 입고유형 품목명 참조번호 @@ -1421,6 +1423,9 @@ export default function ReceivingPage() { {idx + 1} + + {item.inbound_type || modalInboundType} +
diff --git a/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx index 587940ce..bbad370b 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/inbound-outbound/page.tsx @@ -104,10 +104,10 @@ export default function InboundOutboundPage() { const itemCodes = [...new Set(rows.map((r: any) => r.item_code).filter(Boolean))]; if (itemCodes.length > 0) { try { - // 단위 카테고리 코드→라벨 매핑 로드 + // 재고단위 카테고리 코드→라벨 매핑 로드 let unitLabelMap: Record = {}; try { - const catRes = await apiClient.get("/table-categories/item_info/unit/values"); + const catRes = await apiClient.get("/table-categories/item_info/inventory_unit/values"); if (catRes.data?.success && catRes.data.data?.length > 0) { const flatten = (vals: any[]) => { for (const v of vals) { @@ -127,7 +127,7 @@ export default function InboundOutboundPage() { const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || []; const map: Record = {}; for (const i of items) { - const rawUnit = i.unit || ""; + const rawUnit = i.inventory_unit || ""; if (!map[i.item_number]) map[i.item_number] = { item_name: i.item_name || "", unit: unitLabelMap[rawUnit] || rawUnit }; } setItemMap(map); diff --git a/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx index d6e801a1..ea244869 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/outbound/page.tsx @@ -106,6 +106,9 @@ const GRID_COLUMNS = [ { key: "customer_name", label: "거래처" }, { key: "item_number", label: "품목코드" }, { key: "item_name", label: "품목명" }, + { key: "width", label: "가로" }, + { key: "height", label: "세로" }, + { key: "thickness", label: "두께" }, { key: "spec", label: "규격" }, { key: "outbound_qty", label: "출고수량" }, { key: "unit_price", label: "단가" }, @@ -115,8 +118,8 @@ const GRID_COLUMNS = [ { key: "remark", label: "비고" }, ]; -// 총 컬럼 수: 체크박스(1) + GRID_COLUMNS(15) = 16 -const TOTAL_COLS = 16; +// 총 컬럼 수: 체크박스(1) + GRID_COLUMNS(18) = 19 +const TOTAL_COLS = 19; // 헤더 필터 Popover function HeaderFilterPopover({ @@ -626,6 +629,9 @@ export default function OutboundPage() { item_number: si.item_code, item_name: si.item_name, spec: si.spec || "", + width: (si as any).width || "", + height: (si as any).height || "", + thickness: (si as any).thickness || "", material: si.material || "", unit: "EA", outbound_qty: si.remain_qty, @@ -652,6 +658,9 @@ export default function OutboundPage() { item_number: po.item_code, item_name: po.item_name, spec: po.spec || "", + width: (po as any).width || "", + height: (po as any).height || "", + thickness: (po as any).thickness || "", material: po.material || "", unit: "EA", outbound_qty: po.received_qty, @@ -678,6 +687,9 @@ export default function OutboundPage() { item_number: item.item_number, item_name: item.item_name, spec: item.spec || "", + width: (item as any).width || "", + height: (item as any).height || "", + thickness: (item as any).thickness || "", material: item.material || "", unit: item.inventory_unit || "EA", outbound_qty: 0, @@ -896,6 +908,9 @@ export default function OutboundPage() { + + + @@ -1007,6 +1022,9 @@ export default function OutboundPage() { {row.customer_name || ""} {row.item_number || ""} {row.item_name || ""} + {(row as any).width || "-"} + {(row as any).height || "-"} + {(row as any).thickness || "-"} {row.spec || ""} {row.outbound_qty ? Number(row.outbound_qty).toLocaleString() : ""} {row.unit_price ? Number(row.unit_price).toLocaleString() : ""} @@ -1372,7 +1390,7 @@ export default function OutboundPage() { {resolveCat("outbound_type", item.outbound_type) || "-"} - +
{item.item_name} @@ -1381,6 +1399,13 @@ export default function OutboundPage() { {item.item_number} {item.spec ? ` | ${item.spec}` : ""} + {((item as any).width || (item as any).height || (item as any).thickness) && ( + + {(item as any).width && `W ${(item as any).width}`} + {(item as any).height && ` × H ${(item as any).height}`} + {(item as any).thickness && ` × T ${(item as any).thickness}`} + + )}
{item.reference_number} @@ -1535,13 +1560,20 @@ function SourceShipmentInstructionTable({ ? new Date(si.instruction_date).toLocaleDateString("ko-KR") : "-"}
- +
{si.item_name} {si.item_code} {si.spec ? ` | ${si.spec}` : ""} + {((si as any).width || (si as any).height || (si as any).thickness) && ( + + {(si as any).width && `W ${(si as any).width}`} + {(si as any).height && ` × H ${(si as any).height}`} + {(si as any).thickness && ` × T ${(si as any).thickness}`} + + )}
@@ -1612,13 +1644,20 @@ function SourcePurchaseOrderTable({ {po.purchase_no} {po.supplier_name} - +
{po.item_name} {po.item_code} {po.spec ? ` | ${po.spec}` : ""} + {((po as any).width || (po as any).height || (po as any).thickness) && ( + + {(po as any).width && `W ${(po as any).width}`} + {(po as any).height && ` × H ${(po as any).height}`} + {(po as any).thickness && ` × T ${(po as any).thickness}`} + + )}
@@ -1692,6 +1731,13 @@ function SourceItemTable({ {item.item_number} + {((item as any).width || (item as any).height || (item as any).thickness) && ( + + {(item as any).width && `W ${(item as any).width}`} + {(item as any).height && ` × H ${(item as any).height}`} + {(item as any).thickness && ` × T ${(item as any).thickness}`} + + )}
{item.spec || "-"} diff --git a/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx b/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx index a9825af5..602afeed 100644 --- a/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx +++ b/frontend/app/(main)/COMPANY_9/logistics/receiving/page.tsx @@ -54,6 +54,7 @@ import { } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; +import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; @@ -388,7 +389,7 @@ export default function ReceivingPage() { const flatRows = useMemo(() => { return data.map((row) => ({ ...row, - inbound_type: resolveInboundType(row.inbound_type), + inbound_type: resolveInboundType((row as any).detail_inbound_type || row.inbound_type), source_type: row.source_table ? (SOURCE_TABLE_LABEL[row.source_table] || row.source_table) : (row as any).source_type || "", })); }, [data]); @@ -595,7 +596,7 @@ export default function ReceivingPage() { setSelectedItems( grouped.map((g) => ({ key: g.id, - inbound_type: g.inbound_type || "", + inbound_type: (g as any).detail_inbound_type || g.inbound_type || "", reference_number: g.reference_number || "", supplier_code: (g as any).supplier_code || "", supplier_name: g.supplier_name || "", @@ -635,7 +636,7 @@ export default function ReceivingPage() { setPurchaseOrders([]); setShipments([]); setItems([]); - setSelectedItems([]); + // 선택 품목은 유지 (여러 유형 혼합 가능) setSourcePage(1); setSourceTotalCount(0); loadSourceData(type, undefined, 1); @@ -651,7 +652,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "구매입고", + inbound_type: modalInboundType, reference_number: po.purchase_no, supplier_code: po.supplier_code, supplier_name: po.supplier_name, @@ -677,7 +678,7 @@ export default function ReceivingPage() { ...prev, { key, - inbound_type: "반품입고", + inbound_type: modalInboundType, reference_number: sh.instruction_no, supplier_code: "", supplier_name: sh.partner_id, @@ -695,15 +696,15 @@ export default function ReceivingPage() { ]); }; - // 품목 추가 + // 품목 추가 (현재 선택된 입고유형 사용) const addItem = (item: ItemSource) => { - const key = `item-${item.id}`; + const key = `item-${item.id}-${modalInboundType}`; if (selectedItems.some((s) => s.key === key)) return; setSelectedItems((prev) => [ ...prev, { key, - inbound_type: "기타입고", + inbound_type: modalInboundType, reference_number: item.item_number, supplier_code: "", supplier_name: "", @@ -1009,11 +1010,11 @@ export default function ReceivingPage() {
) : ( - paginatedRows.map((row) => { + paginatedRows.map((row, idx) => { const isChecked = checkedIds.includes(row.id); return ( No + 입고유형 품목명 참조번호 @@ -1421,6 +1423,9 @@ export default function ReceivingPage() { {idx + 1} + + {item.inbound_type || modalInboundType} +
diff --git a/frontend/app/(main)/COMPANY_9/master-data/item-info/page.tsx b/frontend/app/(main)/COMPANY_9/master-data/item-info/page.tsx index ad55d2b2..55d4f24b 100644 --- a/frontend/app/(main)/COMPANY_9/master-data/item-info/page.tsx +++ b/frontend/app/(main)/COMPANY_9/master-data/item-info/page.tsx @@ -142,6 +142,9 @@ const GRID_COLUMNS = [ { key: "image", label: "이미지", type: "image" }, { key: "division", label: "관리품목" }, { key: "type", label: "품목구분" }, + { key: "width", label: "가로", align: "right" as const }, + { key: "height", label: "세로", align: "right" as const }, + { key: "thickness", label: "두께", align: "right" as const }, { key: "size", label: "규격" }, { key: "unit", label: "단위" }, { key: "material", label: "재질" }, @@ -160,6 +163,9 @@ const FORM_FIELDS = [ { key: "item_name", label: "품명", type: "text", required: true }, { key: "division", label: "관리품목", type: "multi-category" }, { key: "type", label: "품목구분", type: "category" }, + { key: "width", label: "가로", type: "text", placeholder: "숫자 입력 (예: 1000)" }, + { key: "height", label: "세로", type: "text", placeholder: "숫자 입력 (예: 2000)" }, + { key: "thickness", label: "두께", type: "text", placeholder: "숫자 입력 (예: 10)" }, { key: "size", label: "규격", type: "text" }, { key: "unit", label: "단위", type: "category" }, { key: "material", label: "재질", type: "category" }, @@ -383,8 +389,12 @@ export default function ItemInfoPage() { } return categoryOptions[col]?.find((o) => o.code === code)?.label || code; }; - setRawItems(raw); - const data = raw.map((r: any) => { + // item_number 내림차순 정렬 (최근 품목이 위로, 자연 정렬) + const sortedRaw = [...raw].sort((a: any, b: any) => + String(b.item_number || "").localeCompare(String(a.item_number || ""), undefined, { numeric: true, sensitivity: "base" }) + ); + setRawItems(sortedRaw); + const data = sortedRaw.map((r: any) => { const converted = { ...r }; for (const col of CATEGORY_COLUMNS) { if (converted[col]) converted[col] = resolve(col, converted[col]); diff --git a/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx index 5da53411..965b0a0e 100644 --- a/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx +++ b/frontend/app/(main)/COMPANY_9/purchase/order/page.tsx @@ -76,6 +76,9 @@ const GRID_COLUMNS_CONFIG = [ { key: "supplier_name", label: "공급업체" }, { key: "item_code", label: "품번" }, { key: "item_name", label: "품명" }, + { key: "width", label: "가로" }, + { key: "height", label: "세로" }, + { key: "thickness", label: "두께" }, { key: "spec", label: "규격" }, { key: "order_qty", label: "발주수량" }, { key: "received_qty", label: "입고수량" }, @@ -91,6 +94,9 @@ const MODAL_DETAIL_COLUMNS = [ { key: "item_code", label: "품번", width: "min-w-[120px]" }, { key: "item_name", label: "품명", width: "min-w-[150px]" }, { key: "supplier", label: "공급업체", width: "min-w-[150px]" }, + { key: "width", label: "가로", width: "min-w-[70px]" }, + { key: "height", label: "세로", width: "min-w-[70px]" }, + { key: "thickness", label: "두께", width: "min-w-[70px]" }, { key: "spec", label: "규격", width: "min-w-[80px]" }, { key: "unit", label: "단위", width: "min-w-[90px]" }, { key: "order_qty", label: "발주수량", width: "min-w-[90px]" }, @@ -351,6 +357,9 @@ export default function PurchaseOrderPage() { ...row, item_name: row.item_name || item?.item_name || "", spec: row.spec || item?.size || "", + width: row.width || item?.width || "", + height: row.height || item?.height || "", + thickness: row.thickness || item?.thickness || "", unit: resolveLabel("item_inventory_unit", rawUnit) || rawUnit, status: master?.status || "", supplier_name: master?.supplier_name || "", @@ -641,6 +650,9 @@ export default function PurchaseOrderPage() { item_code: itemCode, item_name: item.item_name, spec: item.size || "", + width: item.width || "", + height: item.height || "", + thickness: item.thickness || "", material: getCategoryLabel("item_material", item.material) || item.material || "", unit: getCategoryLabel("item_inventory_unit", item.inventory_unit) || item.inventory_unit || "", order_qty: "", @@ -1087,6 +1099,12 @@ export default function PurchaseOrderPage() { ); case "spec": return {row.spec}; + case "width": + return {row.width || "-"}; + case "height": + return {row.height || "-"}; + case "thickness": + return {row.thickness || "-"}; case "unit": return {row.unit}; case "order_qty": @@ -1224,6 +1242,9 @@ export default function PurchaseOrderPage() { 품목코드 품명 + 가로 + 세로 + 두께 규격 재질 단위 @@ -1231,7 +1252,7 @@ export default function PurchaseOrderPage() { {itemSearchResults.length === 0 ? ( - 검색 결과가 없어요 + 검색 결과가 없어요 ) : itemSearchResults.map((item) => ( setItemSelectedMap((prev) => { @@ -1244,6 +1265,9 @@ export default function PurchaseOrderPage() { {item.item_number} {item.item_name} + {item.width || "-"} + {item.height || "-"} + {item.thickness || "-"} {item.size} {categoryOptions["item_material"]?.find((o) => o.code === item.material)?.label || item.material} {categoryOptions["item_inventory_unit"]?.find((o) => o.code === item.inventory_unit)?.label || item.inventory_unit} diff --git a/frontend/app/(main)/COMPANY_9/purchase/purchase-item/page.tsx b/frontend/app/(main)/COMPANY_9/purchase/purchase-item/page.tsx index fd203d40..225f69d4 100644 --- a/frontend/app/(main)/COMPANY_9/purchase/purchase-item/page.tsx +++ b/frontend/app/(main)/COMPANY_9/purchase/purchase-item/page.tsx @@ -141,6 +141,9 @@ const FORM_FIELDS = [ { key: "item_name", label: "품명", type: "text", required: true }, { key: "division", label: "관리품목", type: "multi-category" }, { key: "type", label: "품목구분", type: "category" }, + { key: "width", label: "가로", type: "text", placeholder: "숫자 입력 (예: 1000)" }, + { key: "height", label: "세로", type: "text", placeholder: "숫자 입력 (예: 2000)" }, + { key: "thickness", label: "두께", type: "text", placeholder: "숫자 입력 (예: 10)" }, { key: "size", label: "규격", type: "text" }, { key: "inventory_unit", label: "단위", type: "category" }, { key: "material", label: "재질", type: "category" }, @@ -173,6 +176,9 @@ const formatNum = (val: any): string => { const ITEM_GRID_COLUMNS = [ { key: "item_number", label: "품번" }, { key: "item_name", label: "품명" }, + { key: "width", label: "가로", align: "right" as const }, + { key: "height", label: "세로", align: "right" as const }, + { key: "thickness", label: "두께", align: "right" as const }, { key: "size", label: "규격" }, { key: "inventory_unit", label: "단위" }, { key: "standard_price", label: "기준단가/구매단가" }, diff --git a/frontend/app/(main)/COMPANY_9/sales/sales-item/page.tsx b/frontend/app/(main)/COMPANY_9/sales/sales-item/page.tsx index 6e558a82..70ebf5c2 100644 --- a/frontend/app/(main)/COMPANY_9/sales/sales-item/page.tsx +++ b/frontend/app/(main)/COMPANY_9/sales/sales-item/page.tsx @@ -148,6 +148,9 @@ const formatNum = (val: any): string => { const ITEM_GRID_COLUMNS = [ { key: "item_number", label: "품번" }, { key: "item_name", label: "품명" }, + { key: "width", label: "가로", align: "right" as const }, + { key: "height", label: "세로", align: "right" as const }, + { key: "thickness", label: "두께", align: "right" as const }, { key: "size", label: "규격" }, { key: "inventory_unit", label: "단위" }, { key: "standard_price", label: "기준단가" }, @@ -161,6 +164,9 @@ const FORM_FIELDS = [ { key: "item_name", label: "품명", type: "text", required: true }, { key: "division", label: "관리품목", type: "multi-category" }, { key: "type", label: "품목구분", type: "category" }, + { key: "width", label: "가로", type: "text", placeholder: "숫자 입력 (예: 1000)" }, + { key: "height", label: "세로", type: "text", placeholder: "숫자 입력 (예: 2000)" }, + { key: "thickness", label: "두께", type: "text", placeholder: "숫자 입력 (예: 10)" }, { key: "size", label: "규격", type: "text" }, { key: "inventory_unit", label: "단위", type: "category" }, { key: "material", label: "재질", type: "category" }, @@ -1169,6 +1175,9 @@ export default function SalesItemPage() { const itemColumns: EDataTableColumn[] = [ { key: "item_number", label: "품번", width: "w-[110px]" }, { key: "item_name", label: "품명", minWidth: "min-w-[130px]" }, + { key: "width", label: "가로", width: "w-[70px]", align: "right" }, + { key: "height", label: "세로", width: "w-[70px]", align: "right" }, + { key: "thickness", label: "두께", width: "w-[70px]", align: "right" }, { key: "size", label: "규격", width: "w-[80px]" }, { key: "inventory_unit", label: "단위", width: "w-[60px]" }, { key: "standard_price", label: "기준단가", width: "w-[90px]", align: "right", formatNumber: true },