1. 將 VendorProductController 中的 Eloquent 關聯操作改為透過 ProcurementService 使用 DB 操作,解除跨模組 Model 直接依賴。 2. ProcurementService 加入 vendor product 的資料存取方法。 3. 進貨單建立前端 (GoodsReceipt/Create.tsx) 新增重複進貨檢查與警告對話框邏輯。
177 lines
10 KiB
TypeScript
177 lines
10 KiB
TypeScript
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogFooter,
|
||
} from "@/Components/ui/dialog";
|
||
import { Button } from "@/Components/ui/button";
|
||
import { AlertTriangle, ArrowRight } from "lucide-react";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/Components/ui/table";
|
||
import { StatusBadge } from "@/Components/shared/StatusBadge";
|
||
|
||
interface DuplicateWarningDialogProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
onConfirm: () => void;
|
||
warnings: any[];
|
||
processing?: boolean;
|
||
}
|
||
|
||
export function DuplicateWarningDialog({ open, onClose, onConfirm, warnings, processing }: DuplicateWarningDialogProps) {
|
||
return (
|
||
<Dialog open={open} onOpenChange={(val) => !val && onClose()}>
|
||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<div className="flex items-center gap-2 text-amber-600 mb-2">
|
||
<AlertTriangle className="h-6 w-6" />
|
||
<DialogTitle className="text-xl font-bold">偵測到疑似重複進貨</DialogTitle>
|
||
</div>
|
||
<p className="text-gray-500">
|
||
系統偵測到目前填寫的內容與現有紀錄高度相似,請確認是否仍要繼續建立此單據?
|
||
</p>
|
||
</DialogHeader>
|
||
|
||
<div className="py-4 space-y-6">
|
||
{warnings.map((warning, idx) => (
|
||
<div key={idx} className={`p-4 rounded-lg border ${warning.level === 'high' ? 'bg-red-50 border-red-100' : 'bg-amber-50 border-amber-100'}`}>
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<div className={`w-2 h-2 rounded-full ${warning.level === 'high' ? 'bg-red-500' : 'bg-amber-500'}`} />
|
||
<h4 className={`font-bold ${warning.level === 'high' ? 'text-red-900' : 'text-amber-900'}`}>
|
||
{warning.title}
|
||
</h4>
|
||
</div>
|
||
<p className="text-sm text-gray-700 mb-4">{warning.message}</p>
|
||
|
||
{/* Same PO Warning Details */}
|
||
{warning.type === 'same_po' && warning.related_receipts && (
|
||
<div className="bg-white rounded border overflow-hidden">
|
||
<Table>
|
||
<TableHeader className="bg-gray-50">
|
||
<TableRow>
|
||
<TableHead className="text-xs">進貨單號</TableHead>
|
||
<TableHead className="text-xs">日期</TableHead>
|
||
<TableHead className="text-xs">狀態</TableHead>
|
||
<TableHead className="text-xs text-center">品項</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody className="text-xs">
|
||
{warning.related_receipts.map((r: any) => (
|
||
<TableRow key={r.id}>
|
||
<TableCell className="font-medium text-blue-600">{r.code}</TableCell>
|
||
<TableCell>{r.received_date}</TableCell>
|
||
<TableCell>
|
||
<StatusBadge variant="neutral">{r.status}</StatusBadge>
|
||
</TableCell>
|
||
<TableCell className="text-center">{r.item_count} 項</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
)}
|
||
|
||
{/* Recent Products Warning Details */}
|
||
{warning.type === 'recent_duplicate_product' && warning.duplicated_items && (
|
||
<div className="bg-white rounded border overflow-hidden">
|
||
<Table>
|
||
<TableHeader className="bg-gray-50">
|
||
<TableRow>
|
||
<TableHead className="text-xs">商品</TableHead>
|
||
<TableHead className="text-xs">上次日期 / 單號</TableHead>
|
||
<TableHead className="text-xs text-right">上次數量</TableHead>
|
||
<TableHead className="text-xs text-right">本次數量</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody className="text-xs">
|
||
{warning.duplicated_items.map((item: any, i: number) => (
|
||
<TableRow key={i}>
|
||
<TableCell>
|
||
<div>{item.product_name}</div>
|
||
<div className="text-[10px] text-gray-400">{item.product_id}</div>
|
||
</TableCell>
|
||
<TableCell>
|
||
<div>{item.last_receipt_date}</div>
|
||
<div className="text-[10px] text-gray-400">{item.last_receipt_code}</div>
|
||
</TableCell>
|
||
<TableCell className="text-right">{item.last_quantity}</TableCell>
|
||
<TableCell className="text-right font-bold flex items-center justify-end gap-1">
|
||
<ArrowRight className="h-3 w-3 text-gray-300" />
|
||
{item.current_quantity}
|
||
{item.is_high_risk && <span className="text-[10px] bg-red-100 text-red-600 px-1 rounded">數量相同</span>}
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
)}
|
||
|
||
{/* Stale Price Warning Details */}
|
||
{warning.type === 'stale_price' && warning.stale_items && (
|
||
<div className="bg-white rounded border overflow-hidden">
|
||
<Table>
|
||
<TableHeader className="bg-gray-50">
|
||
<TableRow>
|
||
<TableHead className="text-xs">商品</TableHead>
|
||
<TableHead className="text-xs text-right">固定單價</TableHead>
|
||
<TableHead className="text-xs text-center">紀錄筆數</TableHead>
|
||
<TableHead className="text-xs">未變動期間</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody className="text-xs">
|
||
{warning.stale_items.map((item: any, i: number) => (
|
||
<TableRow key={i}>
|
||
<TableCell>
|
||
<div>{item.product_name}</div>
|
||
<div className="text-[10px] text-gray-400">{item.product_id}</div>
|
||
</TableCell>
|
||
<TableCell className="text-right font-mono font-bold text-amber-700">
|
||
${item.unit_price.toLocaleString()}
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
{item.record_count} 筆
|
||
</TableCell>
|
||
<TableCell>
|
||
<div>{item.earliest_date} ~ {item.latest_date}</div>
|
||
<div className="text-[10px] text-gray-400">最近單號:{item.latest_code}</div>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<DialogFooter className="gap-2">
|
||
<Button
|
||
variant="outline"
|
||
onClick={onClose}
|
||
disabled={processing}
|
||
className="button-outlined-primary"
|
||
>
|
||
返回修改
|
||
</Button>
|
||
<Button
|
||
onClick={onConfirm}
|
||
className="button-filled-primary"
|
||
disabled={processing}
|
||
>
|
||
{processing ? '處理中...' : '確認無重複,繼續建立'}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|