refactor: 重構 VendorProduct API 與新增進貨單重複檢查前端邏輯
1. 將 VendorProductController 中的 Eloquent 關聯操作改為透過 ProcurementService 使用 DB 操作,解除跨模組 Model 直接依賴。 2. ProcurementService 加入 vendor product 的資料存取方法。 3. 進貨單建立前端 (GoodsReceipt/Create.tsx) 新增重複進貨檢查與警告對話框邏輯。
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user