This commit is contained in:
김보곤
2026-03-18 21:28:24 +09:00
4 changed files with 103 additions and 90 deletions

View File

@@ -366,7 +366,8 @@ export function BendingLotForm({ initialData, isEditMode = false }: BendingLotFo
if (result.success && result.data) {
setResolvedItem(result.data);
} else {
setResolveError('해당 조합에 매핑된 품목이 없습니다.');
const code = result.data?.expected_code;
setResolveError(`해당 조합에 매핑된 품목이 없습니다.${code ? ` (${code})` : ''}`);
}
}
},

View File

@@ -22,10 +22,6 @@ import {
} from '@/components/ui/table';
import {
Package,
Pencil,
CheckCircle2,
XCircle,
Factory,
ClipboardList,
MessageSquare,
Tag,
@@ -40,7 +36,6 @@ import {
getStockOrderById,
updateStockOrderStatus,
deleteStockOrder,
createStockProductionOrder,
type StockOrder,
type StockStatus,
} from './actions';
@@ -182,88 +177,12 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) {
}
}, [order, router, basePath]);
// 생산지시 생성
const handleCreateProductionOrder = useCallback(async () => {
if (!order) return;
setIsProcessing(true);
try {
const result = await createStockProductionOrder(order.id);
if (result.__authError) {
toast.error('인증이 만료되었습니다.');
return;
}
if (result.success) {
toast.success('생산지시가 생성되었습니다.');
// 상태 갱신
const refreshResult = await getStockOrderById(orderId);
if (refreshResult.success && refreshResult.data) {
setOrder(refreshResult.data);
}
} else {
toast.error(result.error || '생산지시 생성에 실패했습니다.');
}
} finally {
setIsProcessing(false);
}
}, [order, orderId]);
// 수정 이동
const handleEdit = useCallback(() => {
router.push(`${basePath}/${orderId}?mode=edit`);
}, [router, basePath, orderId]);
// 헤더 액션 버튼
// 재고생산: 저장 즉시 IN_PROGRESS (확정/생산지시 자동). draft/confirmed 분기 불필요.
const headerActionItems = useMemo((): ActionItem[] => {
if (!order) return [];
const items: ActionItem[] = [];
// draft → 확정 가능
if (order.status === 'draft') {
items.push({
icon: CheckCircle2,
label: '확정',
onClick: () => handleStatusChange('confirmed'),
className: 'bg-blue-600 hover:bg-blue-700 text-white',
disabled: isProcessing,
});
items.push({
icon: Pencil,
label: '수정',
onClick: handleEdit,
variant: 'outline',
});
}
// confirmed → 생산지시 생성 + 수정 불가 안내
if (order.status === 'confirmed') {
items.push({
icon: Factory,
label: '생산지시 생성',
onClick: handleCreateProductionOrder,
className: 'bg-green-600 hover:bg-green-500 text-white',
disabled: isProcessing,
});
items.push({
icon: Pencil,
label: '수정',
onClick: () => toast.warning('확정 상태에서는 수정이 불가합니다.'),
variant: 'outline',
disabled: false,
className: 'opacity-50',
});
}
// draft/confirmed → 취소 가능
if (order.status === 'draft' || order.status === 'confirmed') {
items.push({
icon: XCircle,
label: '취소',
onClick: () => handleStatusChange('cancelled'),
variant: 'destructive',
disabled: isProcessing,
});
}
// cancelled → 복원 버튼
if (order.status === 'cancelled') {
items.push({
@@ -276,7 +195,7 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) {
}
return items;
}, [order, isProcessing, handleStatusChange, handleEdit, handleCreateProductionOrder]);
}, [order, isProcessing, handleStatusChange]);
// 상태 뱃지 — 기본 정보 카드에 이미 표시되므로 하단 바에는 미표시
const headerActions = useMemo(() => {

View File

@@ -544,6 +544,7 @@ export interface BendingResolvedItem {
item_name: string;
specification: string;
unit: string;
expected_code?: string;
}
export interface BendingLotResult {
@@ -612,12 +613,34 @@ export async function resolveBendingItem(
error?: string;
__authError?: boolean;
}> {
const result = await executeServerAction<BendingResolvedItem>({
url: buildApiUrl('/api/v1/bending/resolve-item', { prod, spec, length }),
errorMessage: '품목 매핑 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, __authError: true };
return { success: result.success, data: result.data, error: result.error };
const { serverFetch } = await import('@/lib/api/fetch-wrapper');
const { safeResponseJson } = await import('@/lib/api/safe-json-parse');
const url = buildApiUrl('/api/v1/bending/resolve-item', { prod, spec, length });
const { response, error: fetchError } = await serverFetch(url, { method: 'GET', cache: 'no-store' });
if (fetchError) {
return { success: false, error: fetchError.message, __authError: fetchError.__authError };
}
if (!response) {
return { success: false, error: '품목 매핑 조회에 실패했습니다.' };
}
const raw = await safeResponseJson(response) as Record<string, unknown>;
if (!response.ok || !raw.success) {
// NOT_MAPPED: error.expected_code 추출
const errorObj = raw.error as Record<string, unknown> | undefined;
const expectedCode = (errorObj?.expected_code as string) || undefined;
return {
success: false,
data: expectedCode ? { expected_code: expectedCode } as BendingResolvedItem : undefined,
error: (raw.message as string) || '해당 조합에 매핑된 품목이 없습니다.',
};
}
// 성공: data에 expected_code 포함
const data = raw.data as BendingResolvedItem;
return { success: true, data };
}
/**