diff --git a/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx b/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx index 8e5074c3..e8f9b1be 100644 --- a/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/purchase/order/page.tsx @@ -464,18 +464,25 @@ export default function PurchaseOrderPage() { if (itemSearchKeyword) { filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword }); } - if (itemSearchDivision !== "all") { - filters.push({ columnName: "division", operator: "contains", value: itemSearchDivision }); - } const res = await apiClient.post(`/table-management/tables/item_info/data`, { - page: p, size: s, + page: 1, size: 500, dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined, autoFilter: true, }); const resData = res.data?.data; - setItemSearchResults(resData?.data || resData?.rows || []); - setItemTotal(resData?.total || 0); - setItemTotalPages(resData?.totalPages || Math.ceil((resData?.total || 0) / s)); + let allRows = resData?.data || resData?.rows || []; + if (itemSearchDivision !== "all") { + const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || ""; + allRows = allRows.filter((item: any) => { + const div = item.division || ""; + return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel)); + }); + } + const total = allRows.length; + const start = (p - 1) * s; + setItemSearchResults(allRows.slice(start, start + s)); + setItemTotal(total); + setItemTotalPages(Math.max(1, Math.ceil(total / s))); } catch { /* skip */ } finally { setItemSearchLoading(false); } @@ -510,32 +517,42 @@ export default function PurchaseOrderPage() { if (selected.length === 0) { toast.error("품목을 선택해주세요."); return; } const supplierCode = masterForm.supplier_code; + const isStandard = masterForm.price_mode === "standard"; + const isSupplier = masterForm.price_mode === "supplier"; let supplierPriceMap: Record = {}; - if (supplierCode) { + if (isSupplier && supplierCode) { try { const itemIds = selected.map((item) => item.item_number || item.id); - const res = await apiClient.post(`/table-management/tables/supplier_item_mapping/data`, { + const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, { page: 1, size: 500, dataFilter: { enabled: true, filters: [ - { columnName: "supplier_code", operator: "equals", value: supplierCode }, - { columnName: "item_code", operator: "in", value: itemIds }, + { columnName: "supplier_id", operator: "equals", value: supplierCode }, + { columnName: "item_id", operator: "in", value: itemIds }, ], }, autoFilter: true, }); - const mappings = res.data?.data?.data || res.data?.data?.rows || []; - for (const m of mappings) { - const price = m.base_price || m.unit_price || ""; - if (price) supplierPriceMap[m.item_code] = String(price); + const prices = res.data?.data?.data || res.data?.data?.rows || []; + const today = new Date().toISOString().slice(0, 10); + for (const p of prices) { + if (p.start_date && p.start_date > today) continue; + if (p.end_date && p.end_date < today) continue; + const price = p.calculated_price || p.base_price || p.unit_price || ""; + if (price && Number(price) > 0) supplierPriceMap[p.item_id] = String(price); } } catch { /* skip */ } } const newRows = selected.map((item) => { const itemCode = item.item_number || item.id; - const unitPrice = supplierPriceMap[itemCode] || item.purchase_price || item.standard_price || ""; + let unitPrice = ""; + if (isStandard) { + unitPrice = item.purchase_price || item.standard_price || ""; + } else if (isSupplier && supplierCode) { + unitPrice = supplierPriceMap[itemCode] || ""; + } return { _id: `new_${Date.now()}_${Math.random()}`, item_code: itemCode, @@ -559,6 +576,65 @@ export default function PurchaseOrderPage() { setItemSelectOpen(false); }; + // 단가 재계산: 단가방식/공급업체 변경 시 기존 품목 단가 갱신 + const recalcPrices = useCallback(async (priceMode: string, supplierCode: string) => { + if (detailRows.length === 0) return; + const isStandard = priceMode === "standard"; + const isSupplier = priceMode === "supplier"; + + if (isStandard) { + const itemCodes = detailRows.map((r) => r.item_code).filter(Boolean); + if (itemCodes.length === 0) return; + try { + const res = await apiClient.post(`/table-management/tables/item_info/data`, { + page: 1, size: 500, + dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] }, + autoFilter: true, + }); + const items = res.data?.data?.data || res.data?.data?.rows || []; + const priceMap: Record = {}; + for (const item of items) { + const price = item.purchase_price || item.standard_price || ""; + if (price) priceMap[item.item_number] = String(price); + } + setDetailRows((prev) => prev.map((row) => { + const up = priceMap[row.item_code] || ""; + const qty = parseFloat(row.order_qty) || 0; + const price = parseFloat(up) || 0; + return { ...row, unit_price: up, amount: (qty * price).toString() }; + })); + } catch { /* skip */ } + } else if (isSupplier && supplierCode) { + const itemCodes = detailRows.map((r) => r.item_code).filter(Boolean); + if (itemCodes.length === 0) return; + try { + const res = await apiClient.post(`/table-management/tables/supplier_item_prices/data`, { + page: 1, size: 500, + dataFilter: { enabled: true, filters: [ + { columnName: "supplier_id", operator: "equals", value: supplierCode }, + { columnName: "item_id", operator: "in", value: itemCodes }, + ]}, + autoFilter: true, + }); + const prices = res.data?.data?.data || res.data?.data?.rows || []; + const today = new Date().toISOString().slice(0, 10); + const priceMap: Record = {}; + for (const p of prices) { + if (p.start_date && p.start_date > today) continue; + if (p.end_date && p.end_date < today) continue; + const price = p.calculated_price || p.base_price || p.unit_price || ""; + if (price && Number(price) > 0) priceMap[p.item_id] = String(price); + } + setDetailRows((prev) => prev.map((row) => { + const up = priceMap[row.item_code] || ""; + const qty = parseFloat(row.order_qty) || 0; + const price = parseFloat(up) || 0; + return { ...row, unit_price: up, amount: (qty * price).toString() }; + })); + } catch { /* skip */ } + } + }, [detailRows]); + const updateDetailRow = (idx: number, field: string, value: string) => { setDetailRows((prev) => { const next = [...prev]; @@ -733,7 +809,7 @@ export default function PurchaseOrderPage() {
- { setMasterForm((p) => ({ ...p, price_mode: v })); recalcPrices(v, masterForm.supplier_code || ""); }} disabled={isReadOnly}> {(categoryOptions["price_mode"] || []).map((o) => ( @@ -742,15 +818,27 @@ export default function PurchaseOrderPage() {
+
+ + +
- {/* 공급업체 / 담당자 */} + {/* 공급업체 / 담당자 — 입력방식이 '공급업체 우선'일 때만 표시 */} + {masterForm.input_mode === "supplierFirst" && (
공급업체 정보
-
+
-
- - -
+ )} {/* 품목 내역 */}
@@ -823,6 +898,9 @@ export default function PurchaseOrderPage() { {!isReadOnly && } 품번 품명 + {masterForm.input_mode === "itemFirst" && ( + 공급업체 + )} 규격 단위 발주수량 @@ -846,6 +924,27 @@ export default function PurchaseOrderPage() { )} {row.item_code} {row.item_name} + {masterForm.input_mode === "itemFirst" && ( + + {isReadOnly ? ( + {(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"} + ) : ( + + )} + + )} {row.spec} {row.unit} diff --git a/frontend/app/(main)/COMPANY_16/purchase/purchase-item/page.tsx b/frontend/app/(main)/COMPANY_16/purchase/purchase-item/page.tsx index c031bc9e..5061ce09 100644 --- a/frontend/app/(main)/COMPANY_16/purchase/purchase-item/page.tsx +++ b/frontend/app/(main)/COMPANY_16/purchase/purchase-item/page.tsx @@ -105,14 +105,39 @@ export default function PurchaseItemPage() { load(); }, []); - // 좌측: 품목 조회 + // 구매품 division 코드 조회 + const [purchaseDivCodes, setPurchaseDivCodes] = useState([]); + useEffect(() => { + const loadDiv = async () => { + try { + const res = await apiClient.get(`/table-categories/${ITEM_TABLE}/division/values`); + const flatten = (vals: any[]): { code: string; label: string }[] => { + const r: { code: string; label: string }[] = []; + for (const v of vals) { r.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) r.push(...flatten(v.children)); } + return r; + }; + const all = flatten(res.data?.data || []); + const codes = all.filter(o => o.label.includes("구매")).map(o => o.code); + setPurchaseDivCodes(codes); + } catch { /* skip */ } + }; + loadDiv(); + }, []); + + // 좌측: 품목 조회 (관리품목이 구매관리/구매품인 것만) const fetchItems = useCallback(async () => { + if (purchaseDivCodes.length === 0) return; setItemLoading(true); try { const filters = searchFilters.map((f) => ({ columnName: f.columnName, operator: f.operator, value: f.value })); + // division 필터 추가: 구매 관련 코드만 (in 연산자) + const allFilters = [ + ...filters, + { columnName: "division", operator: "in", value: purchaseDivCodes }, + ]; const res = await apiClient.post(`/table-management/tables/${ITEM_TABLE}/data`, { page: 1, size: 500, - dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined, + dataFilter: { enabled: true, filters: allFilters }, autoFilter: true, }); setItems(res.data?.data?.data || res.data?.data?.rows || []); @@ -121,7 +146,7 @@ export default function PurchaseItemPage() { } finally { setItemLoading(false); } - }, [searchFilters]); + }, [searchFilters, purchaseDivCodes]); useEffect(() => { fetchItems(); }, [fetchItems]); diff --git a/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx b/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx index 1d1b25d1..00ec01bc 100644 --- a/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/customer/page.tsx @@ -808,15 +808,19 @@ export default function CustomerManagementPage() { try { const filters: any[] = []; if (itemSearchKeyword) filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword }); - filters.push({ columnName: "division", operator: "contains", value: "CAT_ML8ZFVEL_1TOR" }); const res = await apiClient.post(`/table-management/tables/item_info/data`, { - page: 1, size: 50, + page: 1, size: 500, dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined, autoFilter: true, }); const allItems = res.data?.data?.data || res.data?.data?.rows || []; const existingItemIds = new Set(priceItems.map((p: any) => p.item_id || p.item_number)); - setItemSearchResults(allItems.filter((item: any) => !existingItemIds.has(item.item_number) && !existingItemIds.has(item.id))); + const SALES_CODE = "CAT_ML8ZFVEL_1TOR"; + setItemSearchResults(allItems.filter((item: any) => { + if (existingItemIds.has(item.item_number) || existingItemIds.has(item.id)) return false; + const div = item.division || ""; + return div.includes(SALES_CODE) || div.includes("영업"); + })); } catch { /* skip */ } finally { setItemSearchLoading(false); } }; diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx index 48ef567f..f896bbfd 100644 --- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx @@ -684,16 +684,26 @@ export default function SalesOrderPage() { try { const filters: any[] = []; if (itemSearchKeyword) filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword }); - if (itemSearchDivision !== "all") filters.push({ columnName: "division", operator: "contains", value: itemSearchDivision }); const res = await apiClient.post(`/table-management/tables/item_info/data`, { - page: p, size: s, + page: 1, size: 500, dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined, autoFilter: true, }); const resData = res.data?.data; - setItemSearchResults(resData?.data || resData?.rows || []); - setItemTotal(resData?.total || 0); - setItemTotalPages(resData?.totalPages || Math.ceil((resData?.total || 0) / s)); + let allRows = resData?.data || resData?.rows || []; + // 관리품목 필터 (코드/라벨 혼재 대응) + if (itemSearchDivision !== "all") { + const divLabel = categoryOptions["item_division"]?.find((o) => o.code === itemSearchDivision)?.label || ""; + allRows = allRows.filter((item: any) => { + const div = item.division || ""; + return div.includes(itemSearchDivision) || (divLabel && div.includes(divLabel)); + }); + } + const total = allRows.length; + const start = (p - 1) * s; + setItemSearchResults(allRows.slice(start, start + s)); + setItemTotal(total); + setItemTotalPages(Math.max(1, Math.ceil(total / s))); } catch { /* skip */ } finally { setItemSearchLoading(false); } @@ -732,7 +742,7 @@ export default function SalesOrderPage() { if (isCustomerPrice && partnerId) { try { const itemIds = selected.map((item) => item.item_number || item.id); - const res = await apiClient.post(`/table-management/tables/customer_item_mapping/data`, { + const res = await apiClient.post(`/table-management/tables/customer_item_prices/data`, { page: 1, size: 500, dataFilter: { enabled: true, @@ -743,10 +753,18 @@ export default function SalesOrderPage() { }, autoFilter: true, }); - const mappings = res.data?.data?.data || res.data?.data?.rows || []; - for (const m of mappings) { - const price = m.calculated_price || m.current_unit_price || ""; - if (price) customerPriceMap[m.item_id] = String(price); + const prices = res.data?.data?.data || res.data?.data?.rows || []; + const today = new Date().toISOString().slice(0, 10); + for (const p of prices) { + const start = p.start_date || ""; + const end = p.end_date || ""; + if (start && start > today) continue; + if (end && end < today) continue; + const price = p.calculated_price || p.base_price || p.unit_price || ""; + if (price && Number(price) > 0) { + const existing = customerPriceMap[p.item_id]; + if (!existing || Number(price) > 0) customerPriceMap[p.item_id] = String(price); + } } } catch { /* skip */ } } @@ -781,6 +799,69 @@ export default function SalesOrderPage() { setItemSelectOpen(false); }; + // 단가 재계산: 단가방식/거래처 변경 시 기존 품목 단가 갱신 + const recalcPrices = useCallback(async (priceMode: string, partnerId: string) => { + if (detailRows.length === 0) return; + const STANDARD_CODES = ["CAT_MM0BUZKL_HJ7U", "CAT_MLKG792S_54WJ"]; + const CUSTOMER_CODES = ["CAT_MM0BV3OS_41DX", "CAT_MLKG7D8K_N8SI"]; + const isStandard = STANDARD_CODES.includes(priceMode); + const isCustomer = CUSTOMER_CODES.includes(priceMode); + + if (isStandard) { + // 품목 기준단가 조회 + const itemCodes = detailRows.map((r) => r.part_code).filter(Boolean); + if (itemCodes.length === 0) return; + try { + const res = await apiClient.post(`/table-management/tables/item_info/data`, { + page: 1, size: 500, + dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "in", value: itemCodes }] }, + autoFilter: true, + }); + const items = res.data?.data?.data || res.data?.data?.rows || []; + const priceMap: Record = {}; + for (const item of items) { + const price = item.standard_price || item.selling_price || ""; + if (price) priceMap[item.item_number] = String(price); + } + setDetailRows((prev) => prev.map((row) => { + const up = priceMap[row.part_code] || ""; + const qty = parseFloat(row.qty) || 0; + const price = parseFloat(up) || 0; + return { ...row, unit_price: up, amount: (qty * price).toString() }; + })); + } catch { /* skip */ } + } else if (isCustomer && partnerId) { + // 거래처별 단가 조회 + const itemCodes = detailRows.map((r) => r.part_code).filter(Boolean); + if (itemCodes.length === 0) return; + try { + const res = await apiClient.post(`/table-management/tables/customer_item_prices/data`, { + page: 1, size: 500, + dataFilter: { enabled: true, filters: [ + { columnName: "customer_id", operator: "equals", value: partnerId }, + { columnName: "item_id", operator: "in", value: itemCodes }, + ]}, + autoFilter: true, + }); + const prices = res.data?.data?.data || res.data?.data?.rows || []; + const today = new Date().toISOString().slice(0, 10); + const priceMap: Record = {}; + for (const p of prices) { + if (p.start_date && p.start_date > today) continue; + if (p.end_date && p.end_date < today) continue; + const price = p.calculated_price || p.base_price || p.unit_price || ""; + if (price && Number(price) > 0) priceMap[p.item_id] = String(price); + } + setDetailRows((prev) => prev.map((row) => { + const up = priceMap[row.part_code] || ""; + const qty = parseFloat(row.qty) || 0; + const price = parseFloat(up) || 0; + return { ...row, unit_price: up, amount: (qty * price).toString() }; + })); + } catch { /* skip */ } + } + }, [detailRows]); + const updateDetailRow = (idx: number, field: string, value: string) => { setDetailRows((prev) => { const next = [...prev]; @@ -1277,7 +1358,7 @@ export default function SalesOrderPage() {
- { setMasterForm((p) => ({ ...p, price_mode: v })); recalcPrices(v, masterForm.partner_id || ""); }}> {(categoryOptions["price_mode"] || []).map((o) => ( @@ -1306,7 +1387,7 @@ export default function SalesOrderPage() {