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.
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, { item_name: string; unit: string }> = {};
|
||||
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);
|
||||
|
||||
@@ -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() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
paginatedRows.map((row) => {
|
||||
paginatedRows.map((row, idx) => {
|
||||
const isChecked = checkedIds.includes(row.id);
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={`${row.id}-${idx}`}
|
||||
className={cn(
|
||||
"cursor-pointer border-l-[3px] border-l-transparent transition-all",
|
||||
isChecked ? "border-l-primary bg-primary/5" : "hover:bg-accent/50"
|
||||
@@ -1401,6 +1402,7 @@ export default function ReceivingPage() {
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-[30px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-[70px] p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">입고유형</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목명</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">참조번호</TableHead>
|
||||
<TableHead className="w-[80px] p-2 text-right">
|
||||
@@ -1421,6 +1423,9 @@ export default function ReceivingPage() {
|
||||
<TableCell className="p-2 text-center">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell className="p-2">
|
||||
<Badge variant="outline" className="text-[10px]">{item.inbound_type || modalInboundType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, { item_name: string; unit: string }> = {};
|
||||
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);
|
||||
|
||||
@@ -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<SelectedSourceItem[]>([]);
|
||||
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() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
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 (
|
||||
<TableRow
|
||||
key={rowKey}
|
||||
key={`${row.id}-${idx}`}
|
||||
className={cn(
|
||||
"cursor-pointer border-l-[3px] border-l-transparent transition-all",
|
||||
isChecked ? "border-l-primary bg-primary/5" : "hover:bg-accent/50"
|
||||
@@ -1407,6 +1402,7 @@ export default function ReceivingPage() {
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-[30px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-[70px] p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">입고유형</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목명</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">참조번호</TableHead>
|
||||
<TableHead className="w-[80px] p-2 text-right">
|
||||
@@ -1427,6 +1423,9 @@ export default function ReceivingPage() {
|
||||
<TableCell className="p-2 text-center">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell className="p-2">
|
||||
<Badge variant="outline" className="text-[10px]">{item.inbound_type || modalInboundType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, { item_name: string; unit: string }> = {};
|
||||
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);
|
||||
|
||||
@@ -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() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
paginatedRows.map((row) => {
|
||||
paginatedRows.map((row, idx) => {
|
||||
const isChecked = checkedIds.includes(row.id);
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={`${row.id}-${idx}`}
|
||||
className={cn(
|
||||
"cursor-pointer border-l-[3px] border-l-transparent transition-all",
|
||||
isChecked ? "border-l-primary bg-primary/5" : "hover:bg-accent/50"
|
||||
@@ -1401,6 +1402,7 @@ export default function ReceivingPage() {
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-[30px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-[70px] p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">입고유형</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목명</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">참조번호</TableHead>
|
||||
<TableHead className="w-[80px] p-2 text-right">
|
||||
@@ -1421,6 +1423,9 @@ export default function ReceivingPage() {
|
||||
<TableCell className="p-2 text-center">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell className="p-2">
|
||||
<Badge variant="outline" className="text-[10px]">{item.inbound_type || modalInboundType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function InboundOutboundPage() {
|
||||
const [checkedIds, setCheckedIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// 품목명/단위 캐시
|
||||
const [itemMap, setItemMap] = useState<Record<string, { item_name: string; unit: string; width: string; height: string; thickness: string }>>({});
|
||||
const [itemMap, setItemMap] = useState<Record<string, { item_name: string; unit: string }>>({});
|
||||
const [warehouseMap, setWarehouseMap] = useState<Record<string, string>>({});
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, { item_name: string; unit: string; width: string; height: string; thickness: string }> = {};
|
||||
const map: Record<string, { item_name: string; unit: string }> = {};
|
||||
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() {
|
||||
<TableHead className="w-[80px] text-center text-[11px]">위치</TableHead>
|
||||
<TableHead className="w-[110px] text-[11px]">품목코드</TableHead>
|
||||
<TableHead className="w-[160px] text-[11px]">품목명</TableHead>
|
||||
<TableHead className="w-[60px] text-right text-[11px]">가로</TableHead>
|
||||
<TableHead className="w-[60px] text-right text-[11px]">세로</TableHead>
|
||||
<TableHead className="w-[60px] text-right text-[11px]">두께</TableHead>
|
||||
<TableHead className="w-[80px] text-right text-[11px]">수량</TableHead>
|
||||
<TableHead className="w-[50px] text-center text-[11px]">단위</TableHead>
|
||||
<TableHead className="w-[110px] text-[11px]">로트번호</TableHead>
|
||||
@@ -370,7 +361,6 @@ export default function InboundOutboundPage() {
|
||||
<Badge variant="outline" className="text-[10px]">{row._count}건</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell colSpan={3} />
|
||||
<TableCell className="text-right font-mono font-bold text-primary text-[13px]">
|
||||
{fmtNum(row._totalQty)}
|
||||
</TableCell>
|
||||
@@ -414,9 +404,6 @@ export default function InboundOutboundPage() {
|
||||
<TableCell className="text-center text-[12px]">{row.location_code || "-"}</TableCell>
|
||||
<TableCell className="text-[12px] font-mono">{row.item_code || "-"}</TableCell>
|
||||
<TableCell className="text-[13px]">{info?.item_name || "-"}</TableCell>
|
||||
<TableCell className="text-right text-[12px] font-mono text-muted-foreground">{info?.width || "-"}</TableCell>
|
||||
<TableCell className="text-right text-[12px] font-mono text-muted-foreground">{info?.height || "-"}</TableCell>
|
||||
<TableCell className="text-right text-[12px] font-mono text-muted-foreground">{info?.thickness || "-"}</TableCell>
|
||||
<TableCell className={cn("text-right font-mono font-semibold text-[13px]", isIn ? "text-emerald-600" : "text-amber-600")}>
|
||||
{isIn ? "+" : ""}{fmtNum(qty)}
|
||||
</TableCell>
|
||||
|
||||
@@ -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() {
|
||||
<col style={{ width: "110px" }} />
|
||||
<col style={{ width: "100px" }} />
|
||||
<col style={{ width: "140px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "90px" }} />
|
||||
<col style={{ width: "80px" }} />
|
||||
<col style={{ width: "80px" }} />
|
||||
@@ -1024,11 +1010,11 @@ export default function ReceivingPage() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
paginatedRows.map((row) => {
|
||||
paginatedRows.map((row, idx) => {
|
||||
const isChecked = checkedIds.includes(row.id);
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={`${row.id}-${idx}`}
|
||||
className={cn(
|
||||
"cursor-pointer border-l-[3px] border-l-transparent transition-all",
|
||||
isChecked ? "border-l-primary bg-primary/5" : "hover:bg-accent/50"
|
||||
@@ -1065,9 +1051,6 @@ export default function ReceivingPage() {
|
||||
<TableCell className="text-[13px] truncate max-w-[110px]"><span className="block truncate">{row.supplier_name || ""}</span></TableCell>
|
||||
<TableCell className="font-mono text-[13px]">{row.item_number || ""}</TableCell>
|
||||
<TableCell className="text-[13px] max-w-[140px]"><span className="block truncate" title={row.item_name || ""}>{row.item_name || ""}</span></TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] text-muted-foreground">{(row as any).width || "-"}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] text-muted-foreground">{(row as any).height || "-"}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] text-muted-foreground">{(row as any).thickness || "-"}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec || ""}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px]">{row.inbound_qty ? Number(row.inbound_qty).toLocaleString() : ""}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px]">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</TableCell>
|
||||
@@ -1419,6 +1402,7 @@ export default function ReceivingPage() {
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-[30px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-[70px] p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">입고유형</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목명</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">참조번호</TableHead>
|
||||
<TableHead className="w-[80px] p-2 text-right">
|
||||
@@ -1439,7 +1423,10 @@ export default function ReceivingPage() {
|
||||
<TableCell className="p-2 text-center">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[220px] p-2">
|
||||
<TableCell className="p-2">
|
||||
<Badge variant="outline" className="text-[10px]">{item.inbound_type || modalInboundType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
{item.item_name}
|
||||
@@ -1448,13 +1435,6 @@ export default function ReceivingPage() {
|
||||
{item.item_number}
|
||||
{item.spec ? ` | ${item.spec}` : ""}
|
||||
</span>
|
||||
{((item as any).width || (item as any).height || (item as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="p-2 text-[11px]">
|
||||
@@ -1620,20 +1600,13 @@ function SourcePurchaseOrderTable({
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[120px] truncate p-2 font-medium" title={po.purchase_no}>{po.purchase_no}</TableCell>
|
||||
<TableCell className="max-w-[120px] truncate p-2" title={po.supplier_name}>{po.supplier_name}</TableCell>
|
||||
<TableCell className="max-w-[220px] p-2">
|
||||
<TableCell className="max-w-[200px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={po.item_name}>{po.item_name}</span>
|
||||
<span className="text-muted-foreground truncate text-[10px]" title={`${po.item_code}${po.spec ? ` | ${po.spec}` : ""}`}>
|
||||
{po.item_code}
|
||||
{po.spec ? ` | ${po.spec}` : ""}
|
||||
</span>
|
||||
{((po as any).width || (po as any).height || (po as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
@@ -1711,20 +1684,13 @@ function SourceShipmentTable({
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={sh.partner_id}>{sh.partner_id}</TableCell>
|
||||
<TableCell className="max-w-[220px] p-2">
|
||||
<TableCell className="max-w-[200px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={sh.item_name}>{sh.item_name}</span>
|
||||
<span className="text-muted-foreground truncate text-[10px]" title={`${sh.item_code}${sh.spec ? ` | ${sh.spec}` : ""}`}>
|
||||
{sh.item_code}
|
||||
{sh.spec ? ` | ${sh.spec}` : ""}
|
||||
</span>
|
||||
{((sh as any).width || (sh as any).height || (sh as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="p-2 text-right font-semibold">
|
||||
@@ -1797,13 +1763,6 @@ function SourceItemTable({
|
||||
<span className="text-muted-foreground truncate text-[10px]" title={item.item_number}>
|
||||
{item.item_number}
|
||||
</span>
|
||||
{((item as any).width || (item as any).height || (item as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, { item_name: string; unit: string }> = {};
|
||||
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);
|
||||
|
||||
@@ -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() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
paginatedRows.map((row) => {
|
||||
paginatedRows.map((row, idx) => {
|
||||
const isChecked = checkedIds.includes(row.id);
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={`${row.id}-${idx}`}
|
||||
className={cn(
|
||||
"cursor-pointer border-l-[3px] border-l-transparent transition-all",
|
||||
isChecked ? "border-l-primary bg-primary/5" : "hover:bg-accent/50"
|
||||
@@ -1401,6 +1402,7 @@ export default function ReceivingPage() {
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-[30px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-[70px] p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">입고유형</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목명</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">참조번호</TableHead>
|
||||
<TableHead className="w-[80px] p-2 text-right">
|
||||
@@ -1421,6 +1423,9 @@ export default function ReceivingPage() {
|
||||
<TableCell className="p-2 text-center">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell className="p-2">
|
||||
<Badge variant="outline" className="text-[10px]">{item.inbound_type || modalInboundType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
|
||||
@@ -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<string, string> = {};
|
||||
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<string, { item_name: string; unit: string }> = {};
|
||||
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);
|
||||
|
||||
@@ -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() {
|
||||
<col style={{ width: "110px" }} />
|
||||
<col style={{ width: "100px" }} />
|
||||
<col style={{ width: "140px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "90px" }} />
|
||||
<col style={{ width: "80px" }} />
|
||||
<col style={{ width: "80px" }} />
|
||||
@@ -1007,6 +1022,9 @@ export default function OutboundPage() {
|
||||
<TableCell className="text-[13px] truncate max-w-[110px]"><span className="block truncate">{row.customer_name || ""}</span></TableCell>
|
||||
<TableCell className="font-mono text-[13px]">{row.item_number || ""}</TableCell>
|
||||
<TableCell className="text-[13px] max-w-[140px]"><span className="block truncate" title={row.item_name || ""}>{row.item_name || ""}</span></TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] text-muted-foreground">{(row as any).width || "-"}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] text-muted-foreground">{(row as any).height || "-"}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px] text-muted-foreground">{(row as any).thickness || "-"}</TableCell>
|
||||
<TableCell className="text-[13px] text-muted-foreground">{row.spec || ""}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px]">{row.outbound_qty ? Number(row.outbound_qty).toLocaleString() : ""}</TableCell>
|
||||
<TableCell className="text-right font-mono text-[13px]">{row.unit_price ? Number(row.unit_price).toLocaleString() : ""}</TableCell>
|
||||
@@ -1372,7 +1390,7 @@ export default function OutboundPage() {
|
||||
{resolveCat("outbound_type", item.outbound_type) || "-"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<TableCell className="max-w-[220px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
{item.item_name}
|
||||
@@ -1381,6 +1399,13 @@ export default function OutboundPage() {
|
||||
{item.item_number}
|
||||
{item.spec ? ` | ${item.spec}` : ""}
|
||||
</span>
|
||||
{((item as any).width || (item as any).height || (item as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="p-2 text-[11px]">{item.reference_number}</TableCell>
|
||||
@@ -1535,13 +1560,20 @@ function SourceShipmentInstructionTable({
|
||||
? new Date(si.instruction_date).toLocaleDateString("ko-KR")
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[200px] p-2">
|
||||
<TableCell className="max-w-[220px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={si.item_name}>{si.item_name}</span>
|
||||
<span className="text-muted-foreground truncate text-[10px]" title={`${si.item_code}${si.spec ? ` | ${si.spec}` : ""}`}>
|
||||
{si.item_code}
|
||||
{si.spec ? ` | ${si.spec}` : ""}
|
||||
</span>
|
||||
{((si as any).width || (si as any).height || (si as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
@@ -1612,13 +1644,20 @@ function SourcePurchaseOrderTable({
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[120px] truncate p-2 font-medium" title={po.purchase_no}>{po.purchase_no}</TableCell>
|
||||
<TableCell className="max-w-[120px] truncate p-2" title={po.supplier_name}>{po.supplier_name}</TableCell>
|
||||
<TableCell className="max-w-[200px] p-2">
|
||||
<TableCell className="max-w-[220px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={po.item_name}>{po.item_name}</span>
|
||||
<span className="text-muted-foreground truncate text-[10px]" title={`${po.item_code}${po.spec ? ` | ${po.spec}` : ""}`}>
|
||||
{po.item_code}
|
||||
{po.spec ? ` | ${po.spec}` : ""}
|
||||
</span>
|
||||
{((po as any).width || (po as any).height || (po as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
@@ -1692,6 +1731,13 @@ function SourceItemTable({
|
||||
<span className="text-muted-foreground truncate text-[10px]" title={item.item_number}>
|
||||
{item.item_number}
|
||||
</span>
|
||||
{((item as any).width || (item as any).height || (item as any).thickness) && (
|
||||
<span className="text-muted-foreground truncate text-[10px]">
|
||||
{(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}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
|
||||
@@ -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() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
paginatedRows.map((row) => {
|
||||
paginatedRows.map((row, idx) => {
|
||||
const isChecked = checkedIds.includes(row.id);
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={`${row.id}-${idx}`}
|
||||
className={cn(
|
||||
"cursor-pointer border-l-[3px] border-l-transparent transition-all",
|
||||
isChecked ? "border-l-primary bg-primary/5" : "hover:bg-accent/50"
|
||||
@@ -1401,6 +1402,7 @@ export default function ReceivingPage() {
|
||||
<TableHeader className="sticky top-0 z-10">
|
||||
<TableRow className="bg-muted hover:bg-muted">
|
||||
<TableHead className="w-[30px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">No</TableHead>
|
||||
<TableHead className="w-[70px] p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">입고유형</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목명</TableHead>
|
||||
<TableHead className="p-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">참조번호</TableHead>
|
||||
<TableHead className="w-[80px] p-2 text-right">
|
||||
@@ -1421,6 +1423,9 @@ export default function ReceivingPage() {
|
||||
<TableCell className="p-2 text-center">
|
||||
{idx + 1}
|
||||
</TableCell>
|
||||
<TableCell className="p-2">
|
||||
<Badge variant="outline" className="text-[10px]">{item.inbound_type || modalInboundType}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[180px] p-2">
|
||||
<div className="flex flex-col">
|
||||
<span className="truncate font-medium" title={item.item_name}>
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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 <TableCell key={col.key} className="text-[13px] text-muted-foreground">{row.spec}</TableCell>;
|
||||
case "width":
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono text-muted-foreground">{row.width || "-"}</TableCell>;
|
||||
case "height":
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono text-muted-foreground">{row.height || "-"}</TableCell>;
|
||||
case "thickness":
|
||||
return <TableCell key={col.key} className="text-[13px] text-right font-mono text-muted-foreground">{row.thickness || "-"}</TableCell>;
|
||||
case "unit":
|
||||
return <TableCell key={col.key} className="text-[13px]">{row.unit}</TableCell>;
|
||||
case "order_qty":
|
||||
@@ -1224,6 +1242,9 @@ export default function PurchaseOrderPage() {
|
||||
</TableHead>
|
||||
<TableHead className="w-[130px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목코드</TableHead>
|
||||
<TableHead className="min-w-[150px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
|
||||
<TableHead className="w-[60px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">가로</TableHead>
|
||||
<TableHead className="w-[60px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">세로</TableHead>
|
||||
<TableHead className="w-[60px] text-right text-[11px] font-bold uppercase tracking-wide text-muted-foreground">두께</TableHead>
|
||||
<TableHead className="w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">규격</TableHead>
|
||||
<TableHead className="w-[100px] 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>
|
||||
@@ -1231,7 +1252,7 @@ export default function PurchaseOrderPage() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{itemSearchResults.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={6} className="text-center text-muted-foreground py-8">검색 결과가 없어요</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={9} className="text-center text-muted-foreground py-8">검색 결과가 없어요</TableCell></TableRow>
|
||||
) : itemSearchResults.map((item) => (
|
||||
<TableRow key={item.id} className={cn("cursor-pointer", itemSelectedMap.has(item.id) && "bg-primary/5")}
|
||||
onClick={() => setItemSelectedMap((prev) => {
|
||||
@@ -1244,6 +1265,9 @@ export default function PurchaseOrderPage() {
|
||||
</TableCell>
|
||||
<TableCell className="text-[13px] max-w-[130px]"><span className="block truncate" title={item.item_number}>{item.item_number}</span></TableCell>
|
||||
<TableCell className="text-sm max-w-[150px]"><span className="block truncate" title={item.item_name}>{item.item_name}</span></TableCell>
|
||||
<TableCell className="text-right text-[13px] font-mono">{item.width || "-"}</TableCell>
|
||||
<TableCell className="text-right text-[13px] font-mono">{item.height || "-"}</TableCell>
|
||||
<TableCell className="text-right text-[13px] font-mono">{item.thickness || "-"}</TableCell>
|
||||
<TableCell className="text-[13px]">{item.size}</TableCell>
|
||||
<TableCell className="text-[13px]">{categoryOptions["item_material"]?.find((o) => o.code === item.material)?.label || item.material}</TableCell>
|
||||
<TableCell className="text-[13px]">{categoryOptions["item_inventory_unit"]?.find((o) => o.code === item.inventory_unit)?.label || item.inventory_unit}</TableCell>
|
||||
|
||||
@@ -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: "기준단가/구매단가" },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user