diff --git a/.agent/skills/ui-consistency/SKILL.md b/.agent/skills/ui-consistency/SKILL.md index 13ad562..e5ef3bd 100644 --- a/.agent/skills/ui-consistency/SKILL.md +++ b/.agent/skills/ui-consistency/SKILL.md @@ -247,6 +247,30 @@ tooltip ``` +### 3.4 返回按鈕規範 + +詳情頁面(如:查看庫存、進貨單詳情)的返回按鈕應統一放置於 **頁面標題上方**,並採用「**圖標 + 文字**」的 Outlined 樣式。 + +**樣式規格**: +- **位置**:標題區域上方 (`mb-6`),獨立於標題列 +- **樣式**:`variant="outline"` + `className="gap-2 button-outlined-primary"` +- **圖標**:`` +- **文字**:清楚說明返回目的地,例如「返回倉庫管理」、「返回列表」 + +```tsx +
+ + + +
+``` + --- ## 4. 圖標規範 diff --git a/app/Modules/Inventory/Controllers/CountDocController.php b/app/Modules/Inventory/Controllers/CountDocController.php index 8b37e9a..f00fc27 100644 --- a/app/Modules/Inventory/Controllers/CountDocController.php +++ b/app/Modules/Inventory/Controllers/CountDocController.php @@ -40,7 +40,12 @@ class CountDocController extends Controller $perPage = 15; } - $docs = $query->orderByDesc('created_at') + $countQuery = function ($query) { + $query->whereNotNull('counted_qty'); + }; + + $docs = $query->withCount(['items', 'items as counted_items_count' => $countQuery]) + ->orderByDesc('created_at') ->paginate($perPage) ->withQueryString() ->through(function ($doc) { @@ -53,6 +58,8 @@ class CountDocController extends Controller 'completed_at' => $doc->completed_at ? $doc->completed_at->format('Y-m-d H:i') : '-', 'created_by' => $doc->createdBy?->name, 'remarks' => $doc->remarks, + 'total_items' => $doc->items_count, + 'counted_items' => $doc->counted_items_count, ]; }); @@ -116,6 +123,39 @@ class CountDocController extends Controller ]); } + public function print(InventoryCountDoc $doc) + { + $doc->load(['items.product.baseUnit', 'createdBy', 'completedBy', 'warehouse']); + + $docData = [ + 'id' => (string) $doc->id, + 'doc_no' => $doc->doc_no, + 'warehouse_name' => $doc->warehouse->name, + 'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d') : date('Y-m-d'), // Use date only + 'created_at' => $doc->created_at->format('Y-m-d'), + 'print_date' => date('Y-m-d'), + 'created_by' => $doc->createdBy?->name, + 'items' => $doc->items->map(function ($item) { + return [ + 'id' => (string) $item->id, + 'product_name' => $item->product->name, + 'product_code' => $item->product->code, + 'specification' => $item->product->specification, + 'unit' => $item->product->baseUnit?->name, + 'quantity' => (float) ($item->counted_qty ?? $item->system_qty), // Default to system qty if counted is null, or just counted? User wants "Count Sheet" -> maybe blank if not counted? + // Actually, if it's "Completed", we show counted. If it's "Pending", we usually show blank or system. + // The 'Show' page logic suggests we show counted_qty. + 'counted_qty' => $item->counted_qty, + 'notes' => $item->notes, + ]; + }), + ]; + + return Inertia::render('Inventory/Count/Print', [ + 'doc' => $docData, + ]); + } + public function update(Request $request, InventoryCountDoc $doc) { if ($doc->status === 'completed') { @@ -142,6 +182,23 @@ class CountDocController extends Controller return redirect()->back()->with('success', '暫存成功'); } + + public function reopen(InventoryCountDoc $doc) + { + if ($doc->status !== 'completed') { + return redirect()->back()->with('error', '只有已核准的盤點單可以取消核准'); + } + + // TODO: Move logic to Service if complex + $doc->update([ + 'status' => 'counting', // Revert to counting (draft) + 'completed_at' => null, + 'completed_by' => null, + ]); + + return redirect()->route('inventory.count.show', [$doc->id]) + ->with('success', '已取消核准,單據回復為盤點中狀態'); + } public function destroy(InventoryCountDoc $doc) { diff --git a/app/Modules/Inventory/Routes/web.php b/app/Modules/Inventory/Routes/web.php index 238c00a..284c671 100644 --- a/app/Modules/Inventory/Routes/web.php +++ b/app/Modules/Inventory/Routes/web.php @@ -81,7 +81,9 @@ Route::middleware('auth')->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'); }); // 庫存盤調 (Stock Adjustment) - Global diff --git a/resources/js/Pages/Inventory/Adjust/Index.tsx b/resources/js/Pages/Inventory/Adjust/Index.tsx index ffb25a0..381e8e8 100644 --- a/resources/js/Pages/Inventory/Adjust/Index.tsx +++ b/resources/js/Pages/Inventory/Adjust/Index.tsx @@ -163,7 +163,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat 庫存盤調單 -

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

+

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

@@ -392,14 +392,14 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat - - + + diff --git a/resources/js/Pages/Inventory/Adjust/Show.tsx b/resources/js/Pages/Inventory/Adjust/Show.tsx index eaf8bf1..2c05fe7 100644 --- a/resources/js/Pages/Inventory/Adjust/Show.tsx +++ b/resources/js/Pages/Inventory/Adjust/Show.tsx @@ -11,12 +11,6 @@ import { import { Button } from "@/Components/ui/button"; import { Input } from "@/Components/ui/input"; import { Badge } from "@/Components/ui/badge"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/Components/ui/card"; import { AlertDialog, AlertDialogAction, @@ -30,7 +24,7 @@ import { } 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 } from "lucide-react"; +import { Save, CheckCircle, Trash2, ArrowLeft, Plus, X, Search, FileText, ClipboardCheck } from "lucide-react"; import { useState, useEffect } from 'react'; import { Dialog, @@ -156,49 +150,61 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) { breadcrumbs={[ { label: '商品與庫存管理', href: '#' }, { label: '庫存盤調', href: route('inventory.adjust.index') }, - { label: doc.doc_no, href: route('inventory.adjust.show', [doc.id]), isPage: true }, + { label: `盤調單: ${doc.doc_no}`, href: route('inventory.adjust.show', [doc.id]), isPage: true }, ]} > -
-
+
+
+ + +
-
- -
-

- {doc.doc_no} - {isDraft ? ( - 草稿 - ) : ( - 已過帳 - )} +
+
+

+ + 盤調單: {doc.doc_no}

-
- 倉庫: {doc.warehouse_name} - | - 建立者: {doc.created_by} - | - 時間: {doc.created_at} -
+ {isDraft ? ( + 草稿 + ) : ( + 已過帳 + )}
+

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

-
+
{isDraft && ( - @@ -215,190 +221,158 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) { - - )}
+

-
- - - 明細備註 - - -
- - {isDraft ? ( - setData('reason', e.target.value)} - className="focus:ring-primary-main" - /> - ) : ( -
{data.reason}
- )} -
-
- - {isDraft ? ( -