feat: 倉庫業務屬性、庫存成本追蹤與採購單功能更新
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 58s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

1. 倉庫管理:新增業務類型 (Owned/External/Customer) 與車牌資訊與司機欄位。
2. 庫存管理:實作成本追蹤 (unit_cost, total_value),更新列表與撥補單顯示。
3. 採購單:新增採購日期 (order_date),調整欄位名稱與順序。
4. 前端優化:更新相關 TS Type 定義與 UI 顯示。
This commit is contained in:
2026-01-26 17:27:34 +08:00
parent 106de4e945
commit ac6a81b3d2
24 changed files with 429 additions and 130 deletions

View File

@@ -1,6 +1,6 @@
/**
* 倉庫對話框元件
* 重構後:加入驗證邏輯
* 重構後:加入驗證邏輯與業務類型支援
*/
import { useEffect, useState } from "react";
@@ -27,9 +27,10 @@ import { Label } from "@/Components/ui/label";
import { Textarea } from "@/Components/ui/textarea";
import { Button } from "@/Components/ui/button";
import { Trash2 } from "lucide-react";
import { Warehouse } from "@/types/warehouse";
import { Warehouse, WarehouseType } from "@/types/warehouse";
import { validateWarehouse } from "@/utils/validation";
import { toast } from "sonner";
import { SearchableSelect } from "@/Components/ui/searchable-select";
interface WarehouseDialogProps {
open: boolean;
@@ -39,6 +40,15 @@ interface WarehouseDialogProps {
onDelete?: (warehouseId: string) => void;
}
const WAREHOUSE_TYPE_OPTIONS: { label: string; value: WarehouseType }[] = [
{ label: "標準倉 (總倉)", value: "standard" },
{ label: "生產倉 (廚房/加工)", value: "production" },
{ label: "門市倉 (前台销售)", value: "retail" },
{ label: "販賣機 (IoT設備)", value: "vending" },
{ label: "在途倉 (物流車)", value: "transit" },
{ label: "瑕疵倉 (報廢/檢驗)", value: "quarantine" },
];
export default function WarehouseDialog({
open,
onOpenChange,
@@ -51,13 +61,19 @@ export default function WarehouseDialog({
name: string;
address: string;
description: string;
type: WarehouseType;
is_sellable: boolean;
license_plate: string;
driver_name: string;
}>({
code: "",
name: "",
address: "",
description: "",
type: "standard",
is_sellable: true,
license_plate: "",
driver_name: "",
});
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
@@ -69,7 +85,10 @@ export default function WarehouseDialog({
name: warehouse.name,
address: warehouse.address || "",
description: warehouse.description || "",
type: warehouse.type || "standard",
is_sellable: warehouse.is_sellable ?? true,
license_plate: warehouse.license_plate || "",
driver_name: warehouse.driver_name || "",
});
} else {
setFormData({
@@ -77,7 +96,10 @@ export default function WarehouseDialog({
name: "",
address: "",
description: "",
type: "standard",
is_sellable: true,
license_plate: "",
driver_name: "",
});
}
}, [warehouse, open]);
@@ -136,8 +158,21 @@ export default function WarehouseDialog({
/>
</div>
{/* 倉庫名稱 */}
{/* 倉庫類型 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<SearchableSelect
value={formData.type}
onValueChange={(val) => setFormData({ ...formData, type: val as WarehouseType })}
options={WAREHOUSE_TYPE_OPTIONS}
placeholder="選擇倉庫類型"
className="h-9"
showSearch={false}
/>
</div>
{/* 倉庫名稱 */}
<div className="space-y-2 col-span-2">
<Label htmlFor="name">
<span className="text-red-500">*</span>
</Label>
@@ -147,11 +182,43 @@ export default function WarehouseDialog({
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="例:中央倉庫"
required
className="h-9"
/>
</div>
</div>
</div>
{/* 移動倉專屬資訊 */}
{formData.type === 'transit' && (
<div className="space-y-4 bg-yellow-50 p-4 rounded-lg border border-yellow-100">
<div className="border-b border-yellow-200 pb-2">
<h4 className="text-sm text-yellow-800 font-medium"> ()</h4>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="license_plate"></Label>
<Input
id="license_plate"
value={formData.license_plate}
onChange={(e) => setFormData({ ...formData, license_plate: e.target.value })}
placeholder="例ABC-1234"
className="h-9 bg-white"
/>
</div>
<div className="space-y-2">
<Label htmlFor="driver_name"></Label>
<Input
id="driver_name"
value={formData.driver_name}
onChange={(e) => setFormData({ ...formData, driver_name: e.target.value })}
placeholder="例:王小明"
className="h-9 bg-white"
/>
</div>
</div>
</div>
)}
{/* 銷售設定 */}
<div className="space-y-4">
<div className="border-b pb-2">
@@ -167,6 +234,9 @@ export default function WarehouseDialog({
/>
<Label htmlFor="is_sellable"></Label>
</div>
<p className="text-xs text-gray-500 ml-6">
POS
</p>
</div>
{/* 區塊 B位置 */}
@@ -186,6 +256,7 @@ export default function WarehouseDialog({
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
placeholder="例台北市信義區信義路五段7號"
required
className="h-9"
/>
</div>