import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, Link, useForm, router } from '@inertiajs/react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/Components/ui/table"; import { Button } from "@/Components/ui/button"; import { Input } from "@/Components/ui/input"; import { Badge } from "@/Components/ui/badge"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/Components/ui/alert-dialog"; import { Label } from "@/Components/ui/label"; import { Textarea } from "@/Components/ui/textarea"; import { Save, CheckCircle, Trash2, ArrowLeft, Plus, X, Search, FileText, ClipboardCheck } from "lucide-react"; import { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/Components/ui/dialog"; import axios from 'axios'; import { Can } from '@/Components/Permission/Can'; interface AdjItem { id?: string; product_id: string; product_name: string; product_code: string; batch_number: string | null; unit: string; qty_before: number; adjust_qty: number | string; notes: string; } 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 isDraft = doc.status === 'draft'; // Main Form using Inertia useForm const { data, setData, put, delete: destroy, processing } = useForm({ reason: doc.reason, remarks: doc.remarks || '', items: doc.items || [], action: 'save', }); // Helper to add new item const addItem = (product: any, batchNumber: string | null) => { // Check if exists const exists = data.items.find(i => i.product_id === String(product.id) && i.batch_number === batchNumber ); if (exists) { alert('此商品與批號已在列表中'); return; } setData('items', [ ...data.items, { product_id: String(product.id), product_name: product.name, product_code: product.code, unit: product.unit, batch_number: batchNumber, qty_before: product.qty || 0, // Not fetched dynamically for now, or could fetch via API adjust_qty: 0, notes: '', } ]); }; 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 = () => { // Validate if (data.items.length === 0) { alert('請至少加入一個調整項目'); return; } const hasZero = data.items.some(i => Number(i.adjust_qty) === 0); if (hasZero && !confirm('部分項目的調整數量為 0,確定要繼續嗎?')) { return; } if (confirm('確定要過帳嗎?過帳後將無法修改,並直接影響庫存。')) { router.visit(route('inventory.adjust.update', [doc.id]), { method: 'put', data: { ...data, action: 'post' } as any, }); } }; 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} )}

{isDraft && ( 確定要刪除此盤調單嗎? 此動作將會永久移除本張草稿,且無法復原。 取消 確認刪除 )}
{/* Header Fields - Inline */}
{isDraft ? ( setData('reason', e.target.value)} className="focus:ring-primary-main h-9" placeholder="請輸入調整原因..." /> ) : (
{data.reason}
)}
{isDraft ? ( setData('remarks', e.target.value)} className="focus:ring-primary-main h-9" placeholder="選填備註..." /> ) : (
{data.remarks || '-'}
)}

調整項目

{isDraft && ( addItem(product, batch)} /> )}
# 商品資訊 批號 單位 調整前庫存 調整數量 (+/-) 備註 {isDraft && } {data.items.length === 0 ? ( 尚未加入任何項目 ) : ( data.items.map((item, index) => ( {index + 1}
{item.product_name}
{item.product_code}
{item.batch_number || '-'} {item.unit} {item.qty_before} {isDraft ? ( updateItem(index, 'adjust_qty', e.target.value)} /> ) : ( 0 ? 'text-green-600' : 'text-red-600'}`}> {Number(item.adjust_qty) > 0 ? '+' : ''}{item.adjust_qty} )} {isDraft ? ( updateItem(index, 'notes', e.target.value)} placeholder="備註..." /> ) : ( {item.notes || '-'} )} {isDraft && ( )}
)) )}
); } // Simple internal component for product search function ProductSearchDialog({ onSelect }: { warehouseId: string, onSelect: (p: any, b: string | null) => void }) { const [search, setSearch] = useState(''); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); // Debounce search useEffect(() => { if (!search) { setResults([]); return; } const timer = setTimeout(() => { fetchProducts(); }, 500); return () => clearTimeout(timer); }, [search]); const fetchProducts = async () => { setLoading(true); try { // Using existing API logic from Goods Receipts or creating a flexible one // Using count docs logic for now if specific endpoint not available, // but `goods-receipts.search-products` is a good bet for general product search. const res = await axios.get(route('goods-receipts.search-products'), { params: { query: search } }); setResults(res.data); } catch (e) { console.error(e); } finally { setLoading(false); } }; return ( 搜尋並加入商品
setSearch(e.target.value)} autoFocus />
{loading ? (
搜尋中...
) : results.length === 0 ? (

請輸入商品關鍵字開始搜尋

) : (
{results.map(product => (
{ onSelect(product, null); setOpen(false); setSearch(''); setResults([]); }} >
{product.name}
{product.code}
{product.unit || '單位'} 點擊加入
))}
)}
) }