更新採購單跟商品資料一些bug
This commit is contained in:
@@ -21,6 +21,13 @@ import {
|
||||
import { useForm } from "@inertiajs/react";
|
||||
import { toast } from "sonner";
|
||||
import type { Product, Category } from "@/Pages/Product/Index";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/Components/ui/dropdown-menu";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
interface ProductDialogProps {
|
||||
open: boolean;
|
||||
@@ -41,7 +48,7 @@ export default function ProductDialog({
|
||||
category_id: "",
|
||||
brand: "",
|
||||
specification: "",
|
||||
base_unit: "kg",
|
||||
base_unit: "公斤",
|
||||
large_unit: "",
|
||||
conversion_rate: "",
|
||||
purchase_unit: "",
|
||||
@@ -184,32 +191,34 @@ export default function ProductDialog({
|
||||
<Label htmlFor="base_unit">
|
||||
基本庫存單位 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={data.base_unit}
|
||||
onValueChange={(value) => setData("base_unit", value)}
|
||||
>
|
||||
<SelectTrigger id="base_unit" className={errors.base_unit ? "border-red-500" : ""}>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="kg">公斤 (kg)</SelectItem>
|
||||
<SelectItem value="g">公克 (g)</SelectItem>
|
||||
<SelectItem value="l">公升 (l)</SelectItem>
|
||||
<SelectItem value="ml">毫升 (ml)</SelectItem>
|
||||
<SelectItem value="個">個</SelectItem>
|
||||
<SelectItem value="支">支</SelectItem>
|
||||
<SelectItem value="包">包</SelectItem>
|
||||
<SelectItem value="罐">罐</SelectItem>
|
||||
<SelectItem value="瓶">瓶</SelectItem>
|
||||
<SelectItem value="箱">箱</SelectItem>
|
||||
<SelectItem value="袋">袋</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="base_unit"
|
||||
value={data.base_unit}
|
||||
onChange={(e) => setData("base_unit", e.target.value)}
|
||||
placeholder="可輸入或選擇..."
|
||||
className={errors.base_unit ? "border-red-500 flex-1" : "flex-1"}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="shrink-0">
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{["公斤", "公克", "公升", "毫升", "個", "支", "包", "罐", "瓶", "箱", "袋"].map((u) => (
|
||||
<DropdownMenuItem key={u} onClick={() => setData("base_unit", u)}>
|
||||
{u}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{errors.base_unit && <p className="text-sm text-red-500">{errors.base_unit}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="large_unit">大單位 (進貨單位)</Label>
|
||||
<Label htmlFor="large_unit">大單位</Label>
|
||||
<Input
|
||||
id="large_unit"
|
||||
value={data.large_unit}
|
||||
@@ -270,6 +279,6 @@ export default function ProductDialog({
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Dialog >
|
||||
);
|
||||
}
|
||||
@@ -46,11 +46,12 @@ export function PurchaseOrderItemsTable({
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-gray-50 hover:bg-gray-50">
|
||||
<TableHead className="w-[30%] text-left">商品名稱</TableHead>
|
||||
<TableHead className="w-[15%] text-left">數量</TableHead>
|
||||
<TableHead className="w-[10%] text-left">單位</TableHead>
|
||||
<TableHead className="w-[20%] text-left">預估單價</TableHead>
|
||||
<TableHead className="w-[20%] text-left">小計</TableHead>
|
||||
<TableHead className="w-[25%] text-left">商品名稱</TableHead>
|
||||
<TableHead className="w-[10%] text-left">數量</TableHead>
|
||||
<TableHead className="w-[10%] text-left">採購單位</TableHead>
|
||||
<TableHead className="w-[15%] text-left">換算基本單位</TableHead>
|
||||
<TableHead className="w-[15%] text-left">預估單價</TableHead>
|
||||
<TableHead className="w-[15%] text-left">小計</TableHead>
|
||||
{!isReadOnly && <TableHead className="w-[5%]"></TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -58,7 +59,7 @@ export function PurchaseOrderItemsTable({
|
||||
{items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={isReadOnly ? 5 : 6}
|
||||
colSpan={isReadOnly ? 6 : 7}
|
||||
className="text-center text-gray-400 py-12 italic"
|
||||
>
|
||||
{isDisabled ? "請先選擇供應商後才能新增商品" : "尚未新增任何商品項"}
|
||||
@@ -115,11 +116,20 @@ export function PurchaseOrderItemsTable({
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
{/* 單位 */}
|
||||
{/* 採購單位 */}
|
||||
<TableCell>
|
||||
<span className="text-gray-500 font-medium">{item.unit || "-"}</span>
|
||||
</TableCell>
|
||||
|
||||
{/* 換算基本單位 */}
|
||||
<TableCell>
|
||||
<span className="text-gray-500 font-medium">
|
||||
{item.conversion_rate && item.base_unit
|
||||
? `${parseFloat((item.quantity * item.conversion_rate).toFixed(2))} ${item.base_unit}`
|
||||
: "-"}
|
||||
</span>
|
||||
</TableCell>
|
||||
|
||||
{/* 單價 */}
|
||||
<TableCell className="text-left">
|
||||
{isReadOnly ? (
|
||||
@@ -135,12 +145,23 @@ export function PurchaseOrderItemsTable({
|
||||
onItemChange?.(index, "unitPrice", Number(e.target.value))
|
||||
}
|
||||
disabled={isDisabled}
|
||||
className={`h-10 text-left w-32 ${isPriceAlert(item.unitPrice, item.previousPrice)
|
||||
? "border-amber-400 bg-amber-50 focus-visible:ring-amber-500"
|
||||
: "border-gray-200"
|
||||
className={`h-10 text-left w-32 ${
|
||||
// 如果有數量但沒有單價,顯示錯誤樣式
|
||||
item.quantity > 0 && (!item.unitPrice || item.unitPrice <= 0)
|
||||
? "border-red-400 bg-red-50 focus-visible:ring-red-500"
|
||||
: isPriceAlert(item.unitPrice, item.previousPrice)
|
||||
? "border-amber-400 bg-amber-50 focus-visible:ring-amber-500"
|
||||
: "border-gray-200"
|
||||
}`}
|
||||
/>
|
||||
{isPriceAlert(item.unitPrice, item.previousPrice) && (
|
||||
{/* 錯誤提示:有數量但沒有單價 */}
|
||||
{item.quantity > 0 && (!item.unitPrice || item.unitPrice <= 0) && (
|
||||
<p className="text-[10px] text-red-600 font-medium">
|
||||
❌ 請填寫預估單價
|
||||
</p>
|
||||
)}
|
||||
{/* 價格警示:單價高於上次 */}
|
||||
{item.unitPrice > 0 && isPriceAlert(item.unitPrice, item.previousPrice) && (
|
||||
<p className="text-[10px] text-amber-600 font-medium animate-pulse">
|
||||
⚠️ 高於上次: {formatCurrency(item.previousPrice || 0)}
|
||||
</p>
|
||||
|
||||
@@ -60,8 +60,8 @@ export default function CreatePurchaseOrder({
|
||||
setStatus,
|
||||
} = usePurchaseOrderForm({ order, suppliers });
|
||||
|
||||
|
||||
const totalAmount = calculateTotalAmount(items);
|
||||
const isValid = validatePurchaseOrder(String(supplierId), expectedDate, items);
|
||||
|
||||
const handleSave = () => {
|
||||
if (!warehouseId) {
|
||||
@@ -84,9 +84,23 @@ export default function CreatePurchaseOrder({
|
||||
return;
|
||||
}
|
||||
|
||||
// 檢查是否有數量大於 0 的項目
|
||||
const itemsWithQuantity = items.filter(item => item.quantity > 0);
|
||||
if (itemsWithQuantity.length === 0) {
|
||||
toast.error("請填寫有效的採購數量(必須大於 0)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 檢查有數量的項目是否都有填寫單價
|
||||
const itemsWithoutPrice = itemsWithQuantity.filter(item => !item.unitPrice || item.unitPrice <= 0);
|
||||
if (itemsWithoutPrice.length > 0) {
|
||||
toast.error("請填寫所有商品的預估單價(必須大於 0)");
|
||||
return;
|
||||
}
|
||||
|
||||
const validItems = filterValidItems(items);
|
||||
if (validItems.length === 0) {
|
||||
toast.error("請填寫有效的採購數量(必須大於 0)");
|
||||
toast.error("請確保所有商品都有填寫數量和單價");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,7 +121,14 @@ export default function CreatePurchaseOrder({
|
||||
router.put(`/purchase-orders/${order.id}`, data, {
|
||||
onSuccess: () => toast.success("採購單已更新"),
|
||||
onError: (errors) => {
|
||||
toast.error("更新失敗,請檢查輸入內容");
|
||||
// 顯示更詳細的錯誤訊息
|
||||
if (errors.items) {
|
||||
toast.error("商品資料有誤,請檢查數量和單價是否正確填寫");
|
||||
} else if (errors.error) {
|
||||
toast.error(errors.error);
|
||||
} else {
|
||||
toast.error("更新失敗,請檢查輸入內容");
|
||||
}
|
||||
console.error(errors);
|
||||
}
|
||||
});
|
||||
@@ -115,10 +136,12 @@ export default function CreatePurchaseOrder({
|
||||
router.post("/purchase-orders", data, {
|
||||
onSuccess: () => toast.success("採購單已成功建立"),
|
||||
onError: (errors) => {
|
||||
if (errors.error) {
|
||||
if (errors.items) {
|
||||
toast.error("商品資料有誤,請檢查數量和單價是否正確填寫");
|
||||
} else if (errors.error) {
|
||||
toast.error(errors.error);
|
||||
} else {
|
||||
toast.error("建立失敗,請檢查輸入內容");
|
||||
toast.error("建立失敗,請檢查輸入內容");
|
||||
}
|
||||
console.error(errors);
|
||||
}
|
||||
@@ -127,7 +150,6 @@ export default function CreatePurchaseOrder({
|
||||
};
|
||||
|
||||
const hasSupplier = !!supplierId;
|
||||
const canSave = isValid && !!warehouseId && items.length > 0;
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout>
|
||||
|
||||
@@ -76,6 +76,9 @@ export function usePurchaseOrderForm({ order, suppliers }: UsePurchaseOrderFormP
|
||||
if (product) {
|
||||
newItems[index].productName = product.productName;
|
||||
newItems[index].unit = product.unit;
|
||||
newItems[index].base_unit = product.base_unit;
|
||||
newItems[index].purchase_unit = product.purchase_unit;
|
||||
newItems[index].conversion_rate = product.conversion_rate;
|
||||
newItems[index].unitPrice = product.lastPrice;
|
||||
newItems[index].previousPrice = product.lastPrice;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ export interface PurchaseOrderItem {
|
||||
productName: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
base_unit?: string; // 基本庫存單位
|
||||
purchase_unit?: string; // 採購單位
|
||||
conversion_rate?: number;// 換算率
|
||||
unitPrice: number;
|
||||
previousPrice?: number;
|
||||
subtotal: number;
|
||||
@@ -77,6 +80,9 @@ export interface CommonProduct {
|
||||
productId: string;
|
||||
productName: string;
|
||||
unit: string;
|
||||
base_unit?: string;
|
||||
purchase_unit?: string;
|
||||
conversion_rate?: number;
|
||||
lastPrice: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ export function validatePurchaseOrder(
|
||||
}
|
||||
|
||||
/**
|
||||
* 過濾有效項目(數量大於 0)
|
||||
* 過濾有效項目(數量和單價都必須大於 0)
|
||||
*/
|
||||
export function filterValidItems(items: PurchaseOrderItem[]): PurchaseOrderItem[] {
|
||||
return items.filter((item) => item.quantity > 0);
|
||||
return items.filter((item) => item.quantity > 0 && item.unitPrice > 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user