refactor: changes to inventory status (approved/unapprove)
This commit is contained in:
@@ -163,7 +163,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
|
||||
<ClipboardCheck className="h-6 w-6 text-primary-main" />
|
||||
庫存盤調單
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">針對盤點差異進行庫存調整與過帳 (盤盈、盤虧、報廢等)</p>
|
||||
<p className="text-gray-500 mt-1">針對盤點差異進行庫存調整與過帳 (盤盈、盤虧、報廢等)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -392,14 +392,14 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="bg-gray-50 -mx-6 -mb-6 p-4 rounded-b-lg">
|
||||
<Button variant="ghost" onClick={() => setIsDialogOpen(false)}>取消</Button>
|
||||
<DialogFooter className="bg-gray-50 -mx-6 -mb-6 p-4 rounded-b-lg gap-2">
|
||||
<Button variant="outline" className="button-outlined-primary" onClick={() => setIsDialogOpen(false)}>取消</Button>
|
||||
<Button
|
||||
className="button-filled-primary"
|
||||
disabled={processing || !data.warehouse_id || !data.reason}
|
||||
onClick={() => handleCreate()}
|
||||
>
|
||||
建立手動盤調
|
||||
新增
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -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 },
|
||||
]}
|
||||
>
|
||||
<Head title={`盤調單 ${doc.doc_no}`} />
|
||||
|
||||
<div className="container mx-auto p-6 max-w-7xl animate-in fade-in duration-500">
|
||||
<div className="space-y-6">
|
||||
<div className="container mx-auto p-6 max-w-7xl animate-in fade-in duration-500 space-y-6">
|
||||
<div>
|
||||
<Link href={route('inventory.adjust.index')}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-2 button-outlined-primary mb-6"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
返回盤調單列表
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-10 w-10 border-grey-200"
|
||||
onClick={() => router.visit(route('inventory.adjust.index'))}
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5 text-grey-600" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-3">
|
||||
{doc.doc_no}
|
||||
{isDraft ? (
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-600 border-none">草稿</Badge>
|
||||
) : (
|
||||
<Badge className="bg-green-100 text-green-700 border-none">已過帳</Badge>
|
||||
)}
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
||||
<ClipboardCheck className="h-6 w-6 text-primary-main" />
|
||||
盤調單: {doc.doc_no}
|
||||
</h1>
|
||||
<div className="flex items-center gap-3 mt-1 text-sm text-grey-500">
|
||||
<span className="flex items-center gap-1"><CheckCircle className="h-3 w-3" /> 倉庫: {doc.warehouse_name}</span>
|
||||
<span>|</span>
|
||||
<span>建立者: {doc.created_by}</span>
|
||||
<span>|</span>
|
||||
<span>時間: {doc.created_at}</span>
|
||||
</div>
|
||||
{isDraft ? (
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-600 border-none">草稿</Badge>
|
||||
) : (
|
||||
<Badge className="bg-green-100 text-green-700 border-none">已過帳</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1 font-medium flex items-center gap-2">
|
||||
倉庫: {doc.warehouse_name} <span className="mx-1">|</span>
|
||||
建立者: {doc.created_by} <span className="mx-1">|</span>
|
||||
時間: {doc.created_at}
|
||||
{doc.count_doc_id && (
|
||||
<>
|
||||
<span className="mx-1">|</span>
|
||||
<Link
|
||||
href={route('inventory.count.show', [doc.count_doc_id])}
|
||||
className="flex items-center gap-1 text-primary-main hover:underline"
|
||||
>
|
||||
來源盤點單: {doc.count_doc_no}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{isDraft && (
|
||||
<Can permission="inventory.adjust">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" className="text-red-500 hover:bg-red-50 hover:text-red-600">
|
||||
<Trash2 className="mr-2 h-4 w-4" /> 刪除單據
|
||||
<Button variant="outline" size="sm" disabled={processing} className="button-outlined-error">
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
刪除
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
@@ -215,190 +221,158 @@ export default function Show({ doc }: { auth: any, doc: AdjDoc }) {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<Button variant="outline" className="border-primary-200 text-primary-main hover:bg-primary-50" onClick={handleSave} disabled={processing}>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
儲存草稿
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="button-outlined-primary"
|
||||
onClick={handleSave}
|
||||
disabled={processing}
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
儲存
|
||||
</Button>
|
||||
|
||||
<Button className="button-filled-primary" onClick={handlePost} disabled={processing}>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
確認過帳
|
||||
<Button
|
||||
size="sm"
|
||||
className="button-filled-primary"
|
||||
onClick={handlePost}
|
||||
disabled={processing}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
過帳
|
||||
</Button>
|
||||
</Can>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<Card className="lg:col-span-2 shadow-sm border-grey-100">
|
||||
<CardHeader className="bg-grey-50/50 border-b border-grey-100 py-3">
|
||||
<CardTitle className="text-sm font-semibold text-grey-600">明細備註</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-bold text-grey-500 uppercase tracking-wider">調整原因</Label>
|
||||
{isDraft ? (
|
||||
<Input
|
||||
value={data.reason}
|
||||
onChange={e => setData('reason', e.target.value)}
|
||||
className="focus:ring-primary-main"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-grey-900 font-medium">{data.reason}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-bold text-grey-500 uppercase tracking-wider">備註說明</Label>
|
||||
{isDraft ? (
|
||||
<Textarea
|
||||
value={data.remarks}
|
||||
onChange={e => setData('remarks', e.target.value)}
|
||||
rows={1}
|
||||
className="focus:ring-primary-main"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-grey-600">{data.remarks || '-'}</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="shadow-sm border-grey-100 bg-primary-50/30">
|
||||
<CardHeader className="py-3">
|
||||
<CardTitle className="text-sm font-semibold text-primary-main">來源單據</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div>
|
||||
<Label className="text-[10px] font-bold text-primary-main/60 uppercase">關聯盤點單</Label>
|
||||
{doc.count_doc_id ? (
|
||||
<div className="mt-1">
|
||||
<Link
|
||||
href={route('inventory.count.show', [doc.count_doc_id])}
|
||||
className="text-primary-main font-bold hover:underline flex items-center gap-2"
|
||||
>
|
||||
<FileText className="h-4 w-4" />
|
||||
{doc.count_doc_no || '檢視盤點單'}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-grey-400 italic text-sm mt-1">手動建立,無來源盤點單</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="pt-2 border-t border-primary-100">
|
||||
<Label className="text-[10px] font-bold text-primary-main/60 uppercase">倉庫位置</Label>
|
||||
<p className="font-bold text-grey-900">{doc.warehouse_name}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<h3 className="text-lg font-medium text-grey-900">調整項目清單</h3>
|
||||
{isDraft && (
|
||||
<ProductSearchDialog
|
||||
warehouseId={doc.warehouse_id}
|
||||
onSelect={(product, batch) => addItem(product, batch)}
|
||||
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
||||
{/* Header Fields - Inline */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pb-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-bold text-grey-500 uppercase tracking-wider">調整原因</Label>
|
||||
{isDraft ? (
|
||||
<Input
|
||||
value={data.reason}
|
||||
onChange={e => setData('reason', e.target.value)}
|
||||
className="focus:ring-primary-main h-9"
|
||||
placeholder="請輸入調整原因..."
|
||||
/>
|
||||
) : (
|
||||
<div className="text-grey-900 font-medium py-1">{data.reason}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-gray-50">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-bold text-grey-500 uppercase tracking-wider">備註說明</Label>
|
||||
{isDraft ? (
|
||||
<Input
|
||||
value={data.remarks}
|
||||
onChange={e => setData('remarks', e.target.value)}
|
||||
className="focus:ring-primary-main h-9"
|
||||
placeholder="選填備註..."
|
||||
/>
|
||||
) : (
|
||||
<div className="text-grey-600 py-1">{data.remarks || '-'}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4"></div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-medium text-grey-900">調整項目</h3>
|
||||
{isDraft && (
|
||||
<ProductSearchDialog
|
||||
warehouseId={doc.warehouse_id}
|
||||
onSelect={(product, batch) => addItem(product, batch)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-gray-50">
|
||||
<TableRow>
|
||||
<TableHead className="w-[50px] text-center font-medium text-grey-600">#</TableHead>
|
||||
<TableHead className="pl-4 font-medium text-grey-600">商品資訊</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">批號</TableHead>
|
||||
<TableHead className="w-24 text-center font-medium text-grey-600">單位</TableHead>
|
||||
<TableHead className="w-32 text-right font-medium text-grey-600">調整前庫存</TableHead>
|
||||
<TableHead className="w-40 text-right font-medium text-grey-600">調整數量 (+/-)</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">備註</TableHead>
|
||||
{isDraft && <TableHead className="w-[50px]"></TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableHead className="w-[60px] text-center font-medium text-grey-600">#</TableHead>
|
||||
<TableHead className="pl-4 font-medium text-grey-600">商品資訊</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">批號</TableHead>
|
||||
<TableHead className="w-24 text-center font-medium text-grey-600">單位</TableHead>
|
||||
<TableHead className="w-32 text-right font-medium text-grey-600 text-primary-main">調整前庫存</TableHead>
|
||||
<TableHead className="w-40 text-right font-medium text-grey-600">調整數量 (+/-)</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">備註說明</TableHead>
|
||||
{isDraft && <TableHead className="w-[80px]"></TableHead>}
|
||||
<TableCell colSpan={isDraft ? 8 : 7} className="h-32 text-center text-grey-400">
|
||||
尚未加入任何項目
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={isDraft ? 8 : 7} className="h-32 text-center text-grey-400">
|
||||
尚未載入任何調整項目
|
||||
) : (
|
||||
data.items.map((item, index) => (
|
||||
<TableRow
|
||||
key={`${item.product_id}-${item.batch_number}-${index}`}
|
||||
className="group hover:bg-gray-50/50 transition-colors"
|
||||
>
|
||||
<TableCell className="text-center text-grey-400 font-medium">{index + 1}</TableCell>
|
||||
<TableCell className="pl-4 py-3">
|
||||
<div className="font-bold text-grey-900">{item.product_name}</div>
|
||||
<div className="text-xs text-grey-500 font-mono">{item.product_code}</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
data.items.map((item, index) => (
|
||||
<TableRow
|
||||
key={`${item.product_id}-${item.batch_number}-${index}`}
|
||||
className="group hover:bg-gray-50/50 transition-colors"
|
||||
>
|
||||
<TableCell className="text-center text-grey-400 font-medium">{index + 1}</TableCell>
|
||||
<TableCell className="pl-4">
|
||||
<div className="font-bold text-grey-900">{item.product_name}</div>
|
||||
<div className="text-xs text-grey-500 font-mono">{item.product_code}</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-grey-600">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-center text-grey-500">{item.unit}</TableCell>
|
||||
<TableCell className="text-right font-medium text-grey-400">
|
||||
{item.qty_before}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{isDraft ? (
|
||||
<Input
|
||||
type="number"
|
||||
className="text-right h-9 border-grey-200 focus:ring-primary-main"
|
||||
value={item.adjust_qty}
|
||||
onChange={e => updateItem(index, 'adjust_qty', e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<span className={`font-bold ${Number(item.adjust_qty) > 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{Number(item.adjust_qty) > 0 ? '+' : ''}{item.adjust_qty}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isDraft ? (
|
||||
<Input
|
||||
className="h-9 border-grey-200 focus:ring-primary-main"
|
||||
value={item.notes || ''}
|
||||
onChange={e => updateItem(index, 'notes', e.target.value)}
|
||||
placeholder="輸入備註..."
|
||||
/>
|
||||
) : (
|
||||
<span className="text-grey-600 text-sm">{item.notes || '-'}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
{isDraft && (
|
||||
<TableCell className="text-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 text-red-400 hover:text-red-600 hover:bg-red-50 p-0"
|
||||
onClick={() => removeItem(index)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell className="text-grey-600">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-center text-grey-500">{item.unit}</TableCell>
|
||||
<TableCell className="text-right font-medium text-grey-400">
|
||||
{item.qty_before}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{isDraft ? (
|
||||
<Input
|
||||
type="number"
|
||||
className="text-right h-9 border-grey-200 focus:ring-primary-main"
|
||||
value={item.adjust_qty}
|
||||
onChange={e => updateItem(index, 'adjust_qty', e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<span className={`font-bold ${Number(item.adjust_qty) > 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{Number(item.adjust_qty) > 0 ? '+' : ''}{item.adjust_qty}
|
||||
</span>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isDraft ? (
|
||||
<Input
|
||||
className="h-9 border-grey-200 focus:ring-primary-main text-sm"
|
||||
value={item.notes || ''}
|
||||
onChange={e => updateItem(index, 'notes', e.target.value)}
|
||||
placeholder="備註..."
|
||||
/>
|
||||
) : (
|
||||
<span className="text-grey-600 text-sm">{item.notes || '-'}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
{isDraft && (
|
||||
<TableCell className="text-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 text-red-400 hover:text-red-600 hover:bg-red-50 p-0"
|
||||
onClick={() => removeItem(index)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="bg-gray-50/80 border border-dashed border-grey-200 rounded-lg p-4 flex items-start gap-3">
|
||||
<CheckCircle className="h-5 w-5 text-primary-main mt-0.5 shrink-0" />
|
||||
<div className="text-xs text-grey-500 leading-relaxed">
|
||||
<p className="font-bold text-grey-700">操作導引:</p>
|
||||
<ul className="list-disc ml-4 space-y-1 mt-1">
|
||||
<li><strong>調整數量 (+/-):</strong>輸入正數 (+) 表示增加庫存 (盤盈、溢收);輸入負數 (-) 表示扣減庫存 (盤虧、報廢、損耗)。</li>
|
||||
<li><strong>批號控管:</strong>若商品啟用批號管理,請務必確認調整項目對應的批號是否正確。</li>
|
||||
<li><strong>過帳生效:</strong>點擊「確認過帳」後,單據將轉為唯讀,並立即更新即時庫存明細與異動紀錄。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
|
||||
@@ -220,11 +220,11 @@ export default function Index({ auth, docs, warehouses, filters }: any) {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
||||
<Button type="button" variant="outline" className="button-outlined-primary" onClick={() => setIsCreateDialogOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit" className="button-filled-primary" disabled={processing || !data.warehouse_id}>
|
||||
確認建立
|
||||
新增
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
@@ -244,6 +244,7 @@ export default function Index({ auth, docs, warehouses, filters }: any) {
|
||||
<TableHead>倉庫</TableHead>
|
||||
<TableHead>狀態</TableHead>
|
||||
<TableHead>快照時間</TableHead>
|
||||
<TableHead>盤點進度</TableHead>
|
||||
<TableHead>完成時間</TableHead>
|
||||
<TableHead>建立人員</TableHead>
|
||||
<TableHead className="text-center">操作</TableHead>
|
||||
@@ -252,7 +253,7 @@ export default function Index({ auth, docs, warehouses, filters }: any) {
|
||||
<TableBody>
|
||||
{docs.data.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="text-center h-24 text-gray-500">
|
||||
<TableCell colSpan={9} className="text-center h-24 text-gray-500">
|
||||
尚無盤點紀錄
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -266,6 +267,11 @@ export default function Index({ auth, docs, warehouses, filters }: any) {
|
||||
<TableCell>{doc.warehouse_name}</TableCell>
|
||||
<TableCell>{getStatusBadge(doc.status)}</TableCell>
|
||||
<TableCell className="text-gray-500 text-sm">{doc.snapshot_date}</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-medium text-gray-700">{doc.counted_items}</span>
|
||||
<span className="text-gray-400 mx-1">/</span>
|
||||
<span className="text-gray-500">{doc.total_items}</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-gray-500 text-sm">{doc.completed_at || '-'}</TableCell>
|
||||
<TableCell className="text-sm">{doc.created_by}</TableCell>
|
||||
<TableCell className="text-center">
|
||||
@@ -279,11 +285,10 @@ export default function Index({ auth, docs, warehouses, filters }: any) {
|
||||
title={doc.status === 'completed' ? '查閱' : '盤點'}
|
||||
>
|
||||
{doc.status === 'completed' ? (
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
<Eye className="w-4 h-4" />
|
||||
) : (
|
||||
<Pencil className="w-4 h-4 mr-1" />
|
||||
<Pencil className="w-4 h-4" />
|
||||
)}
|
||||
{doc.status === 'completed' ? '查閱' : '盤點'}
|
||||
</Button>
|
||||
</Link>
|
||||
</Can>
|
||||
|
||||
166
resources/js/Pages/Inventory/Count/Print.tsx
Normal file
166
resources/js/Pages/Inventory/Count/Print.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Head } from '@inertiajs/react';
|
||||
import JsBarcode from 'jsbarcode';
|
||||
|
||||
interface PrintProps {
|
||||
doc: {
|
||||
doc_no: string;
|
||||
warehouse_name: string;
|
||||
snapshot_date: string;
|
||||
created_at: string;
|
||||
print_date: string;
|
||||
created_by: string;
|
||||
items: Array<{
|
||||
id: string;
|
||||
product_name: string;
|
||||
product_code: string;
|
||||
specification: string;
|
||||
unit: string;
|
||||
quantity: number;
|
||||
counted_qty: number | null;
|
||||
notes: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Print({ doc }: PrintProps) {
|
||||
const barcodeRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (barcodeRef.current) {
|
||||
try {
|
||||
JsBarcode(barcodeRef.current, doc.doc_no, {
|
||||
format: "CODE128",
|
||||
width: 2, // Thicker bars for better scanning
|
||||
height: 50, // Taller
|
||||
displayValue: false,
|
||||
margin: 10, // Mandatory quiet zone for scanners
|
||||
marginBottom: 5
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Barcode generation failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Delay print slightly to ensure render
|
||||
const timer = setTimeout(() => {
|
||||
window.print();
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [doc.doc_no]);
|
||||
|
||||
return (
|
||||
<div className="bg-white text-black font-sans text-sm min-h-screen">
|
||||
<Head title={`列印盤點單 ${doc.doc_no}`} />
|
||||
|
||||
<style>{`
|
||||
@media print {
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 10mm;
|
||||
}
|
||||
body {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
/* Hide browser default header/footer if possible (browser dependent) */
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* Header Section */}
|
||||
<div className="relative mb-6">
|
||||
{/* Barcode - Top Right */}
|
||||
<div className="absolute top-0 right-0 flex flex-col items-end">
|
||||
<svg ref={barcodeRef}></svg>
|
||||
{/* <span className="text-xs font-mono mt-1">{doc.doc_no}</span> */}
|
||||
</div>
|
||||
|
||||
{/* Company & Title - Center */}
|
||||
<div className="text-center pt-4">
|
||||
<h1 className="text-2xl font-bold tracking-widest mb-2">台灣心零售股份有限公司</h1>
|
||||
<h2 className="text-xl font-bold tracking-widest">盤點單</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info Section */}
|
||||
<div className="flex justify-between items-end mb-2 border-b-2 border-black pb-2">
|
||||
<div className="space-y-1">
|
||||
<div className="flex gap-4">
|
||||
<span className="font-bold">單號:</span>
|
||||
<span className="font-mono font-bold">{doc.doc_no}</span>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<span className="font-bold">日期:</span>
|
||||
<span>{doc.created_at}</span>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<span className="font-bold">倉庫:</span>
|
||||
<span>{doc.warehouse_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex gap-4">
|
||||
<span className="font-bold">印單日期:</span>
|
||||
<span>{doc.print_date}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Section */}
|
||||
<table className="w-full border-collapse border border-black mb-8">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border border-black px-2 py-1 w-12 text-center">序號</th>
|
||||
<th className="border border-black px-2 py-1 w-32 text-left">品號</th>
|
||||
<th className="border border-black px-2 py-1 text-left">品名</th>
|
||||
<th className="border border-black px-2 py-1 w-32 text-left">規格</th>
|
||||
<th className="border border-black px-2 py-1 w-20 text-right">數量</th>
|
||||
<th className="border border-black px-2 py-1 w-16 text-center">單位</th>
|
||||
<th className="border border-black px-2 py-1 w-32 text-left">備註</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{doc.items.map((item, index) => (
|
||||
<tr key={item.id}>
|
||||
<td className="border border-black px-2 py-2 text-center">{index + 1}</td>
|
||||
<td className="border border-black px-2 py-2 font-mono">{item.product_code}</td>
|
||||
<td className="border border-black px-2 py-2">{item.product_name}</td>
|
||||
<td className="border border-black px-2 py-2">{item.specification || '-'}</td>
|
||||
<td className="border border-black px-2 py-2 text-right">
|
||||
{item.counted_qty !== null ? Number(item.counted_qty).toFixed(2) : ''}
|
||||
</td>
|
||||
<td className="border border-black px-2 py-2 text-center">{item.unit || '-'}</td>
|
||||
<td className="border border-black px-2 py-2">{item.notes}</td>
|
||||
</tr>
|
||||
))}
|
||||
{/* Empty rows filler if needed, but usually not required unless strictly paging */}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Footer Section */}
|
||||
<div className="fixed bottom-0 w-full mb-4">
|
||||
<div className="flex justify-between items-end px-4">
|
||||
<div className="w-1/3 border-b border-black pb-1 mb-1">
|
||||
<span className="font-bold">製單人員:</span>
|
||||
<span className="ml-2">{doc.created_by}</span>
|
||||
</div>
|
||||
<div className="w-1/3 border-b border-black pb-1 mb-1 mx-4">
|
||||
<span className="font-bold">盤點人員:</span>
|
||||
</div>
|
||||
<div className="w-1/3 border-b border-black pb-1 mb-1">
|
||||
<span className="font-bold">核對人員:</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-xs mt-4 px-4">
|
||||
<div>
|
||||
製單人員:系統管理員 印單人員:{doc.created_by}
|
||||
</div>
|
||||
<div>
|
||||
第 1 頁 / 共 1 頁
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
||||
import { Head, useForm } from '@inertiajs/react';
|
||||
import { Head, useForm, Link } from '@inertiajs/react'; // Added Link
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Input } from '@/Components/ui/input';
|
||||
import { Badge } from '@/Components/ui/badge';
|
||||
import { Save, CheckCircle, Printer, Trash2, ClipboardCheck } from 'lucide-react';
|
||||
import { Save, CheckCircle, Printer, Trash2, ClipboardCheck, ArrowLeft, RotateCcw } from 'lucide-react'; // Added ArrowLeft
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -55,6 +55,14 @@ export default function Show({ doc }: any) {
|
||||
destroy(route('inventory.count.destroy', [doc.id]));
|
||||
};
|
||||
|
||||
const handleReopen = () => {
|
||||
if (confirm('確定要取消核准嗎?單據將回復為盤點中狀態,且庫存異動將被撤回(若已過帳)。')) {
|
||||
router.visit(route('inventory.count.reopen', [doc.id]), {
|
||||
method: 'put',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isCompleted = doc.status === 'completed';
|
||||
|
||||
// Calculate progress
|
||||
@@ -72,28 +80,62 @@ export default function Show({ doc }: any) {
|
||||
>
|
||||
<Head title={`盤點單 ${doc.doc_no}`} />
|
||||
|
||||
<div className="container mx-auto p-6 max-w-7xl animate-in fade-in duration-500">
|
||||
<div className="space-y-6">
|
||||
<div className="container mx-auto p-6 max-w-7xl animate-in fade-in duration-500 space-y-6">
|
||||
<div>
|
||||
<Link href={route('inventory.count.index')}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-2 button-outlined-primary mb-6"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
返回盤點單列表
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
||||
<ClipboardCheck className="h-6 w-6 text-primary-main" />
|
||||
盤點單: {doc.doc_no}
|
||||
</h1>
|
||||
{doc.status === 'completed' ? (
|
||||
<Badge className="bg-green-500 hover:bg-green-600">已完成</Badge>
|
||||
) : (
|
||||
<Badge className="bg-blue-500 hover:bg-blue-600">盤點中</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1 font-medium">
|
||||
倉庫: {doc.warehouse_name} <span className="mx-2">|</span> 建立人: {doc.created_by} <span className="mx-2">|</span> 快照時間: {doc.snapshot_date}
|
||||
</p>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
||||
<ClipboardCheck className="h-6 w-6 text-primary-main" />
|
||||
盤點單: {doc.doc_no}
|
||||
</h1>
|
||||
{doc.status === 'completed' ? (
|
||||
<Badge className="bg-green-500 hover:bg-green-600">已核准</Badge>
|
||||
) : (
|
||||
<Badge className="bg-blue-500 hover:bg-blue-600">盤點中</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1 font-medium">
|
||||
倉庫: {doc.warehouse_name} <span className="mx-2">|</span> 建立人: {doc.created_by} <span className="mx-2">|</span> 快照時間: {doc.snapshot_date}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="button-outlined-primary"
|
||||
onClick={() => window.open(route('inventory.count.print', [doc.id]), '_blank')}
|
||||
>
|
||||
<Printer className="w-4 h-4 mr-2" />
|
||||
列印
|
||||
</Button>
|
||||
|
||||
{isCompleted && (
|
||||
<Can permission="inventory.adjust">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="button-outlined-error"
|
||||
onClick={handleReopen}
|
||||
disabled={processing}
|
||||
>
|
||||
<RotateCcw className="w-4 h-4 mr-2" />
|
||||
取消核准
|
||||
</Button>
|
||||
</Can>
|
||||
)}
|
||||
|
||||
{!isCompleted && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Can permission="inventory.view">
|
||||
@@ -101,7 +143,7 @@ export default function Show({ doc }: any) {
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" disabled={processing} className="button-outlined-error">
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
作廢盤點單
|
||||
作廢
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
@@ -126,7 +168,7 @@ export default function Show({ doc }: any) {
|
||||
disabled={processing}
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
暫存進度
|
||||
更新
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -135,142 +177,142 @@ export default function Show({ doc }: any) {
|
||||
disabled={processing}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
完成盤點
|
||||
完成
|
||||
</Button>
|
||||
</Can>
|
||||
</div>
|
||||
)}
|
||||
{isCompleted && (
|
||||
<Button variant="outline" size="sm" onClick={() => window.print()}>
|
||||
<Printer className="w-4 h-4 mr-2" />
|
||||
列印報表
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isCompleted && (
|
||||
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-500 font-medium">盤點進度: {countedItems} / {totalItems} 項目</span>
|
||||
<span className="font-bold text-primary-main">{progress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div className="bg-primary-main h-2 rounded-full transition-all duration-300" style={{ width: `${progress}%` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg text-grey-900">盤點明細</h3>
|
||||
<p className="text-sm text-grey-500">
|
||||
請輸入每個項目的「實盤數量」。若有差異系統將自動計算。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-gray-50">
|
||||
<TableRow>
|
||||
<TableHead className="w-[50px] text-center font-medium text-grey-600">#</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">商品名稱 / 代號</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">批號</TableHead>
|
||||
<TableHead className="text-right font-medium text-grey-600">系統庫存</TableHead>
|
||||
<TableHead className="text-right w-32 font-medium text-grey-600">實盤數量</TableHead>
|
||||
<TableHead className="text-right font-medium text-grey-600">差異</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">單位</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">備註</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{doc.items.map((item: any, index: number) => {
|
||||
const formItem = data.items[index];
|
||||
const diff = formItem.counted_qty !== '' && formItem.counted_qty !== null
|
||||
? (parseFloat(formItem.counted_qty) - item.system_qty)
|
||||
: 0;
|
||||
const hasDiff = Math.abs(diff) > 0.0001;
|
||||
|
||||
return (
|
||||
<TableRow key={item.id} className={hasDiff && formItem.counted_qty !== '' ? "bg-red-50/30" : ""}>
|
||||
<TableCell className="text-gray-500 font-medium text-center">
|
||||
{index + 1}
|
||||
</TableCell>
|
||||
<TableCell className="py-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-gray-900">{item.product_name}</span>
|
||||
<span className="text-xs text-gray-500 font-mono">{item.product_code}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm font-mono">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-right font-medium">{item.system_qty.toFixed(2)}</TableCell>
|
||||
<TableCell className="text-right px-1 py-3">
|
||||
{isCompleted ? (
|
||||
<span className="font-semibold mr-2">{item.counted_qty}</span>
|
||||
) : (
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formItem.counted_qty ?? ''}
|
||||
onChange={(e) => updateItem(index, 'counted_qty', e.target.value)}
|
||||
onWheel={(e: any) => e.target.blur()}
|
||||
disabled={processing}
|
||||
className="h-9 text-right font-medium focus:ring-primary-main"
|
||||
placeholder="盤點..."
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<span className={`font-bold ${!hasDiff
|
||||
? 'text-gray-400'
|
||||
: diff > 0
|
||||
? 'text-green-600'
|
||||
: 'text-red-600'
|
||||
}`}>
|
||||
{formItem.counted_qty !== '' && formItem.counted_qty !== null
|
||||
? diff.toFixed(2)
|
||||
: '-'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-gray-500">{item.unit || item.unit_name}</TableCell>
|
||||
<TableCell className="px-1">
|
||||
{isCompleted ? (
|
||||
<span className="text-sm text-gray-600">{item.notes}</span>
|
||||
) : (
|
||||
<Input
|
||||
value={formItem.notes}
|
||||
onChange={(e) => updateItem(index, 'notes', e.target.value)}
|
||||
disabled={processing}
|
||||
className="h-9 text-sm"
|
||||
placeholder="備註..."
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="bg-gray-50/80 border border-dashed border-grey-200 rounded-lg p-4 flex items-start gap-3">
|
||||
<div className="bg-blue-100 p-2 rounded-lg">
|
||||
<Save className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-1 text-sm">操作導引</h4>
|
||||
<p className="text-xs text-gray-500 leading-relaxed">
|
||||
資料會自動保存盤點人與時間。若尚未盤點完,您可以點擊「暫存進度」稍後繼續。
|
||||
確認所有項目資料正確後,請點擊「完成盤點」結束盤點作業。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isCompleted && (
|
||||
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-500 font-medium">盤點進度: {countedItems} / {totalItems} 項目</span>
|
||||
<span className="font-bold text-primary-main">{progress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div className="bg-primary-main h-2 rounded-full transition-all duration-300" style={{ width: `${progress}%` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg text-grey-900">盤點明細</h3>
|
||||
<p className="text-sm text-grey-500">
|
||||
請輸入每個項目的「實盤數量」。若有差異系統將自動計算。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-gray-50">
|
||||
<TableRow>
|
||||
<TableHead className="w-[50px] text-center font-medium text-grey-600">#</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">商品名稱 / 代號</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">批號</TableHead>
|
||||
<TableHead className="text-right font-medium text-grey-600">系統庫存</TableHead>
|
||||
<TableHead className="text-right w-32 font-medium text-grey-600">實盤數量</TableHead>
|
||||
<TableHead className="text-right font-medium text-grey-600">差異</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">單位</TableHead>
|
||||
<TableHead className="font-medium text-grey-600">備註</TableHead>
|
||||
{!isCompleted && <TableHead className="w-[50px]"></TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{doc.items.map((item: any, index: number) => {
|
||||
const formItem = data.items[index];
|
||||
const diff = formItem.counted_qty !== '' && formItem.counted_qty !== null
|
||||
? (parseFloat(formItem.counted_qty) - item.system_qty)
|
||||
: 0;
|
||||
const hasDiff = Math.abs(diff) > 0.0001;
|
||||
|
||||
return (
|
||||
<TableRow key={item.id} className={hasDiff && formItem.counted_qty !== '' ? "bg-red-50/30" : ""}>
|
||||
<TableCell className="text-gray-500 font-medium text-center">
|
||||
{index + 1}
|
||||
</TableCell>
|
||||
<TableCell className="py-3">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-gray-900">{item.product_name}</span>
|
||||
<span className="text-xs text-gray-500 font-mono">{item.product_code}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm font-mono">{item.batch_number || '-'}</TableCell>
|
||||
<TableCell className="text-right font-medium">{item.system_qty.toFixed(2)}</TableCell>
|
||||
<TableCell className="text-right px-1 py-3">
|
||||
{isCompleted ? (
|
||||
<span className="font-semibold mr-2">{item.counted_qty}</span>
|
||||
) : (
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={formItem.counted_qty ?? ''}
|
||||
onChange={(e) => updateItem(index, 'counted_qty', e.target.value)}
|
||||
onWheel={(e: any) => e.target.blur()}
|
||||
disabled={processing}
|
||||
className="h-9 text-right font-medium focus:ring-primary-main"
|
||||
placeholder="盤點..."
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<span className={`font-bold ${!hasDiff
|
||||
? 'text-gray-400'
|
||||
: diff > 0
|
||||
? 'text-green-600'
|
||||
: 'text-red-600'
|
||||
}`}>
|
||||
{formItem.counted_qty !== '' && formItem.counted_qty !== null
|
||||
? diff.toFixed(2)
|
||||
: '-'}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-gray-500">{item.unit || item.unit_name}</TableCell>
|
||||
<TableCell className="px-1">
|
||||
{isCompleted ? (
|
||||
<span className="text-sm text-gray-600">{item.notes}</span>
|
||||
) : (
|
||||
<Input
|
||||
value={formItem.notes}
|
||||
onChange={(e) => updateItem(index, 'notes', e.target.value)}
|
||||
disabled={processing}
|
||||
className="h-9 text-sm"
|
||||
placeholder="備註..."
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
{!isCompleted && (
|
||||
<TableCell className="text-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 text-red-400 hover:text-red-600 hover:bg-red-50 p-0"
|
||||
onClick={() => {
|
||||
updateItem(index, 'counted_qty', '');
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
|
||||
</AuthenticatedLayout >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -184,10 +184,10 @@ export default function Index({ auth, orders, warehouses, filters }) {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreateOpen(false)}>取消</Button>
|
||||
<Button onClick={handleCreate} disabled={creating || !sourceWarehouseId || !targetWarehouseId}>
|
||||
<Button variant="outline" className="button-outlined-primary" onClick={() => setIsCreateOpen(false)}>取消</Button>
|
||||
<Button onClick={handleCreate} className="button-filled-primary" disabled={creating || !sourceWarehouseId || !targetWarehouseId}>
|
||||
{creating && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
建立草稿
|
||||
新增
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
|
||||
import { Head, router, usePage } from "@inertiajs/react";
|
||||
import { Head, router, usePage, Link } from "@inertiajs/react";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Label } from "@/Components/ui/label";
|
||||
@@ -132,32 +132,6 @@ export default function Show({ auth, order }) {
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
user={auth.user}
|
||||
header={
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="font-semibold text-xl text-gray-800 leading-tight flex items-center gap-2">
|
||||
<ArrowLeft className="h-5 w-5 cursor-pointer" onClick={() => router.visit(route('inventory.transfer.index'))} />
|
||||
調撥單詳情 ({order.doc_no})
|
||||
</h2>
|
||||
<div className="flex gap-2">
|
||||
{!isReadOnly && (
|
||||
<>
|
||||
<Button variant="destructive" onClick={handleDelete}>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
刪除
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleSave} disabled={isSaving}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
儲存草稿
|
||||
</Button>
|
||||
<Button onClick={handlePost} disabled={items.length === 0}>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
確認過帳
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
breadcrumbs={[
|
||||
{ label: '首頁', href: '/' },
|
||||
{ label: '庫存調撥', href: route('inventory.transfer.index') },
|
||||
@@ -166,8 +140,44 @@ export default function Show({ auth, order }) {
|
||||
>
|
||||
<Head title={`調撥單 ${order.doc_no}`} />
|
||||
|
||||
<div className="py-12">
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<div className="container mx-auto p-6 max-w-7xl">
|
||||
<div className="mb-6">
|
||||
<Link href={route('inventory.transfer.index')}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-2 button-outlined-primary mb-6"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
返回調撥單列表
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="font-bold text-2xl text-gray-800 leading-tight flex items-center gap-2">
|
||||
調撥單詳情 ({order.doc_no})
|
||||
</h2>
|
||||
<div className="flex gap-2">
|
||||
{!isReadOnly && (
|
||||
<>
|
||||
<Button variant="destructive" onClick={handleDelete}>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
刪除
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleSave} disabled={isSaving}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
儲存草稿
|
||||
</Button>
|
||||
<Button onClick={handlePost} disabled={items.length === 0}>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
確認過帳
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Header Info */}
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-100 grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div>
|
||||
|
||||
5
resources/js/ziggy.js
Normal file
5
resources/js/ziggy.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user