feat: Enhance inventory and outbound pages with category mapping and user information
- Implemented user mapping to display user names instead of IDs in the inventory and receiving pages. - Added category mapping for materials and units in the outbound page, improving data representation. - Updated API calls to fetch user and category data, ensuring accurate and user-friendly displays. - These enhancements aim to improve the overall user experience by providing clearer information and better data management across multiple company implementations.
This commit is contained in:
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -140,31 +140,47 @@ export default function InventoryStatusPage() {
|
||||
Record<string, { code: string; label: string }[]>
|
||||
>({});
|
||||
|
||||
// 카테고리 로드
|
||||
// 사용자 맵 (writer → 이름)
|
||||
const [userMap, setUserMap] = useState<Record<string, string>>({});
|
||||
|
||||
// 카테고리 + 사용자 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const col of ["status", "unit"]) {
|
||||
// inventory_stock 카테고리
|
||||
for (const col of ["status"]) {
|
||||
try {
|
||||
const res = await apiClient.get(
|
||||
`/table-categories/${STOCK_TABLE}/${col}/values`
|
||||
);
|
||||
const res = await apiClient.get(`/table-categories/${STOCK_TABLE}/${col}/values`);
|
||||
if (res.data?.success) optMap[col] = flatten(res.data.data || []);
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
// item_info 단위 카테고리
|
||||
try {
|
||||
const res = await apiClient.get("/table-categories/item_info/unit/values");
|
||||
if (res.data?.success) optMap["item_unit"] = flatten(res.data.data || []);
|
||||
} catch { /* skip */ }
|
||||
setCategoryOptions(optMap);
|
||||
};
|
||||
load();
|
||||
// 사용자 목록 로드
|
||||
apiClient.get("/admin/users").then((res) => {
|
||||
const users = res.data?.data || res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
for (const u of users) {
|
||||
const id = u.user_id || u.id;
|
||||
const name = u.user_name || u.name || id;
|
||||
if (id) map[id] = name;
|
||||
}
|
||||
setUserMap(map);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 재고 목록 조회
|
||||
@@ -193,10 +209,12 @@ export default function InventoryStatusPage() {
|
||||
};
|
||||
const data = raw.map((r: any) => {
|
||||
const itemInfo = itemMap.get(r.item_code) as any;
|
||||
const rawUnit = itemInfo?.unit || r.unit || "";
|
||||
return {
|
||||
...r,
|
||||
item_name: itemInfo?.name || "",
|
||||
unit: itemInfo?.unit || resolve("unit", r.unit),
|
||||
unit: resolve("item_unit", rawUnit) || rawUnit,
|
||||
warehouse_code: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
warehouse_name: whMap.get(r.warehouse_code) || r.warehouse_code || "",
|
||||
status: resolve("status", r.status),
|
||||
_isLow: r.safety_qty && Number(r.current_qty) < Number(r.safety_qty),
|
||||
@@ -613,7 +631,7 @@ export default function InventoryStatusPage() {
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{h.remark || h.reason || ""}
|
||||
</TableCell>
|
||||
<TableCell>{h.writer || h.created_by || ""}</TableCell>
|
||||
<TableCell>{userMap[h.writer] || userMap[h.created_by] || h.writer || h.created_by || ""}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -311,6 +311,34 @@ export default function OutboundPage() {
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editItemIds, setEditItemIds] = useState<string[]>([]);
|
||||
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 소스 데이터
|
||||
const [sourceKeyword, setSourceKeyword] = useState("");
|
||||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
@@ -1337,6 +1365,7 @@ export default function OutboundPage() {
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1772,10 +1801,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1826,8 +1857,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,17 +353,45 @@ export default function ReceivingPage() {
|
||||
|
||||
// 구매관리 division 코드 (라벨 기준 조회)
|
||||
const [purchaseDivisionCode, setPurchaseDivisionCode] = useState<string>("");
|
||||
// 카테고리 코드→라벨 매핑 (재질, 단위)
|
||||
const [catMap, setCatMap] = useState<Record<string, Record<string, string>>>({});
|
||||
|
||||
// 구매관리 division 코드 로드
|
||||
// 구매관리 division 코드 + 재질/단위 카테고리 로드
|
||||
useEffect(() => {
|
||||
const flatten = (arr: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
for (const v of arr) {
|
||||
result.push({ code: v.valueCode || v.value_code || v.code, label: v.valueLabel || v.value_label || v.label });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// division 카테고리에서 "구매관리" 라벨의 코드 조회
|
||||
apiClient.get("/table-categories/item_info/division/values").then((res) => {
|
||||
const vals = res.data?.data || [];
|
||||
const found = vals.find((v: any) => (v.value_label || v.label) === "구매관리");
|
||||
const found = vals.find((v: any) => (v.valueLabel || v.value_label || v.label) === "구매관리");
|
||||
if (found) setPurchaseDivisionCode(found.value_code || found.code);
|
||||
}).catch(() => {});
|
||||
// 재질, 단위 카테고리
|
||||
const map: Record<string, Record<string, string>> = {};
|
||||
Promise.all(
|
||||
["material", "unit"].map(async (col) => {
|
||||
try {
|
||||
const res = await apiClient.get(`/table-categories/item_info/${col}/values`);
|
||||
const items = flatten(res.data?.data || []);
|
||||
map[col] = {};
|
||||
for (const item of items) map[col][item.code] = item.label;
|
||||
} catch { /* skip */ }
|
||||
})
|
||||
).then(() => setCatMap(map));
|
||||
}, []);
|
||||
|
||||
// 카테고리 코드→라벨 변환
|
||||
const resolveCat = useCallback((col: string, code: string) => {
|
||||
if (!code) return "";
|
||||
return catMap[col]?.[code] || code;
|
||||
}, [catMap]);
|
||||
|
||||
// 목록 조회
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -1287,6 +1315,7 @@ export default function ReceivingPage() {
|
||||
data={items}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
resolveCat={resolveCat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1742,10 +1771,12 @@ function SourceItemTable({
|
||||
data,
|
||||
onAdd,
|
||||
selectedKeys,
|
||||
resolveCat,
|
||||
}: {
|
||||
data: ItemSource[];
|
||||
onAdd: (item: ItemSource) => void;
|
||||
selectedKeys: string[];
|
||||
resolveCat: (col: string, code: string) => string;
|
||||
}) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
@@ -1798,8 +1829,8 @@ function SourceItemTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.spec || "-"}>{item.spec || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={item.material || "-"}>{item.material || "-"}</TableCell>
|
||||
<TableCell className="p-2">{item.unit || "-"}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate p-2" title={resolveCat("material", item.material) || "-"}>{resolveCat("material", item.material) || "-"}</TableCell>
|
||||
<TableCell className="p-2">{resolveCat("unit", item.unit) || "-"}</TableCell>
|
||||
<TableCell className="p-2 text-right">
|
||||
{Number(item.standard_price).toLocaleString()}
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user