feat: 修正庫存與撥補單邏輯並整合文件
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 53s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

1. 修復倉庫統計數據加總與樣式。
2. 修正可用庫存計算邏輯(排除不可銷售倉庫)。
3. 撥補單商品列表加入批號與效期顯示。
4. 修正撥補單儲存邏輯以支援精確批號轉移。
5. 整合 FEATURES.md 至 README.md。
This commit is contained in:
2026-01-26 14:59:24 +08:00
parent b0848a6bb8
commit 106de4e945
81 changed files with 4118 additions and 1023 deletions

View File

@@ -95,7 +95,7 @@ export default function AddSafetyStockDialog({
// 更新商品安全庫存量
const updateQuantity = (productId: string, value: number) => {
const newQuantities = new Map(productQuantities);
newQuantities.set(productId, value); // Allow 0
newQuantities.set(productId, value); // 允許為 0
setProductQuantities(newQuantities);
};

View File

@@ -31,7 +31,7 @@ interface TransferOrderDialogProps {
onOpenChange: (open: boolean) => void;
order: TransferOrder | null;
warehouses: Warehouse[];
// inventories: WarehouseInventory[]; // Removed as we fetch from API
// inventories: WarehouseInventory[]; // 因從 API 獲取而移除
onSave: (order: Omit<TransferOrder, "id" | "createdAt" | "orderNumber">) => void;
}
@@ -41,6 +41,7 @@ interface AvailableProduct {
batchNumber: string;
availableQty: number;
unit: string;
expiryDate: string | null;
}
export default function TransferOrderDialog({
@@ -99,7 +100,15 @@ export default function TransferOrderDialog({
if (formData.sourceWarehouseId) {
axios.get(route('api.warehouses.inventories', formData.sourceWarehouseId))
.then(response => {
setAvailableProducts(response.data);
const mappedData = response.data.map((item: any) => ({
productId: item.product_id,
productName: item.product_name,
batchNumber: item.batch_number,
availableQty: item.quantity,
unit: item.unit_name,
expiryDate: item.expiry_date
}));
setAvailableProducts(mappedData);
})
.catch(error => {
console.error("Failed to fetch inventories:", error);
@@ -240,7 +249,7 @@ export default function TransferOrderDialog({
onValueChange={handleProductChange}
disabled={!formData.sourceWarehouseId || !!order}
options={availableProducts.map((product) => ({
label: `${product.productName} (庫存: ${product.availableQty} ${product.unit})`,
label: `${product.productName} | 批號: ${product.batchNumber || '-'} | 效期: ${product.expiryDate || '-'} (庫存: ${product.availableQty} ${product.unit})`,
value: `${product.productId}|||${product.batchNumber}`,
}))}
placeholder="選擇商品與批號"

View File

@@ -78,8 +78,17 @@ export default function WarehouseCard({
{warehouse.description || "無描述"}
</div>
{/* 統計區塊 - 庫存警告 */}
{/* 統計區塊 - 狀態標籤 */}
<div className="space-y-3">
{/* 銷售狀態 */}
<div className="flex items-center justify-between">
<span className="text-sm text-gray-500"></span>
<Badge variant={warehouse.is_sellable ? "default" : "secondary"} className={warehouse.is_sellable ? "bg-green-600" : "bg-gray-400"}>
{warehouse.is_sellable ? "可銷售" : "暫停銷售"}
</Badge>
</div>
{/* 低庫存警告狀態 */}
<div className="flex items-center justify-between p-3 rounded-lg bg-gray-50">
<div className="flex items-center gap-2 text-gray-600">

View File

@@ -51,11 +51,13 @@ export default function WarehouseDialog({
name: string;
address: string;
description: string;
is_sellable: boolean;
}>({
code: "",
name: "",
address: "",
description: "",
is_sellable: true,
});
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
@@ -67,6 +69,7 @@ export default function WarehouseDialog({
name: warehouse.name,
address: warehouse.address || "",
description: warehouse.description || "",
is_sellable: warehouse.is_sellable ?? true,
});
} else {
setFormData({
@@ -74,6 +77,7 @@ export default function WarehouseDialog({
name: "",
address: "",
description: "",
is_sellable: true,
});
}
}, [warehouse, open]);
@@ -148,6 +152,23 @@ export default function WarehouseDialog({
</div>
</div>
{/* 銷售設定 */}
<div className="space-y-4">
<div className="border-b pb-2">
<h4 className="text-sm text-gray-700"></h4>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="is_sellable"
className="h-4 w-4 rounded border-gray-300 text-primary-main focus:ring-primary-main"
checked={formData.is_sellable}
onChange={(e) => setFormData({ ...formData, is_sellable: e.target.checked })}
/>
<Label htmlFor="is_sellable"></Label>
</div>
</div>
{/* 區塊 B位置 */}
<div className="space-y-4">
<div className="border-b pb-2">
@@ -210,10 +231,10 @@ export default function WarehouseDialog({
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</Dialog >
{/* 刪除確認對話框 */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
< AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog} >
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
@@ -231,7 +252,7 @@ export default function WarehouseDialog({
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</AlertDialog >
</>
);
}