From f2f18db449e79954c32d6fcca3a3ec79108f154e Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 30 Mar 2026 17:02:48 +0900 Subject: [PATCH] Refactor customer management page to improve price item handling - Update the logic for retrieving and processing customer item prices. - Replace the previous price mapping with a grouping mechanism based on item_id and today's date. - Enhance the handling of customer item codes and names to ensure proper aggregation. - Improve overall readability and maintainability of the code. This commit enhances the functionality of the customer management page by ensuring accurate price data is displayed based on the current date, improving user experience in managing customer items. --- .../(main)/COMPANY_29/sales/customer/page.tsx | 117 +++++++++++------- .../(main)/COMPANY_7/sales/customer/page.tsx | 117 +++++++++++------- 2 files changed, 148 insertions(+), 86 deletions(-) diff --git a/frontend/app/(main)/COMPANY_29/sales/customer/page.tsx b/frontend/app/(main)/COMPANY_29/sales/customer/page.tsx index c2d521f3..045679e5 100644 --- a/frontend/app/(main)/COMPANY_29/sales/customer/page.tsx +++ b/frontend/app/(main)/COMPANY_29/sales/customer/page.tsx @@ -284,8 +284,8 @@ export default function CustomerManagementPage() { } catch { /* skip */ } } - // 3. customer_item_prices 조회 (단가 — 있으면 보강) - let priceMap: Record = {}; + // 3. customer_item_prices 조회 + let allPrices: any[] = []; if (mappings.length > 0) { try { const priceRes = await apiClient.post(`/table-management/tables/${PRICE_TABLE}/data`, { @@ -295,36 +295,43 @@ export default function CustomerManagementPage() { ]}, autoFilter: true, }); - const prices = priceRes.data?.data?.data || priceRes.data?.data?.rows || []; - // item_id별 최신 단가 매핑 - for (const p of prices) { - const key = p.item_id; - if (!priceMap[key] || (p.start_date && (!priceMap[key].start_date || p.start_date > priceMap[key].start_date))) { - priceMap[key] = p; - } - } + allPrices = priceRes.data?.data?.data || priceRes.data?.data?.rows || []; } catch { /* skip */ } } - // 4. 매핑 + 품목정보 + 단가 병합 + 카테고리 코드→라벨 + // 4. 매핑별 행 생성 + 오늘 날짜 기준 단가 + 같은 품목 첫 행만 품목코드/품명 표시 const priceResolve = (col: string, code: string) => { if (!code) return ""; return priceCategoryOptions[col]?.find((o) => o.code === code)?.label || code; }; - setPriceItems(mappings.map((m: any) => { - const itemInfo = itemMap[m.item_id] || {}; - const price = priceMap[m.item_id] || {}; + const today = new Date().toISOString().split("T")[0]; + const seenItemIds = new Set(); + + // item_id로 정렬하여 같은 품목끼리 묶기 + const sortedMappings = [...mappings].sort((a: any, b: any) => (a.item_id || "").localeCompare(b.item_id || "")); + + setPriceItems(sortedMappings.map((m: any) => { + const itemKey = m.item_id || ""; + const itemInfo = itemMap[itemKey] || {}; + const isFirstOfGroup = !seenItemIds.has(itemKey); + if (itemKey) seenItemIds.add(itemKey); + + // 오늘 날짜에 해당하는 단가 + const itemPriceList = allPrices.filter((p: any) => p.item_id === itemKey); + const todayPrice = itemPriceList.find((p: any) => + (!p.start_date || p.start_date <= today) && (!p.end_date || p.end_date >= today) + ) || itemPriceList[0] || {}; + return { ...m, - item_number: m.item_id, - item_name: itemInfo.item_name || "", - base_price_type: priceResolve("base_price_type", price.base_price_type || m.base_price_type || ""), - base_price: price.base_price || m.base_price || "", - discount_type: priceResolve("discount_type", price.discount_type || m.discount_type || ""), - discount_value: price.discount_value || m.discount_value || "", - rounding_type: priceResolve("rounding_unit_value", price.rounding_type || m.rounding_type || ""), - calculated_price: price.calculated_price || m.calculated_price || "", - currency_code: priceResolve("currency_code", price.currency_code || m.currency_code || ""), + item_number: isFirstOfGroup ? itemKey : "", + item_name: isFirstOfGroup ? (itemInfo.item_name || "") : "", + base_price_type: priceResolve("base_price_type", todayPrice.base_price_type || ""), + base_price: todayPrice.base_price || "", + discount_type: priceResolve("discount_type", todayPrice.discount_type || ""), + discount_value: todayPrice.discount_value || "", + calculated_price: todayPrice.calculated_price || todayPrice.unit_price || "", + currency_code: priceResolve("currency_code", todayPrice.currency_code || ""), }; })); } catch (err) { @@ -572,27 +579,51 @@ export default function CustomerManagementPage() { if (found) itemInfo = found; } catch { /* skip */ } - // 기존 매핑 데이터 → 거래처 품번/품명 - const mappingRows = [{ - _id: `m_existing_${row.id}`, - customer_item_code: row.customer_item_code || "", - customer_item_name: row.customer_item_name || "", - }].filter((m) => m.customer_item_code || m.customer_item_name); + // DB에서 해당 품목의 모든 매핑 조회 + let mappingRows: any[] = []; + try { + const mapRes = await apiClient.post(`/table-management/tables/${MAPPING_TABLE}/data`, { + page: 1, size: 100, + dataFilter: { enabled: true, filters: [ + { columnName: "customer_id", operator: "equals", value: selectedCustomer!.customer_code }, + { columnName: "item_id", operator: "equals", value: itemKey }, + ]}, autoFilter: true, + }); + const allMappings = mapRes.data?.data?.data || mapRes.data?.data?.rows || []; + mappingRows = allMappings + .filter((m: any) => m.customer_item_code || m.customer_item_name) + .map((m: any) => ({ + _id: `m_existing_${m.id}`, + customer_item_code: m.customer_item_code || "", + customer_item_name: m.customer_item_name || "", + })); + } catch { /* skip */ } - // 기존 단가 데이터 - const priceRows = [{ - _id: `p_existing_${row.id}`, - start_date: row.start_date || "", - end_date: row.end_date || "", - currency_code: row.currency_code || "CAT_MLAMDKVN_PZJI", - base_price_type: row.base_price_type || "CAT_MLAMFGFT_4RZW", - base_price: row.base_price ? String(row.base_price) : "", - discount_type: row.discount_type || "", - discount_value: row.discount_value ? String(row.discount_value) : "", - rounding_type: row.rounding_type || "", - rounding_unit_value: row.rounding_unit_value || "", - calculated_price: row.calculated_price ? String(row.calculated_price) : "", - }].filter((p) => p.base_price || p.start_date); + // DB에서 해당 품목의 모든 기간별 단가 조회 + let priceRows: any[] = []; + try { + const priceRes = await apiClient.post(`/table-management/tables/${PRICE_TABLE}/data`, { + page: 1, size: 100, + dataFilter: { enabled: true, filters: [ + { columnName: "customer_id", operator: "equals", value: selectedCustomer!.customer_code }, + { columnName: "item_id", operator: "equals", value: itemKey }, + ]}, autoFilter: true, + }); + const allPriceData = priceRes.data?.data?.data || priceRes.data?.data?.rows || []; + priceRows = allPriceData.map((p: any) => ({ + _id: `p_existing_${p.id}`, + start_date: p.start_date ? String(p.start_date).split("T")[0] : "", + end_date: p.end_date ? String(p.end_date).split("T")[0] : "", + currency_code: p.currency_code || "CAT_MLAMDKVN_PZJI", + base_price_type: p.base_price_type || "CAT_MLAMFGFT_4RZW", + base_price: p.base_price ? String(p.base_price) : "", + discount_type: p.discount_type || "", + discount_value: p.discount_value ? String(p.discount_value) : "", + rounding_type: p.rounding_type || "", + rounding_unit_value: p.rounding_unit_value || "", + calculated_price: p.calculated_price ? String(p.calculated_price) : "", + })); + } catch { /* skip */ } // 빈 단가 행이 없으면 하나 추가 if (priceRows.length === 0) { diff --git a/frontend/app/(main)/COMPANY_7/sales/customer/page.tsx b/frontend/app/(main)/COMPANY_7/sales/customer/page.tsx index c2d521f3..045679e5 100644 --- a/frontend/app/(main)/COMPANY_7/sales/customer/page.tsx +++ b/frontend/app/(main)/COMPANY_7/sales/customer/page.tsx @@ -284,8 +284,8 @@ export default function CustomerManagementPage() { } catch { /* skip */ } } - // 3. customer_item_prices 조회 (단가 — 있으면 보강) - let priceMap: Record = {}; + // 3. customer_item_prices 조회 + let allPrices: any[] = []; if (mappings.length > 0) { try { const priceRes = await apiClient.post(`/table-management/tables/${PRICE_TABLE}/data`, { @@ -295,36 +295,43 @@ export default function CustomerManagementPage() { ]}, autoFilter: true, }); - const prices = priceRes.data?.data?.data || priceRes.data?.data?.rows || []; - // item_id별 최신 단가 매핑 - for (const p of prices) { - const key = p.item_id; - if (!priceMap[key] || (p.start_date && (!priceMap[key].start_date || p.start_date > priceMap[key].start_date))) { - priceMap[key] = p; - } - } + allPrices = priceRes.data?.data?.data || priceRes.data?.data?.rows || []; } catch { /* skip */ } } - // 4. 매핑 + 품목정보 + 단가 병합 + 카테고리 코드→라벨 + // 4. 매핑별 행 생성 + 오늘 날짜 기준 단가 + 같은 품목 첫 행만 품목코드/품명 표시 const priceResolve = (col: string, code: string) => { if (!code) return ""; return priceCategoryOptions[col]?.find((o) => o.code === code)?.label || code; }; - setPriceItems(mappings.map((m: any) => { - const itemInfo = itemMap[m.item_id] || {}; - const price = priceMap[m.item_id] || {}; + const today = new Date().toISOString().split("T")[0]; + const seenItemIds = new Set(); + + // item_id로 정렬하여 같은 품목끼리 묶기 + const sortedMappings = [...mappings].sort((a: any, b: any) => (a.item_id || "").localeCompare(b.item_id || "")); + + setPriceItems(sortedMappings.map((m: any) => { + const itemKey = m.item_id || ""; + const itemInfo = itemMap[itemKey] || {}; + const isFirstOfGroup = !seenItemIds.has(itemKey); + if (itemKey) seenItemIds.add(itemKey); + + // 오늘 날짜에 해당하는 단가 + const itemPriceList = allPrices.filter((p: any) => p.item_id === itemKey); + const todayPrice = itemPriceList.find((p: any) => + (!p.start_date || p.start_date <= today) && (!p.end_date || p.end_date >= today) + ) || itemPriceList[0] || {}; + return { ...m, - item_number: m.item_id, - item_name: itemInfo.item_name || "", - base_price_type: priceResolve("base_price_type", price.base_price_type || m.base_price_type || ""), - base_price: price.base_price || m.base_price || "", - discount_type: priceResolve("discount_type", price.discount_type || m.discount_type || ""), - discount_value: price.discount_value || m.discount_value || "", - rounding_type: priceResolve("rounding_unit_value", price.rounding_type || m.rounding_type || ""), - calculated_price: price.calculated_price || m.calculated_price || "", - currency_code: priceResolve("currency_code", price.currency_code || m.currency_code || ""), + item_number: isFirstOfGroup ? itemKey : "", + item_name: isFirstOfGroup ? (itemInfo.item_name || "") : "", + base_price_type: priceResolve("base_price_type", todayPrice.base_price_type || ""), + base_price: todayPrice.base_price || "", + discount_type: priceResolve("discount_type", todayPrice.discount_type || ""), + discount_value: todayPrice.discount_value || "", + calculated_price: todayPrice.calculated_price || todayPrice.unit_price || "", + currency_code: priceResolve("currency_code", todayPrice.currency_code || ""), }; })); } catch (err) { @@ -572,27 +579,51 @@ export default function CustomerManagementPage() { if (found) itemInfo = found; } catch { /* skip */ } - // 기존 매핑 데이터 → 거래처 품번/품명 - const mappingRows = [{ - _id: `m_existing_${row.id}`, - customer_item_code: row.customer_item_code || "", - customer_item_name: row.customer_item_name || "", - }].filter((m) => m.customer_item_code || m.customer_item_name); + // DB에서 해당 품목의 모든 매핑 조회 + let mappingRows: any[] = []; + try { + const mapRes = await apiClient.post(`/table-management/tables/${MAPPING_TABLE}/data`, { + page: 1, size: 100, + dataFilter: { enabled: true, filters: [ + { columnName: "customer_id", operator: "equals", value: selectedCustomer!.customer_code }, + { columnName: "item_id", operator: "equals", value: itemKey }, + ]}, autoFilter: true, + }); + const allMappings = mapRes.data?.data?.data || mapRes.data?.data?.rows || []; + mappingRows = allMappings + .filter((m: any) => m.customer_item_code || m.customer_item_name) + .map((m: any) => ({ + _id: `m_existing_${m.id}`, + customer_item_code: m.customer_item_code || "", + customer_item_name: m.customer_item_name || "", + })); + } catch { /* skip */ } - // 기존 단가 데이터 - const priceRows = [{ - _id: `p_existing_${row.id}`, - start_date: row.start_date || "", - end_date: row.end_date || "", - currency_code: row.currency_code || "CAT_MLAMDKVN_PZJI", - base_price_type: row.base_price_type || "CAT_MLAMFGFT_4RZW", - base_price: row.base_price ? String(row.base_price) : "", - discount_type: row.discount_type || "", - discount_value: row.discount_value ? String(row.discount_value) : "", - rounding_type: row.rounding_type || "", - rounding_unit_value: row.rounding_unit_value || "", - calculated_price: row.calculated_price ? String(row.calculated_price) : "", - }].filter((p) => p.base_price || p.start_date); + // DB에서 해당 품목의 모든 기간별 단가 조회 + let priceRows: any[] = []; + try { + const priceRes = await apiClient.post(`/table-management/tables/${PRICE_TABLE}/data`, { + page: 1, size: 100, + dataFilter: { enabled: true, filters: [ + { columnName: "customer_id", operator: "equals", value: selectedCustomer!.customer_code }, + { columnName: "item_id", operator: "equals", value: itemKey }, + ]}, autoFilter: true, + }); + const allPriceData = priceRes.data?.data?.data || priceRes.data?.data?.rows || []; + priceRows = allPriceData.map((p: any) => ({ + _id: `p_existing_${p.id}`, + start_date: p.start_date ? String(p.start_date).split("T")[0] : "", + end_date: p.end_date ? String(p.end_date).split("T")[0] : "", + currency_code: p.currency_code || "CAT_MLAMDKVN_PZJI", + base_price_type: p.base_price_type || "CAT_MLAMFGFT_4RZW", + base_price: p.base_price ? String(p.base_price) : "", + discount_type: p.discount_type || "", + discount_value: p.discount_value ? String(p.discount_value) : "", + rounding_type: p.rounding_type || "", + rounding_unit_value: p.rounding_unit_value || "", + calculated_price: p.calculated_price ? String(p.calculated_price) : "", + })); + } catch { /* skip */ } // 빈 단가 행이 없으면 하나 추가 if (priceRows.length === 0) {