refactor: [stocks] 재고생산 수정 화면을 등록과 동일한 레이아웃으로 변경
- BendingLotForm에 edit mode 추가 (initialData 프리필, update API 호출) - actions.ts에 updateBendingStockOrder 함수 추가 - [id]/page.tsx에서 StockProductionForm → BendingLotForm으로 전환 - StockProductionForm.tsx 삭제 (수주서 형식 복사본, 더 이상 불필요)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* 재고생산 상세/수정 페이지
|
||||
*
|
||||
* - 기본: 상세 보기 (StockProductionDetail)
|
||||
* - ?mode=edit: 수정 (StockProductionForm)
|
||||
* - ?mode=edit: 수정 (BendingLotForm — 등록과 동일 레이아웃)
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
@@ -12,7 +12,7 @@ import { useParams, useSearchParams } from 'next/navigation';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { StockProductionDetail } from '@/components/stocks/StockProductionDetail';
|
||||
import { StockProductionForm } from '@/components/stocks/StockProductionForm';
|
||||
import { BendingLotForm } from '@/components/stocks/BendingLotForm';
|
||||
import { getStockOrderById, type StockOrder } from '@/components/stocks/actions';
|
||||
|
||||
function EditStockContent({ id }: { id: string }) {
|
||||
@@ -58,7 +58,7 @@ function EditStockContent({ id }: { id: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
return <StockProductionForm initialData={order} isEditMode />;
|
||||
return <BendingLotForm initialData={order} isEditMode />;
|
||||
}
|
||||
|
||||
export default function StockDetailPage() {
|
||||
|
||||
@@ -38,10 +38,12 @@ import {
|
||||
resolveBendingItem,
|
||||
generateBendingLot,
|
||||
createBendingStockOrder,
|
||||
updateBendingStockOrder,
|
||||
getMaterialLots,
|
||||
type BendingCodeMap,
|
||||
type BendingResolvedItem,
|
||||
type MaterialLot,
|
||||
type StockOrder,
|
||||
} from './actions';
|
||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||
|
||||
@@ -63,6 +65,20 @@ const bendingCreateConfig: DetailConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
const bendingEditConfig: DetailConfig = {
|
||||
title: '절곡품 재고생산',
|
||||
description: '절곡품 재고생산 정보를 수정합니다',
|
||||
icon: Package,
|
||||
basePath: '/sales/stocks',
|
||||
fields: [],
|
||||
actions: {
|
||||
showBack: true,
|
||||
showSave: true,
|
||||
submitLabel: '저장',
|
||||
backLabel: '취소',
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// LOT 프리뷰 날짜코드 생성
|
||||
// ============================================================================
|
||||
@@ -213,13 +229,33 @@ function MaterialLotModal({
|
||||
// Component
|
||||
// ============================================================================
|
||||
|
||||
export function BendingLotForm() {
|
||||
interface BendingLotFormProps {
|
||||
initialData?: StockOrder;
|
||||
isEditMode?: boolean;
|
||||
}
|
||||
|
||||
export function BendingLotForm({ initialData, isEditMode = false }: BendingLotFormProps) {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const locale = (params.locale as string) || 'ko';
|
||||
const basePath = `/${locale}/sales/stocks`;
|
||||
|
||||
const [form, setForm] = useState<BendingFormState>(getInitialForm);
|
||||
const [form, setForm] = useState<BendingFormState>(() => {
|
||||
if (initialData?.bendingLot) {
|
||||
const bl = initialData.bendingLot;
|
||||
return {
|
||||
regDate: getInitialForm().regDate,
|
||||
prodCode: bl.prodCode || '',
|
||||
specCode: bl.specCode || '',
|
||||
lengthCode: bl.lengthCode || '',
|
||||
quantity: initialData.targetStockQty || initialData.quantity || 1,
|
||||
rawLotNo: bl.rawLotNo || '',
|
||||
fabricLotNo: bl.fabricLotNo || '',
|
||||
memo: initialData.memo || '',
|
||||
};
|
||||
}
|
||||
return getInitialForm();
|
||||
});
|
||||
const [codeMap, setCodeMap] = useState<BendingCodeMap | null>(null);
|
||||
const [resolvedItem, setResolvedItem] = useState<BendingResolvedItem | null>(null);
|
||||
const [resolveError, setResolveError] = useState<string>('');
|
||||
@@ -228,7 +264,9 @@ export function BendingLotForm() {
|
||||
const [rawLotModalOpen, setRawLotModalOpen] = useState(false);
|
||||
const [fabricLotModalOpen, setFabricLotModalOpen] = useState(false);
|
||||
|
||||
// 코드맵 로드
|
||||
const config = isEditMode ? bendingEditConfig : bendingCreateConfig;
|
||||
|
||||
// 코드맵 로드 + edit mode 시 초기 품목 매핑 조회
|
||||
useEffect(() => {
|
||||
async function loadCodeMap() {
|
||||
const result = await getBendingCodeMap();
|
||||
@@ -238,6 +276,17 @@ export function BendingLotForm() {
|
||||
}
|
||||
if (result.success && result.data) {
|
||||
setCodeMap(result.data);
|
||||
|
||||
// edit mode: 초기 데이터의 품목 매핑 자동 조회
|
||||
if (initialData?.bendingLot) {
|
||||
const bl = initialData.bendingLot;
|
||||
if (bl.prodCode && bl.specCode && bl.lengthCode) {
|
||||
const itemResult = await resolveBendingItem(bl.prodCode, bl.specCode, bl.lengthCode);
|
||||
if (itemResult.success && itemResult.data) {
|
||||
setResolvedItem(itemResult.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || '코드맵 로딩에 실패했습니다.');
|
||||
}
|
||||
@@ -363,12 +412,12 @@ export function BendingLotForm() {
|
||||
|
||||
const lotData = lotResult.data;
|
||||
|
||||
// 2. 재고생산 저장
|
||||
// 2. 재고생산 저장/수정
|
||||
const itemName =
|
||||
resolvedItem?.item_name ||
|
||||
`${codeMap?.products.find((p) => p.code === form.prodCode)?.name || form.prodCode} ${codeMap?.specs.find((s) => s.code === form.specCode)?.name || form.specCode}`;
|
||||
|
||||
const saveResult = await createBendingStockOrder({
|
||||
const saveParams = {
|
||||
memo: form.memo,
|
||||
targetStockQty: form.quantity,
|
||||
bendingLot: {
|
||||
@@ -388,7 +437,11 @@ export function BendingLotForm() {
|
||||
quantity: form.quantity,
|
||||
unit: resolvedItem?.unit || 'EA',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const saveResult = isEditMode && initialData
|
||||
? await updateBendingStockOrder(initialData.id, saveParams)
|
||||
: await createBendingStockOrder(saveParams);
|
||||
|
||||
if (saveResult.__authError) {
|
||||
toast.error('인증이 만료되었습니다.');
|
||||
@@ -399,7 +452,7 @@ export function BendingLotForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('절곡품 재고생산이 등록되었습니다.');
|
||||
toast.success(isEditMode ? '절곡품 재고생산이 수정되었습니다.' : '절곡품 재고생산이 등록되었습니다.');
|
||||
if (saveResult.data?.id) {
|
||||
router.push(`${basePath}/${saveResult.data.id}`);
|
||||
} else {
|
||||
@@ -412,8 +465,12 @@ export function BendingLotForm() {
|
||||
|
||||
// 취소
|
||||
const handleCancel = useCallback(() => {
|
||||
router.push(basePath);
|
||||
}, [router, basePath]);
|
||||
if (isEditMode && initialData) {
|
||||
router.push(`${basePath}/${initialData.id}`);
|
||||
} else {
|
||||
router.push(basePath);
|
||||
}
|
||||
}, [isEditMode, initialData, router, basePath]);
|
||||
|
||||
// renderForm
|
||||
const renderFormContent = useMemo(
|
||||
@@ -550,15 +607,19 @@ export function BendingLotForm() {
|
||||
<FormSection title="LOT 정보" icon={Tag}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>생산품 LOT (프리뷰)</Label>
|
||||
<Label>{isEditMode ? '생산품 LOT' : '생산품 LOT (프리뷰)'}</Label>
|
||||
<Input
|
||||
value={lotPreview ? `${lotPreview}-___` : ''}
|
||||
value={isEditMode && initialData?.bendingLot?.lotNumber
|
||||
? initialData.bendingLot.lotNumber
|
||||
: lotPreview ? `${lotPreview}-___` : ''}
|
||||
disabled
|
||||
placeholder="품목/종류/등록일 선택 시 자동 생성"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
일련번호는 저장 시 자동 확정됩니다
|
||||
</p>
|
||||
{!isEditMode && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
일련번호는 저장 시 자동 확정됩니다
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>원자재 재질</Label>
|
||||
@@ -661,6 +722,8 @@ export function BendingLotForm() {
|
||||
resolvedItem,
|
||||
resolveError,
|
||||
isSmokeBarrier,
|
||||
isEditMode,
|
||||
initialData,
|
||||
handleProdChange,
|
||||
handleSpecChange,
|
||||
handleLengthChange,
|
||||
@@ -670,8 +733,8 @@ export function BendingLotForm() {
|
||||
return (
|
||||
<>
|
||||
<IntegratedDetailTemplate
|
||||
config={bendingCreateConfig}
|
||||
mode="create"
|
||||
config={config}
|
||||
mode={isEditMode ? 'edit' : 'create'}
|
||||
isLoading={isLoading}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSave}
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 재고생산 등록/수정 폼
|
||||
*
|
||||
* - 생산사유, 목표재고수량, 메모, 비고
|
||||
* - 품목 내역 (자동 추가, 수동 추가 불가)
|
||||
* - IntegratedDetailTemplate + renderForm 패턴
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { QuantityInput } from '@/components/ui/quantity-input';
|
||||
import { NumberInput } from '@/components/ui/number-input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Package, Trash2, MessageSquare, ClipboardList } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { FormSection } from '@/components/organisms/FormSection';
|
||||
import type { OrderItem } from '@/components/orders/ItemAddDialog';
|
||||
import { formatAmount } from '@/lib/utils/amount';
|
||||
import {
|
||||
createStockOrder,
|
||||
updateStockOrder,
|
||||
type StockOrder,
|
||||
type StockOrderFormData,
|
||||
type StockOrderItemFormData,
|
||||
} from './actions';
|
||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||
|
||||
// ============================================================================
|
||||
// Config
|
||||
// ============================================================================
|
||||
|
||||
const stockCreateConfig: DetailConfig = {
|
||||
title: '재고생산',
|
||||
description: '재고생산을 등록합니다',
|
||||
icon: Package,
|
||||
basePath: '/sales/stocks',
|
||||
fields: [],
|
||||
actions: {
|
||||
showBack: true,
|
||||
showSave: true,
|
||||
submitLabel: '저장',
|
||||
backLabel: '취소',
|
||||
},
|
||||
};
|
||||
|
||||
const stockEditConfig: DetailConfig = {
|
||||
title: '재고생산',
|
||||
description: '재고생산 정보를 수정합니다',
|
||||
icon: Package,
|
||||
basePath: '/sales/stocks',
|
||||
fields: [],
|
||||
actions: {
|
||||
showBack: true,
|
||||
showSave: true,
|
||||
submitLabel: '저장',
|
||||
backLabel: '취소',
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 폼 데이터
|
||||
// ============================================================================
|
||||
|
||||
interface StockFormData {
|
||||
productionReason: string;
|
||||
targetStockQty: string;
|
||||
memo: string;
|
||||
remarks: string;
|
||||
items: OrderItem[];
|
||||
}
|
||||
|
||||
const INITIAL_FORM: StockFormData = {
|
||||
productionReason: '',
|
||||
targetStockQty: '',
|
||||
memo: '',
|
||||
remarks: '',
|
||||
items: [],
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Props
|
||||
// ============================================================================
|
||||
|
||||
interface StockProductionFormProps {
|
||||
initialData?: StockOrder;
|
||||
isEditMode?: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Component
|
||||
// ============================================================================
|
||||
|
||||
export function StockProductionForm({
|
||||
initialData,
|
||||
isEditMode = false,
|
||||
}: StockProductionFormProps) {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const locale = (params.locale as string) || 'ko';
|
||||
const basePath = `/${locale}/sales/stocks`;
|
||||
|
||||
const config = isEditMode ? stockEditConfig : stockCreateConfig;
|
||||
|
||||
// 초기 데이터 변환
|
||||
const [form, setForm] = useState<StockFormData>(() => {
|
||||
if (initialData) {
|
||||
return {
|
||||
productionReason: initialData.productionReason || '',
|
||||
targetStockQty: initialData.targetStockQty ? String(initialData.targetStockQty) : '',
|
||||
memo: initialData.memo || '',
|
||||
remarks: initialData.remarks || '',
|
||||
items: initialData.items.map((item) => ({
|
||||
id: item.id,
|
||||
itemId: item.itemId,
|
||||
itemCode: item.itemCode,
|
||||
itemName: item.itemName,
|
||||
specification: item.specification,
|
||||
quantity: item.quantity,
|
||||
unit: item.unit,
|
||||
unitPrice: item.unitPrice,
|
||||
amount: item.totalAmount,
|
||||
})),
|
||||
};
|
||||
}
|
||||
return INITIAL_FORM;
|
||||
});
|
||||
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||
|
||||
// 필드 에러 초기화
|
||||
const clearFieldError = useCallback((field: string) => {
|
||||
setFieldErrors((prev) => {
|
||||
if (prev[field]) {
|
||||
const { [field]: _, ...rest } = prev;
|
||||
return rest;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 품목 삭제
|
||||
const handleRemoveItem = useCallback((itemId: string) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
items: prev.items.filter((item) => item.id !== itemId),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 품목 수량 변경
|
||||
const handleQuantityChange = useCallback((itemId: string, quantity: number) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
item.id === itemId
|
||||
? { ...item, quantity, amount: item.unitPrice * quantity }
|
||||
: item
|
||||
),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 유효성 검사
|
||||
const validate = useCallback((): boolean => {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (form.items.length === 0) {
|
||||
errors.items = '품목을 1개 이상 추가해주세요';
|
||||
}
|
||||
|
||||
setFieldErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
}, [form.items]);
|
||||
|
||||
// 저장
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!validate()) {
|
||||
toast.error('입력 정보를 확인해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData: StockOrderFormData = {
|
||||
orderTypeCode: 'STOCK',
|
||||
memo: form.memo,
|
||||
remarks: form.remarks,
|
||||
productionReason: form.productionReason,
|
||||
targetStockQty: Number(form.targetStockQty) || 0,
|
||||
items: form.items.map((item): StockOrderItemFormData => ({
|
||||
itemId: item.itemId,
|
||||
itemCode: item.itemCode,
|
||||
itemName: item.itemName,
|
||||
specification: item.specification,
|
||||
quantity: item.quantity,
|
||||
unit: item.unit || 'EA',
|
||||
unitPrice: item.unitPrice,
|
||||
})),
|
||||
};
|
||||
|
||||
const result = isEditMode && initialData
|
||||
? await updateStockOrder(initialData.id, formData)
|
||||
: await createStockOrder(formData);
|
||||
|
||||
if (result.__authError) {
|
||||
toast.error('인증이 만료되었습니다. 다시 로그인해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
toast.error(result.error || '저장에 실패했습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(isEditMode ? '재고생산이 수정되었습니다.' : '재고생산이 등록되었습니다.');
|
||||
|
||||
if (result.data?.id) {
|
||||
router.push(`${basePath}/${result.data.id}`);
|
||||
} else {
|
||||
router.push(basePath);
|
||||
}
|
||||
}, [form, isEditMode, initialData, validate, router, basePath]);
|
||||
|
||||
// 취소
|
||||
const handleCancel = useCallback(() => {
|
||||
if (isEditMode && initialData) {
|
||||
router.push(`${basePath}/${initialData.id}`);
|
||||
} else {
|
||||
router.push(basePath);
|
||||
}
|
||||
}, [isEditMode, initialData, router, basePath]);
|
||||
|
||||
// renderForm
|
||||
const renderFormContent = useMemo(
|
||||
() =>
|
||||
() => (
|
||||
<div className="space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
<FormSection title="기본 정보" icon={ClipboardList}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="productionReason">생산사유</Label>
|
||||
<Input
|
||||
id="productionReason"
|
||||
placeholder="생산사유를 입력하세요"
|
||||
value={form.productionReason}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, productionReason: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="targetStockQty">목표재고수량</Label>
|
||||
<NumberInput
|
||||
id="targetStockQty"
|
||||
placeholder="0"
|
||||
value={Number(form.targetStockQty) || 0}
|
||||
onChange={(value) => setForm((prev) => ({ ...prev, targetStockQty: String(value ?? 0) }))}
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
{/* 비고 */}
|
||||
<FormSection title="비고" icon={MessageSquare}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="memo">메모</Label>
|
||||
<Textarea
|
||||
id="memo"
|
||||
placeholder="메모를 입력하세요"
|
||||
value={form.memo}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, memo: e.target.value }))}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="remarks">비고</Label>
|
||||
<Textarea
|
||||
id="remarks"
|
||||
placeholder="비고를 입력하세요"
|
||||
value={form.remarks}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, remarks: e.target.value }))}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
{/* 품목 내역 */}
|
||||
<FormSection
|
||||
title="품목 내역"
|
||||
icon={Package}
|
||||
>
|
||||
{fieldErrors.items && (
|
||||
<p className="text-sm text-red-500 mb-3">{fieldErrors.items}</p>
|
||||
)}
|
||||
|
||||
{form.items.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<Package className="h-12 w-12 mx-auto mb-3 opacity-30" />
|
||||
<p>품목이 없습니다. 재고생산 저장 시 자동으로 추가됩니다.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-12">No</TableHead>
|
||||
<TableHead>품목명</TableHead>
|
||||
<TableHead>규격</TableHead>
|
||||
<TableHead className="w-24 text-center">수량</TableHead>
|
||||
<TableHead className="w-20 text-center">단위</TableHead>
|
||||
<TableHead className="w-28 text-right">단가</TableHead>
|
||||
<TableHead className="w-28 text-right">금액</TableHead>
|
||||
<TableHead className="w-16 text-center">삭제</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{form.items.map((item, index) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell className="text-center">{index + 1}</TableCell>
|
||||
<TableCell>{item.itemName}</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{item.specification || item.spec || '-'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<QuantityInput
|
||||
value={item.quantity}
|
||||
onChange={(value) => handleQuantityChange(item.id, value ?? 1)}
|
||||
min={1}
|
||||
className="w-20"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{item.unit || 'EA'}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatAmount(item.unitPrice)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatAmount((item.amount ?? item.unitPrice * item.quantity))}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 text-red-500 hover:text-red-700"
|
||||
onClick={() => handleRemoveItem(item.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</FormSection>
|
||||
</div>
|
||||
),
|
||||
[form, fieldErrors, handleQuantityChange, handleRemoveItem]
|
||||
);
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={config}
|
||||
mode={isEditMode ? 'edit' : 'create'}
|
||||
isLoading={false}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={() => handleSave()}
|
||||
renderForm={renderFormContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -724,6 +724,81 @@ export async function createBendingStockOrder(params: {
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
/**
|
||||
* 절곡품 재고생산 수정 (기존 orders API + bending_lot 확장)
|
||||
*/
|
||||
export async function updateBendingStockOrder(id: string, params: {
|
||||
memo?: string;
|
||||
targetStockQty: number;
|
||||
bendingLot: BendingLotFormData;
|
||||
item: {
|
||||
itemId?: number;
|
||||
itemCode?: string;
|
||||
itemName: string;
|
||||
specification?: string;
|
||||
quantity: number;
|
||||
unit?: string;
|
||||
};
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
data?: StockOrder;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
const apiData = {
|
||||
order_type_code: 'STOCK',
|
||||
memo: params.memo || null,
|
||||
remarks: null,
|
||||
options: {
|
||||
production_reason: '절곡품 재고생산',
|
||||
target_stock_qty: params.targetStockQty || null,
|
||||
bending_lot: {
|
||||
lot_number: params.bendingLot.lot_number,
|
||||
prod_code: params.bendingLot.prod_code,
|
||||
spec_code: params.bendingLot.spec_code,
|
||||
length_code: params.bendingLot.length_code,
|
||||
raw_lot_no: params.bendingLot.raw_lot_no || null,
|
||||
fabric_lot_no: params.bendingLot.fabric_lot_no || null,
|
||||
material: params.bendingLot.material || null,
|
||||
},
|
||||
},
|
||||
client_id: null,
|
||||
client_name: null,
|
||||
site_name: null,
|
||||
delivery_date: null,
|
||||
delivery_method_code: null,
|
||||
discount_rate: 0,
|
||||
discount_amount: 0,
|
||||
supply_amount: 0,
|
||||
tax_amount: 0,
|
||||
total_amount: 0,
|
||||
items: [
|
||||
{
|
||||
item_id: params.item.itemId || null,
|
||||
item_code: params.item.itemCode || null,
|
||||
item_name: params.item.itemName,
|
||||
specification: params.item.specification || null,
|
||||
quantity: params.item.quantity,
|
||||
unit: params.item.unit || 'EA',
|
||||
unit_price: 0,
|
||||
supply_amount: 0,
|
||||
tax_amount: 0,
|
||||
total_amount: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = await executeServerAction({
|
||||
url: buildApiUrl(`/api/v1/orders/${id}`),
|
||||
method: 'PUT',
|
||||
body: apiData,
|
||||
transform: (d: ApiStockOrder) => transformApiToFrontend(d),
|
||||
errorMessage: '절곡품 재고생산 수정에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
/**
|
||||
* 원자재 LOT 목록 조회 (수입검사 완료 입고 건)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user