From b6c1b08049d7e7b9b4fc618a87dc62bf4e92366d Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Wed, 1 Apr 2026 18:53:32 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EA=B5=AC=EB=A7=A4=EC=9E=85=EA=B3=A0=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=20?= =?UTF-8?q?=EC=99=84=EC=84=B1=20(E2E=2016/16=20=ED=86=B5=EA=B3=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backend: inventory_stock INSERT 시 id 누락 버그 수정 - frontend: 거래처 API supplier_mng으로 수정 - frontend: cart_items 실제 컬럼 구조 맞춤 - frontend: InboundCart 확정 로직 PC와 동일하게 정렬 - 검증: 발주→장바구니→입고등록→재고증가→발주상태변경 전체 확인 --- .../src/controllers/receivingController.ts | 4 +- .../pop/hardcoded/inbound/InboundCart.tsx | 44 ++++++++++-------- .../pop/hardcoded/inbound/PurchaseInbound.tsx | 45 ++++++++++--------- .../pop/hardcoded/inbound/SupplierModal.tsx | 44 +++++++++--------- 4 files changed, 75 insertions(+), 62 deletions(-) diff --git a/backend-node/src/controllers/receivingController.ts b/backend-node/src/controllers/receivingController.ts index 3a1aeec4..ef0fa38f 100644 --- a/backend-node/src/controllers/receivingController.ts +++ b/backend-node/src/controllers/receivingController.ts @@ -246,10 +246,10 @@ export async function create(req: AuthenticatedRequest, res: Response) { } else { await client.query( `INSERT INTO inventory_stock ( - company_code, item_code, warehouse_code, location_code, + id, company_code, item_code, warehouse_code, location_code, current_qty, safety_qty, last_in_date, created_date, updated_date, writer - ) VALUES ($1, $2, $3, $4, $5, '0', NOW(), NOW(), NOW(), $6)`, + ) VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, '0', NOW(), NOW(), NOW(), $6)`, [companyCode, itemCode, whCode, locCode, String(inQty), userId] ); } diff --git a/frontend/components/pop/hardcoded/inbound/InboundCart.tsx b/frontend/components/pop/hardcoded/inbound/InboundCart.tsx index 2e6116d5..47de2029 100644 --- a/frontend/components/pop/hardcoded/inbound/InboundCart.tsx +++ b/frontend/components/pop/hardcoded/inbound/InboundCart.tsx @@ -140,14 +140,18 @@ export function InboundCart({ setInspectionTarget(null); }; - /* Confirm inbound */ + /* Confirm inbound — PC receivingController.create 와 동일한 body 구조 */ const handleConfirm = async () => { if (items.length === 0) return; + if (!selectedWarehouse) { + setResultMsg("오류: 입고 창고를 선택해주세요."); + return; + } setConfirming(true); setResultMsg(null); try { - // 1. Generate inbound number + // 1. 입고번호 채번 (RCV-YYYY-XXXX) let inboundNumber: string | undefined; try { const numRes = await apiClient.get("/receiving/generate-number"); @@ -155,24 +159,25 @@ export function InboundCart({ inboundNumber = numRes.data.data; } } catch { - // If number generation fails, backend will handle it + // 채번 실패 시 백엔드가 처리 } - // 2. Build payload with warehouse_code + // 2. POST /api/receiving — PC create 와 동일한 payload const payload = { - inbound_date: new Date().toISOString().slice(0, 10), inbound_number: inboundNumber, - warehouse_code: selectedWarehouse || undefined, - items: items.map((item) => ({ + inbound_date: new Date().toISOString().slice(0, 10), + warehouse_code: selectedWarehouse, + inbound_type: "구매입고", + items: items.map((item, idx) => ({ inbound_type: "구매입고", item_number: item.item_code, item_name: item.item_name, - spec: item.spec, - material: item.material, + spec: item.spec || "", + material: item.material || "", unit: "EA", inbound_qty: String(item.inbound_qty), - unit_price: String(item.unit_price), - total_amount: String(item.inbound_qty * item.unit_price), + unit_price: String(item.unit_price || 0), + total_amount: String((item.inbound_qty || 0) * (item.unit_price || 0)), reference_number: item.purchase_no, supplier_code: item.supplier_code, supplier_name: item.supplier_name, @@ -182,27 +187,30 @@ export function InboundCart({ ? "검사대기" : "합격", source_table: item.source_table, - source_id: item.source_id, + source_id: item.source_id || item.id, + seq_no: idx + 1, })), }; const res = await apiClient.post("/receiving", payload); if (res.data?.success) { - // 3. Clean up cart_items in DB (background, non-blocking) - const sourceIds = items.map((item) => item.source_id).filter(Boolean); - if (sourceIds.length > 0) { + // 3. cart_items DB 정리 (백그라운드, 논블로킹) + // cart_items.row_key 로 삭제 (row_key = source_id 로 저장됨) + const rowKeys = items.map((item) => item.source_id || item.id).filter(Boolean); + if (rowKeys.length > 0) { apiClient.post("/pop/execute-action", { tasks: [{ type: "cart-save" }], cartChanges: { - toDelete: sourceIds, + toDelete: rowKeys, }, }).catch(() => { - // cart cleanup failed silently + // cart cleanup 실패 시 무시 }); } - setResultMsg(`${items.length}건 입고 등록 완료!`); + const inboundNo = res.data?.data?.header?.inbound_number || inboundNumber || ""; + setResultMsg(`${items.length}건 입고 등록 완료! (${inboundNo})`); setTimeout(() => { onClear(); onClose(); diff --git a/frontend/components/pop/hardcoded/inbound/PurchaseInbound.tsx b/frontend/components/pop/hardcoded/inbound/PurchaseInbound.tsx index c5637fcb..6cf00c13 100644 --- a/frontend/components/pop/hardcoded/inbound/PurchaseInbound.tsx +++ b/frontend/components/pop/hardcoded/inbound/PurchaseInbound.tsx @@ -124,28 +124,22 @@ export function PurchaseInbound({ onCartCountChange, externalCartOpen, onExterna const supplierInputRef = useRef(null); const supplierDropdownRef = useRef(null); - /* Fetch all suppliers for inline search */ + /* Fetch all suppliers for inline search (supplier_mng = 공급사) */ const fetchAllSuppliers = useCallback(async () => { try { - const res = await apiClient.get("/data/customer_info", { params: { pageSize: 500 } }); + const res = await apiClient.get("/data/supplier_mng", { params: { pageSize: 500 } }); const data = res.data?.data ?? res.data?.rows ?? []; const list: Supplier[] = (Array.isArray(data) ? data : []).map((r: Record) => ({ id: String(r.id ?? ""), - customer_name: String(r.customer_name ?? r.name ?? ""), - customer_code: String(r.customer_code ?? r.code ?? ""), + customer_name: String(r.supplier_name ?? r.customer_name ?? r.name ?? ""), + customer_code: String(r.supplier_code ?? r.customer_code ?? r.code ?? ""), business_number: String(r.business_number ?? ""), - phone: String(r.phone ?? ""), + phone: String(r.contact_phone ?? r.phone ?? ""), address: String(r.address ?? ""), })); setAllSuppliers(list); } catch { - setAllSuppliers([ - { id: "d1", customer_name: "(주)한국철강", customer_code: "C001" }, - { id: "d2", customer_name: "(주)대한알루미늄", customer_code: "C002" }, - { id: "d3", customer_name: "삼성기공", customer_code: "C003" }, - { id: "d4", customer_name: "(주)금강볼트", customer_code: "C004" }, - { id: "d5", customer_name: "태양산업", customer_code: "C005" }, - ]); + setAllSuppliers([]); } }, []); @@ -273,22 +267,29 @@ export function PurchaseInbound({ onCartCountChange, externalCartOpen, onExterna setNumpadTarget(null); // Save to cart_items table (background, non-blocking) + // cart_items 실제 컬럼: screen_id, source_table, row_key, row_data, quantity, unit apiClient.post("/pop/execute-action", { tasks: [{ type: "cart-save" }], cartChanges: { toCreate: [{ screen_id: "pop-purchase-inbound", - item_code: order.item_code, - item_name: order.item_name, - qty: String(Math.min(qty, order.remain_qty)), - supplier_code: order.supplier_code, - supplier_name: order.supplier_name, - purchase_no: order.purchase_no, source_table: order.source_table, - source_id: order.id, - unit_price: String(order.unit_price || 0), - spec: order.spec || "", - material: order.material || "", + row_key: order.id, + row_data: JSON.stringify({ + item_code: order.item_code, + item_name: order.item_name, + supplier_code: order.supplier_code, + supplier_name: order.supplier_name, + purchase_no: order.purchase_no, + unit_price: order.unit_price || 0, + spec: order.spec || "", + material: order.material || "", + order_qty: order.order_qty, + remain_qty: order.remain_qty, + }), + quantity: String(Math.min(qty, order.remain_qty)), + unit: "EA", + status: "pending", }], }, }).catch(() => { diff --git a/frontend/components/pop/hardcoded/inbound/SupplierModal.tsx b/frontend/components/pop/hardcoded/inbound/SupplierModal.tsx index cfcde88d..111edb0f 100644 --- a/frontend/components/pop/hardcoded/inbound/SupplierModal.tsx +++ b/frontend/components/pop/hardcoded/inbound/SupplierModal.tsx @@ -82,39 +82,43 @@ export function SupplierModal({ open, onClose, onSelect }: SupplierModalProps) { const [sortMode, setSortMode] = useState<"korean" | "abc">("korean"); const [loading, setLoading] = useState(false); - /* Fetch suppliers */ + /* Fetch suppliers (supplier_mng = 공급사/거래처) */ const fetchSuppliers = useCallback(async () => { setLoading(true); try { - const res = await apiClient.get("/data/customer_info", { + // 구매입고 거래처 = supplier_mng (공급사) + const res = await apiClient.get("/data/supplier_mng", { params: { pageSize: 500 }, }); const data = res.data?.data ?? res.data?.rows ?? []; const list: Supplier[] = (Array.isArray(data) ? data : []).map((r: Record) => ({ id: String(r.id ?? ""), - customer_name: String(r.customer_name ?? r.name ?? ""), - customer_code: String(r.customer_code ?? r.code ?? ""), + customer_name: String(r.supplier_name ?? r.customer_name ?? r.name ?? ""), + customer_code: String(r.supplier_code ?? r.customer_code ?? r.code ?? ""), business_number: String(r.business_number ?? ""), - phone: String(r.phone ?? ""), + phone: String(r.contact_phone ?? r.phone ?? ""), address: String(r.address ?? ""), })); setSuppliers(list); } catch { - // fallback: dummy suppliers - setSuppliers([ - { id: "d1", customer_name: "(주)한국철강", customer_code: "C001" }, - { id: "d2", customer_name: "(주)대한알루미늄", customer_code: "C002" }, - { id: "d3", customer_name: "삼성기공", customer_code: "C003" }, - { id: "d4", customer_name: "(주)금강볼트", customer_code: "C004" }, - { id: "d5", customer_name: "태양산업", customer_code: "C005" }, - { id: "d6", customer_name: "가나다철강", customer_code: "C006" }, - { id: "d7", customer_name: "나이스플라스틱", customer_code: "C007" }, - { id: "d8", customer_name: "다솔기계", customer_code: "C008" }, - { id: "d9", customer_name: "마산정밀", customer_code: "C009" }, - { id: "d10", customer_name: "바다금속", customer_code: "C010" }, - { id: "d11", customer_name: "사단법인 산업협회", customer_code: "C011" }, - { id: "d12", customer_name: "아진산업", customer_code: "C012" }, - ]); + // fallback: customer_info 시도 + try { + const res2 = await apiClient.get("/data/customer_info", { + params: { pageSize: 500 }, + }); + const data2 = res2.data?.data ?? res2.data?.rows ?? []; + const list2: Supplier[] = (Array.isArray(data2) ? data2 : []).map((r: Record) => ({ + id: String(r.id ?? ""), + customer_name: String(r.customer_name ?? r.name ?? ""), + customer_code: String(r.customer_code ?? r.code ?? ""), + business_number: String(r.business_number ?? ""), + phone: String(r.phone ?? ""), + address: String(r.address ?? ""), + })); + setSuppliers(list2); + } catch { + setSuppliers([]); + } } finally { setLoading(false); }