From bd999c7bb63ecbb655ed37c5e40e8d7c89f65e23 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Tue, 3 Feb 2026 17:24:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=B5=B1=E4=B8=80=E5=BA=AB=E5=AD=98?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=88=86=E9=A0=81=20UI=20=E8=88=87=E5=AF=AC?= =?UTF-8?q?=E5=BA=A6=E8=A6=8F=E7=AF=84=EF=BC=8C=E4=B8=A6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20SKILL=20=E8=A6=8F=E7=AF=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agent/skills/ui-consistency/SKILL.md | 36 +++-- .../Core/Controllers/RoleController.php | 16 +- .../Controllers/AdjustDocController.php | 2 +- .../Controllers/CountDocController.php | 2 +- .../Controllers/TransferOrderController.php | 6 +- .../Controllers/WarehouseController.php | 9 +- app/Modules/Inventory/Routes/web.php | 49 +++--- database/seeders/PermissionSeeder.php | 31 +++- resources/js/Layouts/AuthenticatedLayout.tsx | 6 +- resources/js/Pages/Admin/Role/Edit.tsx | 22 ++- resources/js/Pages/Inventory/Adjust/Index.tsx | 93 +++++++---- resources/js/Pages/Inventory/Adjust/Show.tsx | 144 +++++++++--------- resources/js/Pages/Inventory/Count/Index.tsx | 69 ++++++--- resources/js/Pages/Inventory/Count/Show.tsx | 14 +- .../js/Pages/Inventory/Transfer/Index.tsx | 26 ++-- resources/js/Pages/Product/Index.tsx | 2 +- resources/js/Pages/Warehouse/Index.tsx | 35 ++++- 17 files changed, 357 insertions(+), 205 deletions(-) diff --git a/.agent/skills/ui-consistency/SKILL.md b/.agent/skills/ui-consistency/SKILL.md index e5ef3bd..e2f6f79 100644 --- a/.agent/skills/ui-consistency/SKILL.md +++ b/.agent/skills/ui-consistency/SKILL.md @@ -467,23 +467,27 @@ const handleSort = (field: string) => { import Pagination from "@/Components/shared/Pagination"; import { SearchableSelect } from "@/Components/ui/searchable-select"; -// 在表格下方 +// 在表格下方(底部工具列)
-
- 每頁顯示 - - +
+
+ 每頁顯示 + + +
+ {/* 總筆數顯示:統一放在每頁顯示右側,使用 text-gray-500 */} + 共 {data.total} 筆紀錄
diff --git a/app/Modules/Core/Controllers/RoleController.php b/app/Modules/Core/Controllers/RoleController.php index f54d214..1d53d32 100644 --- a/app/Modules/Core/Controllers/RoleController.php +++ b/app/Modules/Core/Controllers/RoleController.php @@ -123,7 +123,7 @@ class RoleController extends Controller $role->syncPermissions($validated['permissions']); } - return redirect()->route('roles.index')->with('success', '角色更新成功'); + return back()->with('success', '角色更新成功'); } /** @@ -160,8 +160,13 @@ class RoleController extends Controller $action = $parts[1] ?? ''; // 特定權限遷移邏輯 - if ($permission->name === 'inventory.transfer') { - $group = 'warehouses'; // 調撥功能移至倉庫管理下 + if ($permission->name === 'inventory.view_cost') { + $group = 'inventory'; + } + + // 移除不再使用的權限選項 + if (in_array($permission->name, ['inventory.count', 'inventory.transfer'])) { + continue; } if (!isset($grouped[$group])) { @@ -175,7 +180,10 @@ class RoleController extends Controller $groupDefinitions = [ 'products' => '商品資料管理', 'warehouses' => '倉庫管理', - 'inventory' => '庫存管理', + 'inventory' => '庫存資料管理', + 'inventory_count' => '庫存盤點管理', + 'inventory_adjust' => '庫存盤調管理', + 'inventory_transfer' => '庫存調撥管理', 'vendors' => '廠商資料管理', 'purchase_orders' => '採購單管理', 'goods_receipts' => '進貨單管理', diff --git a/app/Modules/Inventory/Controllers/AdjustDocController.php b/app/Modules/Inventory/Controllers/AdjustDocController.php index be8ddf4..2c5db51 100644 --- a/app/Modules/Inventory/Controllers/AdjustDocController.php +++ b/app/Modules/Inventory/Controllers/AdjustDocController.php @@ -39,7 +39,7 @@ class AdjustDocController extends Controller $query->where('warehouse_id', $request->warehouse_id); } - $perPage = $request->input('per_page', 15); + $perPage = $request->input('per_page', 10); $docs = $query->orderByDesc('created_at') ->paginate($perPage) ->withQueryString() diff --git a/app/Modules/Inventory/Controllers/CountDocController.php b/app/Modules/Inventory/Controllers/CountDocController.php index f00fc27..e2768dc 100644 --- a/app/Modules/Inventory/Controllers/CountDocController.php +++ b/app/Modules/Inventory/Controllers/CountDocController.php @@ -37,7 +37,7 @@ class CountDocController extends Controller $perPage = $request->input('per_page', 10); if (!in_array($perPage, [10, 20, 50, 100])) { - $perPage = 15; + $perPage = 10; } $countQuery = function ($query) { diff --git a/app/Modules/Inventory/Controllers/TransferOrderController.php b/app/Modules/Inventory/Controllers/TransferOrderController.php index 32e0dc3..690efc1 100644 --- a/app/Modules/Inventory/Controllers/TransferOrderController.php +++ b/app/Modules/Inventory/Controllers/TransferOrderController.php @@ -32,8 +32,10 @@ class TransferOrderController extends Controller }); } + $perPage = $request->input('per_page', 10); $orders = $query->orderByDesc('created_at') - ->paginate(15) + ->paginate($perPage) + ->withQueryString() ->through(function ($order) { return [ 'id' => (string) $order->id, @@ -50,7 +52,7 @@ class TransferOrderController extends Controller return Inertia::render('Inventory/Transfer/Index', [ 'orders' => $orders, 'warehouses' => Warehouse::all()->map(fn($w) => ['id' => (string)$w->id, 'name' => $w->name]), - 'filters' => $request->only(['warehouse_id']), + 'filters' => $request->only(['warehouse_id', 'per_page']), ]); } diff --git a/app/Modules/Inventory/Controllers/WarehouseController.php b/app/Modules/Inventory/Controllers/WarehouseController.php index a1391b5..658cf2a 100644 --- a/app/Modules/Inventory/Controllers/WarehouseController.php +++ b/app/Modules/Inventory/Controllers/WarehouseController.php @@ -24,6 +24,11 @@ class WarehouseController extends Controller }); } + $perPage = $request->input('per_page', 10); + if (!in_array($perPage, [10, 20, 50, 100])) { + $perPage = 10; + } + $warehouses = $query->withSum('inventories as book_stock', 'quantity') // 帳面庫存 = 所有庫存總和 ->withSum(['inventories as available_stock' => function ($query) { // 可用庫存 = 庫存 > 0 且 品質正常 且 (未過期 或 無效期) 且 倉庫類型不為瑕疵倉 @@ -44,7 +49,7 @@ class WarehouseController extends Controller ->whereRaw('(SELECT COALESCE(SUM(quantity), 0) FROM inventories WHERE warehouse_id = ss.warehouse_id AND product_id = ss.product_id) < ss.safety_stock'); }]) ->orderBy('created_at', 'desc') - ->paginate(10) + ->paginate($perPage) ->withQueryString(); // 移除原本對 is_sellable 的手動修正邏輯,現在由 type 自動過濾 @@ -67,7 +72,7 @@ class WarehouseController extends Controller return Inertia::render('Warehouse/Index', [ 'warehouses' => $warehouses, 'totals' => $totals, - 'filters' => $request->only(['search']), + 'filters' => $request->only(['search', 'per_page']), ]); } diff --git a/app/Modules/Inventory/Routes/web.php b/app/Modules/Inventory/Routes/web.php index ec5b579..27d7ec0 100644 --- a/app/Modules/Inventory/Routes/web.php +++ b/app/Modules/Inventory/Routes/web.php @@ -22,10 +22,10 @@ Route::middleware('auth')->group(function () { }); // 單位管理 - 需要商品權限 - Route::middleware('permission:products.create|products.edit')->group(function () { - Route::post('/units', [UnitController::class, 'store'])->name('units.store'); - Route::put('/units/{unit}', [UnitController::class, 'update'])->name('units.update'); - Route::delete('/units/{unit}', [UnitController::class, 'destroy'])->name('units.destroy'); + Route::middleware('permission:products.view')->group(function () { + Route::post('/units', [UnitController::class, 'store'])->middleware('permission:products.create')->name('units.store'); + Route::put('/units/{unit}', [UnitController::class, 'update'])->middleware('permission:products.edit')->name('units.update'); + Route::delete('/units/{unit}', [UnitController::class, 'destroy'])->middleware('permission:products.delete')->name('units.destroy'); }); // 商品管理 @@ -75,37 +75,34 @@ Route::middleware('auth')->group(function () { }); // 庫存盤點 (Stock Counting) - Global - Route::middleware('permission:inventory.view')->group(function () { - Route::get('/inventory/count-docs', [CountDocController::class, 'index'])->name('inventory.count.index'); - Route::get('/inventory/count-docs/{doc}', [CountDocController::class, 'show'])->name('inventory.count.show'); - - Route::middleware('permission:inventory.adjust')->group(function () { - Route::post('/inventory/count-docs', [CountDocController::class, 'store'])->name('inventory.count.store'); - Route::put('/inventory/count-docs/{doc}', [CountDocController::class, 'update'])->name('inventory.count.update'); - Route::delete('/inventory/count-docs/{doc}', [CountDocController::class, 'destroy'])->name('inventory.count.destroy'); - Route::put('/inventory/count-docs/{doc}/reopen', [CountDocController::class, 'reopen'])->name('inventory.count.reopen'); - }); - Route::get('/inventory/count-docs/{doc}/print', [CountDocController::class, 'print'])->name('inventory.count.print'); + Route::middleware('permission:inventory_count.view')->group(function () { + Route::get('/inventory/count-docs', [CountDocController::class, 'index'])->name('inventory.count.index'); + Route::get('/inventory/count-docs/{doc}', [CountDocController::class, 'show'])->name('inventory.count.show'); + Route::get('/inventory/count-docs/{doc}/print', [CountDocController::class, 'print'])->name('inventory.count.print'); }); + Route::post('/inventory/count-docs', [CountDocController::class, 'store'])->middleware('permission:inventory_count.create')->name('inventory.count.store'); + Route::put('/inventory/count-docs/{doc}', [CountDocController::class, 'update'])->middleware('permission:inventory_count.edit')->name('inventory.count.update'); + Route::delete('/inventory/count-docs/{doc}', [CountDocController::class, 'destroy'])->middleware('permission:inventory_count.delete')->name('inventory.count.destroy'); + Route::put('/inventory/count-docs/{doc}/reopen', [CountDocController::class, 'reopen'])->middleware('permission:inventory_count.edit')->name('inventory.count.reopen'); // 庫存盤調 (Stock Adjustment) - Global - Route::middleware('permission:inventory.adjust')->group(function () { - Route::get('/inventory/adjust-docs', [AdjustDocController::class, 'index'])->name('inventory.adjust.index'); - Route::get('/inventory/adjust-docs/get-pending-counts', [AdjustDocController::class, 'getPendingCounts'])->name('inventory.adjust.pending-counts'); - Route::get('/inventory/adjust-docs/{doc}', [AdjustDocController::class, 'show'])->name('inventory.adjust.show'); - Route::post('/inventory/adjust-docs', [AdjustDocController::class, 'store'])->name('inventory.adjust.store'); - Route::put('/inventory/adjust-docs/{doc}', [AdjustDocController::class, 'update'])->name('inventory.adjust.update'); - Route::delete('/inventory/adjust-docs/{doc}', [AdjustDocController::class, 'destroy'])->name('inventory.adjust.destroy'); + Route::middleware('permission:inventory_adjust.view')->group(function () { + Route::get('/inventory/adjust-docs', [AdjustDocController::class, 'index'])->name('inventory.adjust.index'); + Route::get('/inventory/adjust-docs/get-pending-counts', [AdjustDocController::class, 'getPendingCounts'])->name('inventory.adjust.pending-counts'); + Route::get('/inventory/adjust-docs/{doc}', [AdjustDocController::class, 'show'])->name('inventory.adjust.show'); }); + Route::post('/inventory/adjust-docs', [AdjustDocController::class, 'store'])->middleware('permission:inventory_adjust.create')->name('inventory.adjust.store'); + Route::put('/inventory/adjust-docs/{doc}', [AdjustDocController::class, 'update'])->middleware('permission:inventory_adjust.edit')->name('inventory.adjust.update'); + Route::delete('/inventory/adjust-docs/{doc}', [AdjustDocController::class, 'destroy'])->middleware('permission:inventory_adjust.delete')->name('inventory.adjust.destroy'); // 撥補單/調撥單 (Transfer Order) - Global - Route::middleware('permission:inventory.transfer')->group(function () { + Route::middleware('permission:inventory_transfer.view')->group(function () { Route::get('/inventory/transfer-orders', [TransferOrderController::class, 'index'])->name('inventory.transfer.index'); Route::get('/inventory/transfer-orders/{order}', [TransferOrderController::class, 'show'])->name('inventory.transfer.show'); - Route::post('/inventory/transfer-orders', [TransferOrderController::class, 'store'])->name('inventory.transfer.store'); - Route::put('/inventory/transfer-orders/{order}', [TransferOrderController::class, 'update'])->name('inventory.transfer.update'); - Route::delete('/inventory/transfer-orders/{order}', [TransferOrderController::class, 'destroy'])->name('inventory.transfer.destroy'); }); + Route::post('/inventory/transfer-orders', [TransferOrderController::class, 'store'])->middleware('permission:inventory_transfer.create')->name('inventory.transfer.store'); + Route::put('/inventory/transfer-orders/{order}', [TransferOrderController::class, 'update'])->middleware('permission:inventory_transfer.edit')->name('inventory.transfer.update'); + Route::delete('/inventory/transfer-orders/{order}', [TransferOrderController::class, 'destroy'])->middleware('permission:inventory_transfer.delete')->name('inventory.transfer.destroy'); Route::get('/api/warehouses/{warehouse}/inventories', [TransferOrderController::class, 'getWarehouseInventories']) ->middleware('permission:inventory.view') ->name('api.warehouses.inventories'); diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index f4b056f..732a078 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -35,11 +35,26 @@ class PermissionSeeder extends Seeder // 庫存管理 'inventory.view', 'inventory.view_cost', // 查看成本與價值 - 'inventory.adjust', - 'inventory.count', // 庫存盤點 - 'inventory.transfer', // 庫存調撥 'inventory.delete', + // 庫存盤點 (Stock Counting) + 'inventory_count.view', + 'inventory_count.create', + 'inventory_count.edit', + 'inventory_count.delete', + + // 庫存調整 (Stock Adjustment) + 'inventory_adjust.view', + 'inventory_adjust.create', + 'inventory_adjust.edit', + 'inventory_adjust.delete', + + // 庫存調撥 (Stock Transfer) + 'inventory_transfer.view', + 'inventory_transfer.create', + 'inventory_transfer.edit', + 'inventory_transfer.delete', + // 進貨單管理 'goods_receipts.view', 'goods_receipts.create', @@ -118,7 +133,10 @@ class PermissionSeeder extends Seeder 'products.view', 'products.create', 'products.edit', 'products.delete', 'purchase_orders.view', 'purchase_orders.create', 'purchase_orders.edit', 'purchase_orders.delete', 'purchase_orders.publish', - 'inventory.view', 'inventory.view_cost', 'inventory.adjust', 'inventory.transfer', 'inventory.delete', + 'inventory.view', 'inventory.view_cost', 'inventory.delete', + 'inventory_count.view', 'inventory_count.create', 'inventory_count.edit', 'inventory_count.delete', + 'inventory_adjust.view', 'inventory_adjust.create', 'inventory_adjust.edit', 'inventory_adjust.delete', + 'inventory_transfer.view', 'inventory_transfer.create', 'inventory_transfer.edit', 'inventory_transfer.delete', 'goods_receipts.view', 'goods_receipts.create', 'goods_receipts.edit', 'goods_receipts.delete', 'production_orders.view', 'production_orders.create', 'production_orders.edit', 'production_orders.delete', 'recipes.view', 'recipes.create', 'recipes.edit', 'recipes.delete', @@ -134,7 +152,10 @@ class PermissionSeeder extends Seeder // warehouse-manager 管理庫存與倉庫 $warehouseManager->givePermissionTo([ 'products.view', - 'inventory.view', 'inventory.adjust', 'inventory.count', 'inventory.transfer', 'inventory.delete', + 'inventory.view', 'inventory.delete', + 'inventory_count.view', 'inventory_count.create', 'inventory_count.edit', 'inventory_count.delete', + 'inventory_adjust.view', 'inventory_adjust.create', 'inventory_adjust.edit', 'inventory_adjust.delete', + 'inventory_transfer.view', 'inventory_transfer.create', 'inventory_transfer.edit', 'inventory_transfer.delete', 'goods_receipts.view', 'goods_receipts.create', 'goods_receipts.edit', 'goods_receipts.delete', 'production_orders.view', 'production_orders.create', 'production_orders.edit', 'warehouses.view', 'warehouses.create', 'warehouses.edit', diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx index b10dc70..65fdf7c 100644 --- a/resources/js/Layouts/AuthenticatedLayout.tsx +++ b/resources/js/Layouts/AuthenticatedLayout.tsx @@ -105,21 +105,21 @@ export default function AuthenticatedLayout({ label: "庫存盤點", icon: , route: "/inventory/count-docs", - permission: "inventory.view", + permission: "inventory_count.view", }, { id: "stock-adjustment", label: "庫存盤調", icon: , route: "/inventory/adjust-docs", - permission: "inventory.adjust", + permission: "inventory_adjust.view", }, { id: "stock-transfer", label: "庫存調撥", icon: , route: "/inventory/transfer-orders", - permission: "inventory.transfer", + permission: "inventory_transfer.view", }, ], }, diff --git a/resources/js/Pages/Admin/Role/Edit.tsx b/resources/js/Pages/Admin/Role/Edit.tsx index a5c8261..3dec114 100644 --- a/resources/js/Pages/Admin/Role/Edit.tsx +++ b/resources/js/Pages/Admin/Role/Edit.tsx @@ -70,16 +70,20 @@ export default function RoleEdit({ role, groupedPermissions, currentPermissions const translateAction = (permissionName: string) => { const parts = permissionName.split('.'); if (parts.length < 2) return permissionName; - const action = parts[1]; + const action = parts[parts.length - 1]; const map: Record = { 'view': '檢視', 'create': '新增', 'edit': '編輯', 'delete': '刪除', - 'publish': '發布', + 'publish': '發佈', 'adjust': '調整', 'transfer': '調撥', + 'count': '盤點', + // 'inventory_count': '盤點', // Hide prefix + // 'inventory_adjust': '盤調', // Hide prefix + // 'inventory_transfer': '調撥', // Hide prefix 'safety_stock': '安全庫存設定', 'export': '匯出', 'complete': '完成', @@ -88,7 +92,19 @@ export default function RoleEdit({ role, groupedPermissions, currentPermissions 'activate': '啟用/停用', }; - return map[action] || action; + const actionText = map[action] || action; + + // 處理多段式權限 (例如 inventory_count.view) + if (parts.length >= 2) { + const middleKey = parts[parts.length - 2]; + + // 如果中間那段有翻譯且不等於動作本身,則顯示為 "標籤: 動作" + if (map[middleKey] && middleKey !== action) { + return `${map[middleKey]}: ${actionText}`; + } + } + + return actionText; }; return ( diff --git a/resources/js/Pages/Inventory/Adjust/Index.tsx b/resources/js/Pages/Inventory/Adjust/Index.tsx index 4a69cf1..d7ba412 100644 --- a/resources/js/Pages/Inventory/Adjust/Index.tsx +++ b/resources/js/Pages/Inventory/Adjust/Index.tsx @@ -1,5 +1,6 @@ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, useForm, router, Link } from '@inertiajs/react'; +import { usePermission } from '@/hooks/usePermission'; import { Table, TableBody, @@ -71,7 +72,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat 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 [perPage, setPerPage] = useState(filters.per_page || '10'); const [deleteId, setDeleteId] = useState(null); // For Count Doc Selection @@ -115,9 +116,12 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat 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 handlePerPageChange = (value: string) => { + setPerPage(value); + router.get(route('inventory.adjust.index'), + { ...filters, search: searchQuery, warehouse_id: warehouseId, per_page: value, page: 1 }, + { preserveState: false, replace: true, preserveScroll: true } + ); }; const confirmDelete = (id: string, e: React.MouseEvent) => { @@ -134,6 +138,8 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat } }; + const { can } = usePermission(); + const { data, setData, post, processing, reset } = useForm({ count_doc_id: null as string | null, warehouse_id: '', @@ -229,7 +235,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat {/* Action Buttons */}
- + - + {(() => { + const isDraft = doc.status === 'draft'; + const canEdit = can('inventory_adjust.edit'); + const canView = can('inventory_adjust.view'); + + if (isDraft && canEdit) { + return ( + + + + ); + } + + if (canView) { + return ( + + + + ); + } + return null; + })()} + {doc.status === 'draft' && ( - + + + )}
@@ -328,12 +359,12 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat value={perPage} onValueChange={handlePerPageChange} options={[ - { label: "15", value: "15" }, - { label: "30", value: "30" }, + { label: "10", value: "10" }, + { label: "20", value: "20" }, { label: "50", value: "50" }, { label: "100", value: "100" } ]} - className="w-[100px] h-8" + className="w-[90px] h-8" showSearch={false} /> diff --git a/resources/js/Pages/Inventory/Adjust/Show.tsx b/resources/js/Pages/Inventory/Adjust/Show.tsx index cd829c9..6c26ce2 100644 --- a/resources/js/Pages/Inventory/Adjust/Show.tsx +++ b/resources/js/Pages/Inventory/Adjust/Show.tsx @@ -1,5 +1,6 @@ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, Link, useForm, router } from '@inertiajs/react'; +import { usePermission } from '@/hooks/usePermission'; import { Table, TableBody, @@ -65,7 +66,10 @@ interface AdjDoc { } 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({ @@ -183,7 +187,6 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) { setData('action', 'save'); put(route('inventory.adjust.update', [doc.id]), { preserveScroll: true, - onSuccess: () => toast.success("草稿儲存成功"), }); }; @@ -199,15 +202,12 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) { } as any, { onSuccess: () => { setIsPostDialogOpen(false); - toast.success("盤調單過帳成功"); } }); }; const handleDelete = () => { - destroy(route('inventory.adjust.destroy', [doc.id]), { - onSuccess: () => toast.success("盤調單已刪除"), - }); + destroy(route('inventory.adjust.destroy', [doc.id])); }; return ( @@ -263,65 +263,69 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {

- {isDraft && ( - - - - - - - - 確定要刪除此盤調單嗎? - - 此動作將會永久移除本張草稿,且無法復原。 - - - - 取消 - 確認刪除 - - - + {!isReadOnly && ( + <> + + + + + + + + 確定要刪除此盤調單嗎? + + 此動作將會永久移除本張草稿,且無法復原。 + + + + 取消 + 確認刪除 + + + + - + + - - - - - - - 確定要過帳嗎? - - 過帳後庫存將立即根據調整數量進行增減,且無法再修改此盤調單。 - - - - 取消 - 確認過帳 - - - - + + + + + + + 確定要過帳嗎? + + 過帳後庫存將立即根據調整數量進行增減,且無法再修改此盤調單。 + + + + 取消 + 確認過帳 + + + + + )}
@@ -332,7 +336,7 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
- {isDraft ? ( + {!isReadOnly ? ( setData('reason', e.target.value)} @@ -345,7 +349,7 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
- {isDraft ? ( + {!isReadOnly ? ( setData('remarks', e.target.value)} @@ -367,7 +371,7 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) { 請輸入各項商品的實盤與帳面之差異數量。正數為增加,負數為減少。

- {isDraft && !doc.count_doc_id && ( + {!isReadOnly && !doc.count_doc_id && ( - - {!['completed', 'adjusted'].includes(doc.status) && ( + {/* Action Button Logic: Prefer Edit if allowed and status is active, otherwise fallback to View if allowed */} + {(() => { + const isEditable = !['completed', 'adjusted'].includes(doc.status); + const canEdit = can('inventory_count.edit'); + const canView = can('inventory_count.view'); + + if (isEditable && canEdit) { + return ( + + + + ); + } + + if (canView) { + return ( + + + + ); + } + + return null; + })()} + {!['completed', 'adjusted'].includes(doc.status) && ( + - )} - + + )}
@@ -351,7 +378,7 @@ export default function Index({ auth, docs, warehouses, filters }: any) { { label: "50", value: "50" }, { label: "100", value: "100" } ]} - className="w-[100px] h-8" + className="w-[90px] h-8" showSearch={false} /> diff --git a/resources/js/Pages/Inventory/Count/Show.tsx b/resources/js/Pages/Inventory/Count/Show.tsx index 5d39c49..f2c4204 100644 --- a/resources/js/Pages/Inventory/Count/Show.tsx +++ b/resources/js/Pages/Inventory/Count/Show.tsx @@ -1,5 +1,6 @@ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; -import { Head, useForm, Link, router } from '@inertiajs/react'; // Added Link +import { Head, useForm, Link, router } from '@inertiajs/react'; +import { usePermission } from '@/hooks/usePermission'; // Added Link import { Table, TableBody, @@ -61,7 +62,10 @@ export default function Show({ doc }: any) { }); } + const { can } = usePermission(); const isCompleted = ['completed', 'adjusted'].includes(doc.status); + const canEdit = can('inventory_count.edit'); + const isReadOnly = isCompleted || !canEdit; // Calculate progress const totalItems = doc.items.length; @@ -155,7 +159,7 @@ export default function Show({ doc }: any) { {!isCompleted && (
- + + + + )}
@@ -369,7 +371,7 @@ export default function Index({ warehouses, orders, filters }: any) { { label: "50", value: "50" }, { label: "100", value: "100" } ]} - className="w-[100px] h-8" + className="w-[90px] h-8" showSearch={false} /> diff --git a/resources/js/Pages/Product/Index.tsx b/resources/js/Pages/Product/Index.tsx index a0d49d6..47f598b 100644 --- a/resources/js/Pages/Product/Index.tsx +++ b/resources/js/Pages/Product/Index.tsx @@ -288,7 +288,7 @@ export default function ProductManagement({ products, categories, units, filters { label: "50", value: "50" }, { label: "100", value: "100" } ]} - className="w-[100px] h-8" + className="w-[90px] h-8" showSearch={false} /> diff --git a/resources/js/Pages/Warehouse/Index.tsx b/resources/js/Pages/Warehouse/Index.tsx index 763e522..b88aec0 100644 --- a/resources/js/Pages/Warehouse/Index.tsx +++ b/resources/js/Pages/Warehouse/Index.tsx @@ -38,12 +38,14 @@ interface PageProps { }; filters: { search?: string; + per_page?: string; }; } export default function WarehouseIndex({ warehouses, totals, filters }: PageProps) { // 篩選狀態 const [searchTerm, setSearchTerm] = useState(filters.search || ""); + const [perPage, setPerPage] = useState(filters.per_page || '10'); // 對話框狀態 const [isDialogOpen, setIsDialogOpen] = useState(false); @@ -58,13 +60,21 @@ export default function WarehouseIndex({ warehouses, totals, filters }: PageProp // 搜尋處理 const handleSearch = (term: string) => { setSearchTerm(term); - router.get(route('warehouses.index'), { search: term }, { + router.get(route('warehouses.index'), { search: term, per_page: perPage }, { preserveState: true, preserveScroll: true, replace: true, }); }; + const handlePerPageChange = (value: string) => { + setPerPage(value); + router.get(route('warehouses.index'), + { ...filters, per_page: value, page: 1 }, + { preserveState: false, replace: true, preserveScroll: true } + ); + }; + // 導航處理 const handleViewInventory = (warehouseId: string | number) => { router.get(`/warehouses/${warehouseId}/inventory`); @@ -193,7 +203,7 @@ export default function WarehouseIndex({ warehouses, totals, filters }: PageProp {/* 操作按鈕 */}
- +