import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, useForm } from '@inertiajs/react'; import { Button } from '@/Components/ui/button'; import { Input } from '@/Components/ui/input'; import { Label } from '@/Components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/Components/ui/select'; import { useState } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/Components/ui/table'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/Components/ui/alert-dialog"; import { Search, Trash2, Calendar as CalendarIcon, Save, ArrowLeft, Package } from 'lucide-react'; import axios from 'axios'; interface POItem { id: number; product_id: number; product: { name: string; sku: string }; quantity: number; received_quantity: number; unit_price: number; } interface PO { id: number; code: string; vendor_id: number; vendor: { id: number; name: string }; warehouse_id: number | null; items: POItem[]; } export default function GoodsReceiptCreate({ warehouses }: { warehouses: any[] }) { const [poSearch, setPoSearch] = useState(''); const [foundPOs, setFoundPOs] = useState([]); const [selectedPO, setSelectedPO] = useState(null); const [isSearching, setIsSearching] = useState(false); // Manual Selection States const [vendorSearch, setVendorSearch] = useState(''); const [foundVendors, setFoundVendors] = useState([]); const [selectedVendor, setSelectedVendor] = useState(null); const [productSearch, setProductSearch] = useState(''); const [foundProducts, setFoundProducts] = useState([]); const { data, setData, post, processing, errors } = useForm({ type: 'standard', // 'standard', 'miscellaneous', 'other' warehouse_id: '', purchase_order_id: '', vendor_id: '', received_date: new Date().toISOString().split('T')[0], remarks: '', items: [] as any[], }); const searchPO = async () => { if (!poSearch) return; setIsSearching(true); try { const response = await axios.get(route('goods-receipts.search-pos'), { params: { query: poSearch }, }); setFoundPOs(response.data); } catch (error) { console.error('Failed to search POs', error); } finally { setIsSearching(false); } }; const searchVendors = async () => { if (!vendorSearch) return; setIsSearching(true); try { const response = await axios.get(route('goods-receipts.search-vendors'), { params: { query: vendorSearch }, }); setFoundVendors(response.data); } catch (error) { console.error('Failed to search vendors', error); } finally { setIsSearching(false); } }; const searchProducts = async () => { if (!productSearch) return; setIsSearching(true); try { const response = await axios.get(route('goods-receipts.search-products'), { params: { query: productSearch }, }); setFoundProducts(response.data); } catch (error) { console.error('Failed to search products', error); } finally { setIsSearching(false); } }; const handleSelectPO = (po: PO) => { setSelectedPO(po); setSelectedVendor(po.vendor); const pendingItems = po.items.map((item) => { const remaining = item.quantity - item.received_quantity; return { product_id: item.product_id, purchase_order_item_id: item.id, product_name: item.product.name, sku: item.product.sku, quantity_ordered: item.quantity, quantity_received_so_far: item.received_quantity, quantity_received: remaining > 0 ? remaining : 0, unit_price: item.unit_price, batch_number: '', expiry_date: '', }; }); setData((prev) => ({ ...prev, purchase_order_id: po.id.toString(), vendor_id: po.vendor_id.toString(), warehouse_id: po.warehouse_id ? po.warehouse_id.toString() : prev.warehouse_id, items: pendingItems, })); setFoundPOs([]); }; const handleSelectVendor = (vendor: any) => { setSelectedVendor(vendor); setData('vendor_id', vendor.id.toString()); setFoundVendors([]); }; const handleAddProduct = (product: any) => { const newItem = { product_id: product.id, product_name: product.name, sku: product.code, quantity_received: 0, unit_price: product.price || 0, batch_number: '', expiry_date: '', }; setData('items', [...data.items, newItem]); setFoundProducts([]); setProductSearch(''); }; const removeItem = (index: number) => { const newItems = [...data.items]; newItems.splice(index, 1); setData('items', newItems); }; const updateItem = (index: number, field: string, value: any) => { const newItems = [...data.items]; newItems[index] = { ...newItems[index], [field]: value }; setData('items', newItems); }; const submit = (e: React.FormEvent) => { e.preventDefault(); post(route('goods-receipts.store')); }; return (
{/* Header */}

新增進貨單

建立新的進貨單並入庫

{/* Step 0: Select Type */}
{[ { id: 'standard', label: '標準採購', desc: '從採購單帶入' }, { id: 'miscellaneous', label: '雜項入庫', desc: '非採購之入庫' }, { id: 'other', label: '其他', desc: '其他原因入庫' }, ].map((t) => ( ))}
{/* Step 1: Source Selection */}
{(data.type === 'standard' ? selectedPO : selectedVendor) ? '✓' : '1'}

{data.type === 'standard' ? '選擇來源採購單' : '選擇供應商'}

{data.type === 'standard' ? ( !selectedPO ? (
setPoSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && searchPO()} className="h-9" />
{foundPOs.length > 0 && (
單號 供應商 操作 {foundPOs.map((po) => ( {po.code} {po.vendor?.name} ))}
)}
) : (
已選採購單 {selectedPO.code}
供應商 {selectedPO.vendor?.name}
) ) : ( !selectedVendor ? (
setVendorSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && searchVendors()} className="h-9" />
{foundVendors.length > 0 && (
名稱 代號 操作 {foundVendors.map((v) => ( {v.name} {v.code} ))}
)}
) : (
已選供應商 {selectedVendor.name}
供應商代號 {selectedVendor.code}
) )}
{/* Step 2: Details & Items */} {((data.type === 'standard' && selectedPO) || (data.type !== 'standard' && selectedVendor)) && (
2

進貨資訊與明細

{errors.warehouse_id &&

{errors.warehouse_id}

}
setData('received_date', e.target.value)} className="pl-9 h-9 block w-full" />
{errors.received_date &&

{errors.received_date}

}
setData('remarks', e.target.value)} className="h-9" placeholder="選填..." />

商品明細

{data.type !== 'standard' && (
setProductSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && searchProducts()} className="h-9 w-64 pl-9" /> {foundProducts.length > 0 && (
{foundProducts.map(p => ( ))}
)}
)}
商品資訊 {data.type === 'standard' ? '採購量 / 已收' : '規格'} 單價 收貨量 * 批號 效期 小計 {data.type !== 'standard' && } {data.items.length === 0 ? ( 尚無明細,請搜尋商品加入。 ) : ( data.items.map((item, index) => { const errorKey = `items.${index}.quantity_received` as keyof typeof errors; return (
{item.product_name}
{item.sku}
{data.type === 'standard' ? `${item.quantity_ordered} / ${item.quantity_received_so_far}` : '一般'} updateItem(index, 'unit_price', e.target.value)} className="h-8 text-right w-20 ml-auto" disabled={data.type === 'standard'} /> updateItem(index, 'quantity_received', e.target.value)} className={`h-8 w-20 ${errors[errorKey] ? 'border-red-500' : ''}`} /> {errors[errorKey] && (

{errors[errorKey] as string}

)}
updateItem(index, 'batch_number', e.target.value)} placeholder="選填" className="h-8" /> updateItem(index, 'expiry_date', e.target.value)} className="h-8" /> ${(parseFloat(item.quantity_received || 0) * parseFloat(item.unit_price)).toLocaleString()} {data.type !== 'standard' && ( 確定要移除此商品嗎? 此動作將從清單中移除該商品,您之後需要重新搜尋才能再次加入。 取消 removeItem(index)} className="bg-red-600 hover:bg-red-700" > 確定移除 )}
) }) )}
)}
{/* Bottom Action Bar */}
); }