/** * 生產工單完工入庫 - 選擇倉庫彈窗 * 含產出確認與耗損記錄功能 */ import React from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/Components/ui/dialog"; import { Button } from "@/Components/ui/button"; import { SearchableSelect } from "@/Components/ui/searchable-select"; import { Input } from "@/Components/ui/input"; import { Label } from "@/Components/ui/label"; import { Warehouse as WarehouseIcon, AlertTriangle, Tag, CalendarIcon, CheckCircle2, X } from "lucide-react"; import { formatQuantity } from "@/lib/utils"; interface Warehouse { id: number; name: string; } interface WarehouseSelectionModalProps { isOpen: boolean; onClose: () => void; onConfirm: (data: { warehouseId: number; batchNumber: string; expiryDate: string; actualOutputQuantity: number; lossReason: string; }) => void; warehouses: Warehouse[]; processing?: boolean; // 商品資訊用於產生批號 productCode?: string; productId?: number; // 預計產量(用於耗損計算) outputQuantity: number; // 成品單位名稱 unitName?: string; } export default function WarehouseSelectionModal({ isOpen, onClose, onConfirm, warehouses, processing = false, productCode, productId, outputQuantity, unitName = '', }: WarehouseSelectionModalProps) { const [selectedId, setSelectedId] = React.useState(null); const [batchNumber, setBatchNumber] = React.useState(""); const [expiryDate, setExpiryDate] = React.useState(""); const [actualOutputQuantity, setActualOutputQuantity] = React.useState(""); const [lossReason, setLossReason] = React.useState(""); // 當開啟時,初始化實際產出數量為預計產量 React.useEffect(() => { if (isOpen) { setActualOutputQuantity(String(outputQuantity)); setLossReason(""); } }, [isOpen, outputQuantity]); // 當開啟時,嘗試產生成品批號 (若有資訊) React.useEffect(() => { if (isOpen && productCode && productId) { const today = new Date().toISOString().split('T')[0].replace(/-/g, ''); const originCountry = 'TW'; // 先放一個預設值,實際序號由後端在儲存時再次確認或提供 API fetch(`/api/warehouses/${selectedId || warehouses[0]?.id || 1}/inventory/batches/${productId}?originCountry=${originCountry}&arrivalDate=${new Date().toISOString().split('T')[0]}`) .then(res => res.json()) .then(result => { const seq = result.nextSequence || '01'; setBatchNumber(`${productCode}-${originCountry}-${today}-${seq}`); }) .catch(() => { setBatchNumber(`${productCode}-${originCountry}-${today}-01`); }); } }, [isOpen, productCode, productId]); // 計算耗損數量 const actualQty = parseFloat(actualOutputQuantity) || 0; const lossQuantity = outputQuantity - actualQty; const hasLoss = lossQuantity > 0; const handleConfirm = () => { if (selectedId && batchNumber && actualQty > 0) { onConfirm({ warehouseId: selectedId, batchNumber, expiryDate, actualOutputQuantity: actualQty, lossReason: hasLoss ? lossReason : '', }); } }; // 驗證:實際產出不可大於預計產量,也不可小於等於 0 const isActualQtyValid = actualQty > 0 && actualQty <= outputQuantity; return ( !open && onClose()}> 完工入庫確認
{/* 倉庫選擇 */}
({ value: w.id.toString(), label: w.name }))} value={selectedId?.toString() || ""} onValueChange={(val) => setSelectedId(parseInt(val))} placeholder="請選擇倉庫..." className="w-full" />
{/* 成品批號 */}
setBatchNumber(e.target.value)} placeholder="輸入成品批號" className="h-9 font-mono" />
{/* 成品效期 */}
setExpiryDate(e.target.value)} className="h-9" />
{/* 分隔線 - 產出確認區 */}

產出確認

{/* 預計產量(唯讀) */}
預計產量 {formatQuantity(outputQuantity)} {unitName}
{/* 實際產出數量 */}
setActualOutputQuantity(e.target.value)} className={`h-9 font-bold ${!isActualQtyValid && actualOutputQuantity !== '' ? 'border-red-400 focus:ring-red-400' : ''}`} /> {unitName && {unitName}}
{actualQty > outputQuantity && (

實際產出不可超過預計產量

)}
{/* 耗損顯示 */} {hasLoss && (
耗損數量:{formatQuantity(lossQuantity)} {unitName}
setLossReason(e.target.value)} placeholder="例如:製作過程損耗、品質不合格..." className="h-9 border-orange-200 focus:ring-orange-400" />
)}
); }