import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, Link, useForm, router } from '@inertiajs/react'; import { usePermission } from '@/hooks/usePermission'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/Components/ui/table"; import { Button } from "@/Components/ui/button"; import { Input } from "@/Components/ui/input"; import { StatusBadge } from "@/Components/shared/StatusBadge"; import { Checkbox } from "@/Components/ui/checkbox"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/Components/ui/alert-dialog"; import { Label } from "@/Components/ui/label"; import { Save, CheckCircle, Trash2, ArrowLeft, Plus, ClipboardCheck, Package, Search } from "lucide-react"; import { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/Components/ui/dialog"; import axios from 'axios'; import { Can } from '@/Components/Permission/Can'; import { toast } from 'sonner'; interface AdjItem { id?: string; product_id: string; product_name: string; product_code: string; batch_number: string | null; unit: string; qty_before: number | string; adjust_qty: number | string; notes: string; expiry_date?: string | null; } interface AdjDoc { id: string; doc_no: string; warehouse_id: string; warehouse_name: string; status: string; reason: string; remarks: string; created_at: string; created_by: string; count_doc_id?: string; count_doc_no?: string; items: AdjItem[]; } export default function Show({ doc }: { auth: any, doc: AdjDoc }) { const { can } = usePermission(); const isDraft = doc.status === 'draft'; const canEdit = can('inventory_adjust.edit'); const isReadOnly = !isDraft || !canEdit; // Main Form using Inertia useForm const { data, setData, put, delete: destroy, processing } = useForm({ reason: doc.reason, remarks: doc.remarks || '', items: doc.items || [], action: 'save', }); // Product Selection State const [isProductDialogOpen, setIsProductDialogOpen] = useState(false); const [availableInventory, setAvailableInventory] = useState([]); const [loadingInventory, setLoadingInventory] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedInventory, setSelectedInventory] = useState([]); // product_id-batch const [isPostDialogOpen, setIsPostDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); useEffect(() => { if (isProductDialogOpen) { loadInventory(); setSelectedInventory([]); // Reset selection when opening setSearchQuery(''); // Reset search when opening } }, [isProductDialogOpen]); const loadInventory = async () => { setLoadingInventory(true); try { const response = await axios.get(route('api.warehouses.inventories', doc.warehouse_id)); setAvailableInventory(response.data); } catch (error) { console.error("Failed to load inventory", error); toast.error("無法載入庫存資料"); } finally { setLoadingInventory(false); } }; const toggleSelect = (key: string) => { setSelectedInventory(prev => prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key] ); }; const toggleSelectAll = () => { const filtered = availableInventory.filter(inv => inv.product_name.toLowerCase().includes(searchQuery.toLowerCase()) || inv.product_code.toLowerCase().includes(searchQuery.toLowerCase()) ); const filteredKeys = filtered.map(inv => `${inv.product_id}-${inv.batch_number}`); if (filteredKeys.length > 0 && filteredKeys.every(k => selectedInventory.includes(k))) { setSelectedInventory(prev => prev.filter(k => !filteredKeys.includes(k))); } else { setSelectedInventory(prev => Array.from(new Set([...prev, ...filteredKeys]))); } }; // Helper to add selected items to the main list const handleAddSelected = () => { if (selectedInventory.length === 0) return; const newItems = [...data.items]; let addedCount = 0; availableInventory.forEach(inv => { const key = `${inv.product_id}-${inv.batch_number}`; if (selectedInventory.includes(key)) { // Check if already exists const exists = newItems.find((i: any) => i.product_id === String(inv.product_id) && i.batch_number === inv.batch_number ); if (!exists) { newItems.push({ product_id: String(inv.product_id), product_name: inv.product_name, product_code: inv.product_code, unit: inv.unit_name, batch_number: inv.batch_number, qty_before: inv.quantity || 0, adjust_qty: 0, notes: '', expiry_date: inv.expiry_date, }); addedCount++; } } }); setData('items', newItems); setIsProductDialogOpen(false); if (addedCount > 0) { toast.success(`已成功加入 ${addedCount} 個項目`); } else { toast.info("選取的商品已在清單中"); } }; const removeItem = (index: number) => { const newItems = [...data.items]; newItems.splice(index, 1); setData('items', newItems); }; const updateItem = (index: number, field: keyof AdjItem, value: any) => { const newItems = [...data.items]; (newItems[index] as any)[field] = value; setData('items', newItems); }; const handleSave = () => { setData('action', 'save'); put(route('inventory.adjust.update', [doc.id]), { preserveScroll: true, }); }; const handlePost = () => { if (data.items.length === 0) { toast.error('請至少加入一個調整項目'); return; } router.put(route('inventory.adjust.update', [doc.id]), { ...data, action: 'post' } as any, { onSuccess: () => { setIsPostDialogOpen(false); } }); }; const handleDelete = () => { destroy(route('inventory.adjust.destroy', [doc.id])); }; return (

盤調單: {doc.doc_no}

{isDraft ? ( 草稿 ) : ( 已過帳 )}

倉庫: {doc.warehouse_name} | 建立者: {doc.created_by} | 時間: {doc.created_at} {doc.count_doc_id && ( <> | 來源盤點單: {doc.count_doc_no} )}

{!isReadOnly && ( <> 確定要刪除此盤調單嗎? 此動作將會永久移除本張草稿,且無法復原。 取消 確認刪除 確定要過帳嗎? 過帳後庫存將立即根據調整數量進行增減,且無法再修改此盤調單。 取消 確認過帳 )}
{/* Header Fields - Inline */}
{!isReadOnly ? ( setData('reason', e.target.value)} className="focus:ring-primary-main h-9" placeholder="請輸入調整原因..." /> ) : (
{data.reason}
)}
{!isReadOnly ? ( setData('remarks', e.target.value)} className="focus:ring-primary-main h-9" placeholder="選填備註..." /> ) : (
{data.remarks || '-'}
)}

調整項目

請輸入各項商品的實盤與帳面之差異數量。正數為增加,負數為減少。

{!isReadOnly && !doc.count_doc_id && ( 選擇倉庫商品 ({doc.warehouse_name})
setSearchQuery(e.target.value)} />
{loadingInventory ? (

庫存資料載入中...

) : (
0 && selectedInventory.length === availableInventory.length} onCheckedChange={() => toggleSelectAll()} /> 品名 批號 效期 現有庫存 {(() => { const filtered = availableInventory.filter(inv => inv.product_name.toLowerCase().includes(searchQuery.toLowerCase()) || inv.product_code.toLowerCase().includes(searchQuery.toLowerCase()) ); if (filtered.length === 0) { return ( {searchQuery ? `找不到與 "${searchQuery}" 相關的商品` : '尚無庫存資料'} ); } return filtered.map((inv) => { const key = `${inv.product_id}-${inv.batch_number}`; const isSelected = selectedInventory.includes(key); return ( toggleSelect(key)} > e.stopPropagation()}> toggleSelect(key)} />
{inv.product_name} {inv.product_code}
{inv.batch_number || '-'} {inv.expiry_date || '-'} {inv.quantity} {inv.unit_name}
); }); })()}
)}
已選取 {selectedInventory.length} 項商品
{selectedInventory.length > 0 && ( )}
)}
# 商品名稱 / 代號 批號 單位 調整前庫存 調整數量 (+/-) 備註 {!isReadOnly && } {data.items.length === 0 ? ( 尚未加入任何項目 ) : ( data.items.map((item, index) => ( {index + 1}
{item.product_name} {item.product_code}
{item.batch_number || '-'}
{item.expiry_date && (
效期: {item.expiry_date}
)}
{item.unit} {item.qty_before} {!isReadOnly ? (
updateItem(index, 'adjust_qty', e.target.value)} />
) : ( 0 ? 'text-green-600' : Number(item.adjust_qty) < 0 ? 'text-red-600' : 'text-gray-600'}`}> {Number(item.adjust_qty) > 0 ? '+' : ''}{item.adjust_qty} )}
{!isReadOnly ? ( updateItem(index, 'notes', e.target.value)} placeholder="備註..." /> ) : ( {item.notes || '-'} )} {!isReadOnly && !doc.count_doc_id && ( )}
)) )}
); }