-
-
-
-
-
-
- setSearchQuery(e.target.value)}
- />
-
-
+
+
+
+
+
+ 庫存調撥管理
+
+
+ 建立與管理倉庫間的商品調撥單,追蹤庫存轉移紀錄。
+
+
+
+ {/* Toolbar */}
+
+
+ {/* Search */}
+
+
+ handleSearchChange(e.target.value)}
+ className="pl-10 pr-10 h-9"
+ />
+ {searchTerm && (
+
+ )}
+
+
+ {/* Warehouse Filter */}
+
({ label: w.name, value: w.id.toString() }))
+ ]}
+ placeholder="選擇倉庫"
+ className="w-full md:w-[200px] h-9"
+ />
+
+ {/* Action Buttons */}
+
+
+
+
+
+
+
+ #
+ 單號
+ 狀態
+ 來源倉庫
+ 目的倉庫
+ 建立日期
+ 過帳日期
+ 建立人員
+ 操作
+
+
+
+ {orders.data.length === 0 ? (
+
+
+ 尚無調撥紀錄
+
+
+ ) : (
+ orders.data.map((order: any, index: number) => (
+ router.visit(route('inventory.transfer.show', [order.id]))}
+ >
+
+ {(orders.current_page - 1) * orders.per_page + index + 1}
+
+ {order.doc_no}
+ {getStatusBadge(order.status)}
+ {order.from_warehouse_name}
+ {order.to_warehouse_name}
+ {order.created_at}
+ {order.posted_at || '-'}
+ {order.created_by}
+
+ e.stopPropagation()}>
+
+
+
+ {['completed', 'voided'].includes(order.status) ? (
+
+ ) : (
+
+ )}
+
+
+ {order.status === 'draft' && (
+ confirmDelete(order.id)}
+ >
+
+
+ )}
+
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+ 每頁顯示
+
+ 筆
+
+
共 {orders.total} 筆紀錄
+
+
+
+
+
!open && setDeleteId(null)}>
+
+
+ 確定要刪除此調撥單嗎?
+
+ 此動作無法復原。如果單據已存在重要資料,請謹慎操作。
+
+
+
+ 取消
+ 確認刪除
+
+
+
);
diff --git a/resources/js/Pages/Inventory/Transfer/Show.tsx b/resources/js/Pages/Inventory/Transfer/Show.tsx
index 745bf65..7f83559 100644
--- a/resources/js/Pages/Inventory/Transfer/Show.tsx
+++ b/resources/js/Pages/Inventory/Transfer/Show.tsx
@@ -1,11 +1,10 @@
import { useState, useEffect } from "react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
-import { Head, router, usePage, Link } from "@inertiajs/react";
+import { Head, router, Link } from "@inertiajs/react";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
-import { Textarea } from "@/Components/ui/textarea";
import {
Table,
TableBody,
@@ -15,38 +14,49 @@ import {
TableRow,
} from "@/Components/ui/table";
import { Badge } from "@/Components/ui/badge";
+import { Checkbox } from "@/Components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
- DialogFooter,
} from "@/Components/ui/dialog";
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/Components/ui/select";
-import { Plus, Save, Trash2, ArrowLeft, CheckCircle, Ban, History, Package } from "lucide-react";
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/Components/ui/alert-dialog";
+import { Plus, Save, Trash2, ArrowLeft, CheckCircle, Package, ArrowLeftRight, Printer, Search } from "lucide-react";
import { toast } from "sonner";
import axios from "axios";
+import { Can } from '@/Components/Permission/Can';
-export default function Show({ auth, order }) {
+export default function Show({ order }: any) {
const [items, setItems] = useState(order.items || []);
const [remarks, setRemarks] = useState(order.remarks || "");
const [isSaving, setIsSaving] = useState(false);
+ const [deleteId, setDeleteId] = useState
(null);
+ const [isPostDialogOpen, setIsPostDialogOpen] = useState(false);
// Product Selection
const [isProductDialogOpen, setIsProductDialogOpen] = useState(false);
- const [availableInventory, setAvailableInventory] = useState([]);
+ const [availableInventory, setAvailableInventory] = useState([]);
const [loadingInventory, setLoadingInventory] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [selectedInventory, setSelectedInventory] = useState([]); // product_id-batch
useEffect(() => {
if (isProductDialogOpen) {
loadInventory();
+ setSelectedInventory([]);
+ setSearchQuery('');
}
}, [isProductDialogOpen]);
@@ -64,39 +74,75 @@ export default function Show({ auth, order }) {
}
};
- const handleAddItem = (inventoryItem) => {
- // Check if already added
- const exists = items.find(i =>
- i.product_id === inventoryItem.product_id &&
- i.batch_number === inventoryItem.batch_number
+ const toggleSelect = (key: string) => {
+ setSelectedInventory(prev =>
+ prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key]
);
-
- if (exists) {
- toast.error("該商品與批號已在列表中");
- return;
- }
-
- setItems([...items, {
- product_id: inventoryItem.product_id,
- product_name: inventoryItem.product_name,
- product_code: inventoryItem.product_code,
- batch_number: inventoryItem.batch_number,
- unit: inventoryItem.unit_name,
- quantity: 1, // Default 1
- max_quantity: inventoryItem.quantity, // Max available
- notes: "",
- }]);
- setIsProductDialogOpen(false);
};
- const handleUpdateItem = (index, field, value) => {
+ 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])));
+ }
+ };
+
+ const handleAddSelected = () => {
+ if (selectedInventory.length === 0) return;
+
+ const newItems = [...items];
+ let addedCount = 0;
+
+ availableInventory.forEach(inv => {
+ const key = `${inv.product_id}-${inv.batch_number}`;
+ if (selectedInventory.includes(key)) {
+ // Check if already added
+ const exists = newItems.find((i: any) =>
+ i.product_id === inv.product_id &&
+ i.batch_number === inv.batch_number
+ );
+
+ if (!exists) {
+ newItems.push({
+ product_id: inv.product_id,
+ product_name: inv.product_name,
+ product_code: inv.product_code,
+ batch_number: inv.batch_number,
+ unit: inv.unit_name,
+ quantity: 1, // Default 1
+ max_quantity: inv.quantity, // Max available
+ notes: "",
+ });
+ addedCount++;
+ }
+ }
+ });
+
+ setItems(newItems);
+ setIsProductDialogOpen(false);
+
+ if (addedCount > 0) {
+ toast.success(`已成功加入 ${addedCount} 個項目`);
+ } else {
+ toast.info("選取的商品已在清單中");
+ }
+ };
+
+ const handleUpdateItem = (index: number, field: string, value: any) => {
const newItems = [...items];
newItems[index][field] = value;
setItems(newItems);
};
- const handleRemoveItem = (index) => {
- const newItems = items.filter((_, i) => i !== index);
+ const handleRemoveItem = (index: number) => {
+ const newItems = items.filter((_: any, i: number) => i !== index);
setItems(newItems);
};
@@ -116,32 +162,39 @@ export default function Show({ auth, order }) {
};
const handlePost = () => {
- if (!confirm("確定要過帳嗎?過帳後庫存將立即轉移且無法修改。")) return;
router.put(route('inventory.transfer.update', [order.id]), {
action: 'post'
+ }, {
+ onSuccess: () => {
+ setIsPostDialogOpen(false);
+ toast.success("過帳成功");
+ }
});
};
const handleDelete = () => {
- if (!confirm("確定要刪除此草稿嗎?")) return;
- router.delete(route('inventory.transfer.destroy', [order.id]));
+ router.delete(route('inventory.transfer.destroy', [order.id]), {
+ onSuccess: () => {
+ setDeleteId(null);
+ toast.success("已成功刪除");
+ }
+ });
};
const isReadOnly = order.status !== 'draft';
return (
-
-
+
+
-
-
- 調撥單詳情 ({order.doc_no})
-
-
+
+
+
+
+
+ 調撥單: {order.doc_no}
+
+ {order.status === 'completed' &&
已完成}
+ {order.status === 'draft' &&
草稿}
+ {order.status === 'voided' &&
已作廢}
+
+
+ 來源: {order.from_warehouse_name} 目的: {order.to_warehouse_name} | 建立人: {order.created_by}
+
+
+
+
+
window.print()}
+ >
+
+ 列印
+
+
{!isReadOnly && (
- <>
-
-
- 刪除
-
-
-
- 儲存草稿
-
-
-
- 確認過帳
-
- >
+
+
+ !open && setDeleteId(null)}>
+
+ setDeleteId(order.id)}>
+
+ 刪除
+
+
+
+
+ 確定要刪除此調撥單嗎?
+
+ 此動作無法復原。如果單據已存在重要資料,請謹慎操作。
+
+
+
+ 取消
+ 確認刪除
+
+
+
+
+
+
+ 儲存草稿
+
+
+
+
+
+
+ 確認過帳
+
+
+
+
+ 確定要過帳嗎?
+
+ 過帳後庫存將立即從「{order.from_warehouse_name}」轉移至「{order.to_warehouse_name}」,且無法再進行修改。
+
+
+
+ 取消
+ 確認過帳
+
+
+
+
+
)}
-
- {/* Header Info */}
-
-
-
-
{order.from_warehouse_name}
+
+
+
+
+ {isReadOnly ? (
+
+ {order.remarks || '無備註'}
+ ) : (
+
setRemarks(e.target.value)}
+ className="h-9 focus:ring-primary-main"
+ placeholder="填寫調撥單備註..."
+ />
+ )}
+
+
+
+
-
-
{order.to_warehouse_name}
-
-
-
-
- {order.status === 'draft' && 草稿}
- {order.status === 'completed' && 已完成}
- {order.status === 'voided' && 已作廢}
-
-
-
-
- {isReadOnly ? (
-
{order.remarks || '-'}
- ) : (
-
setRemarks(e.target.value)}
- className="mt-1"
- placeholder="填寫備註..."
- />
- )}
+
調撥明細
+
+ 請選擇要調撥的商品並輸入數量。所有商品將從「{order.from_warehouse_name}」轉出。
+
+ {!isReadOnly && (
+
+ )}
- {/* Items */}
-
-
-
-
調撥明細
- {!isReadOnly && (
-