2025-12-30 15:03:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 庫存計算相關工具函式
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { WarehouseInventory, WarehouseStats, SafetyStockSetting, SafetyStockStatus } from "../types/warehouse";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 計算倉庫的總庫存數量
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const calculateTotalQuantity = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
warehouseId: string
|
|
|
|
|
|
): number => {
|
|
|
|
|
|
return inventories
|
|
|
|
|
|
.filter((inv) => inv.warehouseId === warehouseId)
|
|
|
|
|
|
.reduce((sum, inv) => sum + inv.quantity, 0);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 按商品分組計算總庫存
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const calculateProductTotalStock = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
productId: string
|
|
|
|
|
|
): number => {
|
|
|
|
|
|
return inventories
|
|
|
|
|
|
.filter((inv) => inv.productId === productId)
|
|
|
|
|
|
.reduce((sum, inv) => sum + inv.quantity, 0);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 計算安全庫存狀態
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const getSafetyStockStatus = (
|
|
|
|
|
|
currentStock: number,
|
|
|
|
|
|
safetyStock: number | null | undefined
|
|
|
|
|
|
): SafetyStockStatus => {
|
|
|
|
|
|
if (!safetyStock || safetyStock === 0) return "正常";
|
2026-01-22 15:39:35 +08:00
|
|
|
|
if (currentStock < safetyStock) return "低於";
|
|
|
|
|
|
return "正常";
|
2025-12-30 15:03:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 檢查商品是否低於安全庫存
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const isProductLowStock = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
safetyStockSettings: SafetyStockSetting[]
|
|
|
|
|
|
): boolean => {
|
|
|
|
|
|
const setting = safetyStockSettings.find((s) => s.productId === productId);
|
|
|
|
|
|
if (!setting) return false;
|
|
|
|
|
|
|
|
|
|
|
|
const totalStock = calculateProductTotalStock(inventories, productId);
|
|
|
|
|
|
return totalStock < setting.safetyStock;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 計算低庫存警告數量(按商品計算)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const calculateLowStockCount = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
warehouseId: string,
|
|
|
|
|
|
safetyStockSettings: SafetyStockSetting[]
|
|
|
|
|
|
): number => {
|
|
|
|
|
|
// 取得該倉庫的所有庫存
|
|
|
|
|
|
const warehouseInventories = inventories.filter(
|
|
|
|
|
|
(inv) => String(inv.warehouseId) === String(warehouseId)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 取得該倉庫的安全庫存設定
|
|
|
|
|
|
const warehouseSettings = safetyStockSettings.filter(
|
|
|
|
|
|
(s) => String(s.warehouseId) === String(warehouseId)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 計算有多少商品低於安全庫存
|
|
|
|
|
|
let lowStockCount = 0;
|
|
|
|
|
|
warehouseSettings.forEach((setting) => {
|
|
|
|
|
|
// 找出該設定對應的商品庫存
|
|
|
|
|
|
// 注意:這裡假設一個商品在同一個倉庫只有一種庫存紀錄 (warehouse_inventory table)
|
|
|
|
|
|
// 如果有批號區分,則需要加總所有該商品的數量
|
|
|
|
|
|
const productTotalStock = warehouseInventories
|
|
|
|
|
|
.filter(inv => String(inv.productId) === String(setting.productId))
|
|
|
|
|
|
.reduce((sum, inv) => sum + inv.quantity, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 只有當安全庫存設定存在時才進行判斷 (後端已過濾掉 null)
|
|
|
|
|
|
// 若設定為 0,則表示允許庫存為 0,不會觸發警告 (除非庫存為負)
|
|
|
|
|
|
if (productTotalStock < setting.safetyStock) {
|
|
|
|
|
|
lowStockCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return lowStockCount;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 計算待撥補需求數量(按商品計算)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const calculateReplenishmentNeeded = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
warehouseId: string,
|
|
|
|
|
|
safetyStockSettings: SafetyStockSetting[]
|
|
|
|
|
|
): number => {
|
|
|
|
|
|
// 取得該倉庫的所有庫存
|
|
|
|
|
|
const warehouseInventories = inventories.filter(
|
|
|
|
|
|
(inv) => inv.warehouseId === warehouseId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 取得該倉庫的安全庫存設定
|
|
|
|
|
|
const warehouseSettings = safetyStockSettings.filter(
|
|
|
|
|
|
(s) => s.warehouseId === warehouseId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 計算需要撥補的總量
|
|
|
|
|
|
let replenishmentNeeded = 0;
|
|
|
|
|
|
warehouseSettings.forEach((setting) => {
|
|
|
|
|
|
const productTotalStock = calculateProductTotalStock(
|
|
|
|
|
|
warehouseInventories,
|
|
|
|
|
|
setting.productId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (productTotalStock < setting.safetyStock) {
|
|
|
|
|
|
replenishmentNeeded += setting.safetyStock - productTotalStock;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return replenishmentNeeded;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 計算倉庫統計資訊
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const calculateWarehouseStats = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
warehouseId: string,
|
|
|
|
|
|
safetyStockSettings: SafetyStockSetting[]
|
|
|
|
|
|
): WarehouseStats => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
totalQuantity: calculateTotalQuantity(inventories, warehouseId),
|
|
|
|
|
|
lowStockCount: calculateLowStockCount(inventories, warehouseId, safetyStockSettings),
|
|
|
|
|
|
replenishmentNeeded: calculateReplenishmentNeeded(inventories, warehouseId, safetyStockSettings),
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 檢查倉庫是否有庫存警告
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const hasWarehouseWarning = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
warehouseId: string,
|
|
|
|
|
|
safetyStockSettings: SafetyStockSetting[]
|
|
|
|
|
|
): boolean => {
|
|
|
|
|
|
return calculateLowStockCount(inventories, warehouseId, safetyStockSettings) > 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 過濾倉庫的庫存
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const filterWarehouseInventories = (
|
|
|
|
|
|
inventories: WarehouseInventory[],
|
|
|
|
|
|
warehouseId: string
|
|
|
|
|
|
): WarehouseInventory[] => {
|
|
|
|
|
|
return inventories.filter((inv) => inv.warehouseId === warehouseId);
|
|
|
|
|
|
};
|