import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, useForm, router, Link } 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 { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/Components/ui/dialog"; import { Label } from "@/Components/ui/label"; import { Plus, Search, X, Eye, Pencil, ClipboardCheck, Trash2 } from "lucide-react"; import { useState, useCallback, useEffect } from 'react'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/Components/ui/alert-dialog"; import Pagination from '@/Components/shared/Pagination'; import { SearchableSelect } from '@/Components/ui/searchable-select'; import { Can } from '@/Components/Permission/Can'; import debounce from 'lodash/debounce'; import axios from 'axios'; interface Doc { id: string; doc_no: string; warehouse_name: string; reason: string; status: string; created_by: string; created_at: string; posted_at: string; } interface Warehouse { id: string; name: string; } interface Filters { search?: string; warehouse_id?: string; per_page?: string; } interface DocsPagination { data: Doc[]; current_page: number; per_page: number; total: number; links: any[]; // Adjust type as needed for pagination links } export default function Index({ docs, warehouses, filters }: { docs: DocsPagination, warehouses: Warehouse[], filters: Filters }) { const [isDialogOpen, setIsDialogOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(filters.search || ''); const [warehouseId, setWarehouseId] = useState(filters.warehouse_id || ''); const [perPage, setPerPage] = useState(filters.per_page || '15'); const [deleteId, setDeleteId] = useState(null); // For Count Doc Selection const [pendingCounts, setPendingCounts] = useState([]); const [loadingPending, setLoadingPending] = useState(false); const [scanSearch, setScanSearch] = useState(''); const fetchPendingCounts = useCallback( debounce((search = '') => { setLoadingPending(true); axios.get(route('inventory.adjust.pending-counts'), { params: { search } }) .then(res => setPendingCounts(res.data)) .finally(() => setLoadingPending(false)); }, 300), [] ); useEffect(() => { if (isDialogOpen) { fetchPendingCounts(); } }, [isDialogOpen, fetchPendingCounts]); const debouncedFilter = useCallback( debounce((params: Filters) => { router.get(route('inventory.adjust.index'), params as any, { preserveState: true, replace: true, }); }, 300), [] ); const handleSearchChange = (val: string) => { setSearchQuery(val); debouncedFilter({ search: val, warehouse_id: warehouseId, per_page: perPage }); }; const handleWarehouseChange = (val: string) => { setWarehouseId(val); debouncedFilter({ search: searchQuery, warehouse_id: val, per_page: perPage }); }; const handlePerPageChange = (val: string) => { setPerPage(val); debouncedFilter({ search: searchQuery, warehouse_id: warehouseId, per_page: val }); }; const confirmDelete = (id: string, e: React.MouseEvent) => { e.stopPropagation(); setDeleteId(id); }; const handleDelete = () => { if (deleteId) { router.delete(route('inventory.adjust.destroy', [deleteId]), { onSuccess: () => setDeleteId(null), onError: () => setDeleteId(null), }); } }; const { data, setData, post, processing, reset } = useForm({ count_doc_id: null as string | null, warehouse_id: '', reason: '手動調整庫存', remarks: '', }); const handleCreate = (countDocId?: string) => { if (countDocId) { setData('count_doc_id', countDocId); router.post(route('inventory.adjust.store'), { count_doc_id: countDocId }, { onSuccess: () => setIsDialogOpen(false), }); return; } post(route('inventory.adjust.store'), { onSuccess: () => { setIsDialogOpen(false); reset(); }, }); }; const getStatusBadge = (status: string) => { switch (status) { case 'draft': return 草稿; case 'posted': return 已過帳; case 'voided': return 已作廢; default: return {status}; } }; return (

庫存盤調管理

針對盤點差異進行庫存調整與過帳 (盤盈、盤虧、報廢等)。

{/* Toolbar Context */}
{/* Search */}
handleSearchChange(e.target.value)} className="pl-10 pr-10 h-9" /> {searchQuery && ( )}
{/* Warehouse Filter */} ({ value: w.id, label: w.name })) ]} value={warehouseId} onValueChange={handleWarehouseChange} placeholder="選擇倉庫" className="w-full md:w-[200px] h-9" /> {/* Action Buttons */}
{/* Table Container */}
# 單號 倉庫 調整原因 狀態 建立者 建立時間 過帳時間 操作 {docs.data.length === 0 ? ( 尚無任何盤調單據 ) : ( docs.data.map((doc: Doc, index: number) => ( router.visit(route('inventory.adjust.show', [doc.id]))} > {(docs.current_page - 1) * docs.per_page + index + 1} {doc.doc_no} {doc.warehouse_name} {doc.reason} {getStatusBadge(doc.status)} {doc.created_by} {doc.created_at} {doc.posted_at || '-'}
e.stopPropagation()}> {doc.status === 'draft' && ( )}
)) )}
每頁顯示
共 {docs?.total || 0} 筆紀錄
!open && setDeleteId(null)}> 確定要刪除此盤調單嗎? 此動作無法復原。刪除後請重新建立盤調單。 取消 確認刪除
{/* Create Dialog */} 新增盤調單 請選擇一個已完成的盤點單來生成盤調項目,或手動建立。
{/* Option 1: Scan/Select from Count Docs */}
{ setScanSearch(e.target.value); fetchPendingCounts(e.target.value); }} />
{loadingPending ? (
載入中...
) : pendingCounts.length === 0 ? (
查無可供盤調的盤點單 (需為已核准狀態)
) : (
{pendingCounts.map((c: any) => (
handleCreate(c.id)} >

{c.doc_no}

{c.warehouse_name} | 完成於: {c.completed_at}

))}
)}
{/* Option 2: Manual (Optional, though less common in this flow) */}
({ value: w.id, label: w.name }))} value={data.warehouse_id} onValueChange={(val) => setData('warehouse_id', val)} placeholder="選擇倉庫" className="h-9" />
setData('reason', e.target.value)} className="h-9" />
); }