fix: Improve numbering rule handling and item routing functionality

- Added temporary debug response in `numberingRuleController` for better troubleshooting.
- Refactored SQL queries in `NumberingRuleService` to enhance parameter handling and improve clarity.
- Updated `ItemInfoPage` to correctly handle manual input values for user-generated codes.
- Implemented sorting logic in `ItemRoutingTab` to prioritize default routing versions and added functionality to set a version as default.

These changes aim to enhance the reliability and user experience in managing numbering rules and item routing processes.
This commit is contained in:
kjs
2026-04-09 12:18:26 +09:00
parent 51eddc6d84
commit a9d2df48bf
16 changed files with 309 additions and 25 deletions
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"
@@ -556,8 +556,8 @@ export default function ItemInfoPage() {
if (numberingRuleIdRef.current) {
try {
const hasManual = numberingParts.some(p => p.isManual);
const userInputCode = hasManual
? buildCodeFromParts(numberingParts, manualInputValue)
const userInputCode = hasManual && manualInputValue
? manualInputValue
: undefined;
const allocRes = await apiClient.post(
@@ -39,6 +39,7 @@ import {
type RoutingVersion,
} from "@/lib/api/processInfo";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
function normalizeDefaultFlag(v: RoutingVersion): boolean {
const raw = v.is_default as unknown;
@@ -210,6 +211,7 @@ export function ItemRoutingTab() {
return;
}
const list = res.data.map((v) => ({ ...v, is_default: normalizeDefaultFlag(v) }));
list.sort((a, b) => (a.is_default === b.is_default ? 0 : a.is_default ? -1 : 1));
setVersions(list);
const preferred = preferVersionId ? list.find((v) => v.id === preferVersionId) : undefined;
const def = list.find((v) => v.is_default);
@@ -399,6 +401,38 @@ export function ItemRoutingTab() {
}
};
// 선택 버전이 기본인지
const selectedVersionIsDefault = useMemo(() => {
if (!selectedVersionId) return false;
const v = versions.find((v) => v.id === selectedVersionId);
return v ? normalizeDefaultFlag(v) : false;
}, [selectedVersionId, versions]);
// 기본 라우팅으로 설정
const handleSetDefaultVersion = async () => {
if (!selectedVersionId || !selectedItem) return;
try {
// 기존 기본 해제
for (const v of versions) {
if (normalizeDefaultFlag(v) && v.id !== selectedVersionId) {
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: v.id },
updatedData: { is_default: false },
});
}
}
// 선택 버전 기본 설정
await apiClient.put(`/table-management/tables/item_routing_version/edit`, {
originalData: { id: selectedVersionId },
updatedData: { is_default: true },
});
toast.success("기본 라우팅으로 설정했어요");
await loadVersions(selectedItem, selectedVersionId);
} catch {
toast.error("기본 설정에 실패했어요");
}
};
const submitNewVersion = async () => {
if (!selectedItem) return;
const name = versionName.trim();
@@ -639,6 +673,12 @@ export function ItemRoutingTab() {
</Badge>
</div>
<div className="flex gap-2">
{selectedVersionId && !selectedVersionIsDefault && (
<Button variant="outline" size="sm" className="gap-1" onClick={handleSetDefaultVersion}>
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
</Button>
)}
<Button
variant="secondary"
size="sm"