feat: Update Purchase Order and Sales Order pages for improved functionality
- Added a new manager selection dropdown in the Purchase Order page, displayed conditionally based on the input mode. - Adjusted the layout of the supplier information section to improve UI consistency. - Enhanced the Sales Order page to include dynamic filtering based on division codes, ensuring only relevant items are displayed. - Implemented recalculation of prices based on selected pricing modes and partner IDs, improving pricing accuracy during order processing. These changes aim to enhance user experience and streamline order management processes.
This commit is contained in:
@@ -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<string, string> = {};
|
||||
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<string, string> = {};
|
||||
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<string, string> = {};
|
||||
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() {
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[11px] font-semibold text-muted-foreground tracking-wide">단가방식</Label>
|
||||
<Select value={masterForm.price_mode || ""} onValueChange={(v) => setMasterForm((p) => ({ ...p, price_mode: v }))} disabled={isReadOnly}>
|
||||
<Select value={masterForm.price_mode || ""} onValueChange={(v) => { setMasterForm((p) => ({ ...p, price_mode: v })); recalcPrices(v, masterForm.supplier_code || ""); }} disabled={isReadOnly}>
|
||||
<SelectTrigger className="h-9"><SelectValue placeholder="선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["price_mode"] || []).map((o) => (
|
||||
@@ -742,15 +818,27 @@ export default function PurchaseOrderPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[11px] font-semibold text-muted-foreground tracking-wide">담당자</Label>
|
||||
<Select value={masterForm.manager || ""} onValueChange={(v) => setMasterForm((p) => ({ ...p, manager: v }))} disabled={isReadOnly}>
|
||||
<SelectTrigger className="h-9"><SelectValue placeholder="담당자 선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["manager"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 공급업체 / 담당자 */}
|
||||
{/* 공급업체 / 담당자 — 입력방식이 '공급업체 우선'일 때만 표시 */}
|
||||
{masterForm.input_mode === "supplierFirst" && (
|
||||
<div className="p-4 bg-muted/50 border border-dashed border-border rounded-lg space-y-3">
|
||||
<div className="text-xs font-semibold text-primary flex items-center gap-1.5">
|
||||
<ClipboardList className="w-3.5 h-3.5" /> 공급업체 정보
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3.5">
|
||||
<div className="grid grid-cols-2 gap-3.5">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[11px] font-semibold text-muted-foreground tracking-wide">공급업체</Label>
|
||||
<Select
|
||||
@@ -759,6 +847,7 @@ export default function PurchaseOrderPage() {
|
||||
const supp = categoryOptions["supplier_code"]?.find((o) => o.code === v);
|
||||
const name = supp?.label.replace(` (${v})`, "") || "";
|
||||
setMasterForm((p) => ({ ...p, supplier_code: v, supplier_name: name }));
|
||||
recalcPrices(masterForm.price_mode || "", v);
|
||||
}}
|
||||
disabled={isReadOnly}
|
||||
>
|
||||
@@ -770,23 +859,9 @@ export default function PurchaseOrderPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[11px] font-semibold text-muted-foreground tracking-wide">담당자</Label>
|
||||
<Select
|
||||
value={masterForm.manager || ""}
|
||||
onValueChange={(v) => setMasterForm((p) => ({ ...p, manager: v }))}
|
||||
disabled={isReadOnly}
|
||||
>
|
||||
<SelectTrigger className="h-9"><SelectValue placeholder="담당자 선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["manager"] || []).map((o) => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 품목 내역 */}
|
||||
<div className="space-y-2.5">
|
||||
@@ -823,6 +898,9 @@ export default function PurchaseOrderPage() {
|
||||
{!isReadOnly && <TableHead className="w-[40px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground"></TableHead>}
|
||||
<TableHead className="w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품번</TableHead>
|
||||
<TableHead className="w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
{masterForm.input_mode === "itemFirst" && (
|
||||
<TableHead className="w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">공급업체</TableHead>
|
||||
)}
|
||||
<TableHead className="w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-[60px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
|
||||
<TableHead className="w-[90px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">발주수량</TableHead>
|
||||
@@ -846,6 +924,27 @@ export default function PurchaseOrderPage() {
|
||||
)}
|
||||
<TableCell className="text-[13px] font-mono max-w-[120px]"><span className="block truncate" title={row.item_code}>{row.item_code}</span></TableCell>
|
||||
<TableCell className="text-[13px] max-w-[120px]"><span className="block truncate" title={row.item_name}>{row.item_name}</span></TableCell>
|
||||
{masterForm.input_mode === "itemFirst" && (
|
||||
<TableCell>
|
||||
{isReadOnly ? (
|
||||
<span className="text-xs">{(categoryOptions["supplier_code"] || []).find(o => o.code === row.supplier_code)?.label || row.supplier_code || "-"}</span>
|
||||
) : (
|
||||
<Select value={row.supplier_code || ""} onValueChange={(v) => {
|
||||
const supp = categoryOptions["supplier_code"]?.find(o => o.code === v);
|
||||
const name = supp?.label.replace(` (${v})`, "") || "";
|
||||
updateDetailRow(idx, "supplier_code", v);
|
||||
updateDetailRow(idx, "supplier_name", name);
|
||||
}}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공급업체" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["supplier_code"] || []).map(o => (
|
||||
<SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec}</TableCell>
|
||||
<TableCell className="text-[13px]">{row.unit}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -105,14 +105,39 @@ export default function PurchaseItemPage() {
|
||||
load();
|
||||
}, []);
|
||||
|
||||
// 좌측: 품목 조회
|
||||
// 구매품 division 코드 조회
|
||||
const [purchaseDivCodes, setPurchaseDivCodes] = useState<string[]>([]);
|
||||
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]);
|
||||
|
||||
|
||||
@@ -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); }
|
||||
};
|
||||
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, string> = {};
|
||||
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() {
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">단가방식</Label>
|
||||
<Select value={masterForm.price_mode || ""} onValueChange={(v) => setMasterForm((p) => ({ ...p, price_mode: v }))}>
|
||||
<Select value={masterForm.price_mode || ""} onValueChange={(v) => { setMasterForm((p) => ({ ...p, price_mode: v })); recalcPrices(v, masterForm.partner_id || ""); }}>
|
||||
<SelectTrigger className="h-9"><SelectValue placeholder="선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(categoryOptions["price_mode"] || []).map((o) => (
|
||||
@@ -1306,7 +1387,7 @@ export default function SalesOrderPage() {
|
||||
<Label className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">거래처</Label>
|
||||
<Select
|
||||
value={masterForm.partner_id || ""}
|
||||
onValueChange={(v) => { setMasterForm((p) => ({ ...p, partner_id: v, delivery_partner_id: "" })); loadDeliveryOptions(v); }}
|
||||
onValueChange={(v) => { setMasterForm((p) => ({ ...p, partner_id: v, delivery_partner_id: "" })); loadDeliveryOptions(v); recalcPrices(masterForm.price_mode || "", v); }}
|
||||
>
|
||||
<SelectTrigger className="h-9"><SelectValue placeholder="거래처 선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
Reference in New Issue
Block a user