feat(inventory): 支援庫存新增不使用批號模式與自動累加邏輯

This commit is contained in:
2026-02-06 15:56:50 +08:00
parent 6c259859cf
commit 200d1989bd
3 changed files with 75 additions and 40 deletions

View File

@@ -163,7 +163,7 @@ class InventoryController extends Controller
'items.*.productId' => 'required|exists:products,id', 'items.*.productId' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.01', 'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.unit_cost' => 'nullable|numeric|min:0', // 新增成本驗證 'items.*.unit_cost' => 'nullable|numeric|min:0', // 新增成本驗證
'items.*.batchMode' => 'required|in:existing,new', 'items.*.batchMode' => 'required|in:existing,new,none',
'items.*.inventoryId' => 'required_if:items.*.batchMode,existing|nullable|exists:inventories,id', 'items.*.inventoryId' => 'required_if:items.*.batchMode,existing|nullable|exists:inventories,id',
'items.*.originCountry' => 'required_if:items.*.batchMode,new|nullable|string|max:2', 'items.*.originCountry' => 'required_if:items.*.batchMode,new|nullable|string|max:2',
'items.*.expiryDate' => 'nullable|date', 'items.*.expiryDate' => 'nullable|date',
@@ -188,6 +188,26 @@ class InventoryController extends Controller
if (isset($item['unit_cost'])) { if (isset($item['unit_cost'])) {
$inventory->unit_cost = $item['unit_cost']; $inventory->unit_cost = $item['unit_cost'];
} }
} elseif ($item['batchMode'] === 'none') {
// 模式 C不使用批號 (自動累加至 NO-BATCH)
$inventory = $warehouse->inventories()->withTrashed()->firstOrNew(
[
'product_id' => $item['productId'],
'batch_number' => 'NO-BATCH'
],
[
'quantity' => 0,
'unit_cost' => $item['unit_cost'] ?? 0,
'total_value' => 0,
'arrival_date' => $validated['inboundDate'],
'expiry_date' => null,
'origin_country' => 'TW',
]
);
if ($inventory->trashed()) {
$inventory->restore();
}
} else { } else {
// 模式 B建立新批號 // 模式 B建立新批號
$originCountry = $item['originCountry'] ?? 'TW'; $originCountry = $item['originCountry'] ?? 'TW';

View File

@@ -548,7 +548,7 @@ export default function AddInventoryPage({ warehouse, products }: Props) {
<TableCell> <TableCell>
<div className="space-y-2"> <div className="space-y-2">
<SearchableSelect <SearchableSelect
value={item.batchMode === 'new' ? 'new_batch' : (item.inventoryId || "")} value={item.batchMode === 'none' ? 'no_batch' : (item.batchMode === 'new' ? 'new_batch' : (item.inventoryId || ""))}
onValueChange={(value) => { onValueChange={(value) => {
if (value === 'new_batch') { if (value === 'new_batch') {
handleUpdateItem(item.tempId, { handleUpdateItem(item.tempId, {
@@ -557,6 +557,15 @@ export default function AddInventoryPage({ warehouse, products }: Props) {
originCountry: 'TW', originCountry: 'TW',
expiryDate: undefined expiryDate: undefined
}); });
} else if (value === 'no_batch') {
// 嘗試匹配現有的 NO-BATCH 紀錄
const existingNoBatch = (batchesCache[item.productId]?.batches || []).find(b => b.batchNumber === 'NO-BATCH');
handleUpdateItem(item.tempId, {
batchMode: 'none',
inventoryId: existingNoBatch?.inventoryId || undefined,
originCountry: 'TW',
expiryDate: undefined
});
} else { } else {
const selectedBatch = (batchesCache[item.productId]?.batches || []).find(b => b.inventoryId === value); const selectedBatch = (batchesCache[item.productId]?.batches || []).find(b => b.inventoryId === value);
handleUpdateItem(item.tempId, { handleUpdateItem(item.tempId, {
@@ -568,9 +577,10 @@ export default function AddInventoryPage({ warehouse, products }: Props) {
} }
}} }}
options={[ options={[
{ label: "📦 不使用批號 (自動累加)", value: "no_batch" },
{ label: "+ 建立新批號", value: "new_batch" }, { label: "+ 建立新批號", value: "new_batch" },
...(batchesCache[item.productId]?.batches || []).map(b => ({ ...(batchesCache[item.productId]?.batches || []).map(b => ({
label: `${b.batchNumber} - 庫存: ${b.quantity}`, label: `${b.batchNumber === 'NO-BATCH' ? '(無批號紀錄)' : b.batchNumber} - 庫存: ${b.quantity}`,
value: b.inventoryId value: b.inventoryId
})) }))
]} ]}
@@ -582,9 +592,7 @@ export default function AddInventoryPage({ warehouse, products }: Props) {
{errors[`item-${index}-batch`]} {errors[`item-${index}-batch`]}
</p> </p>
)} )}
{item.batchMode === 'new' && ( {item.batchMode === 'new' && (
<>
<div className="flex items-center gap-2 mt-2"> <div className="flex items-center gap-2 mt-2">
<div className="flex-1"> <div className="flex-1">
<Input <Input
@@ -602,7 +610,9 @@ export default function AddInventoryPage({ warehouse, products }: Props) {
{getBatchPreview(item.productId, product?.code, item.originCountry || 'TW', inboundDate)} {getBatchPreview(item.productId, product?.code, item.originCountry || 'TW', inboundDate)}
</div> </div>
</div> </div>
{/* 新增效期輸入 (在新增批號模式下) */} )}
{/* 新增效期輸入 (僅在建立新批號模式下) */}
{item.batchMode === 'new' && (
<div className="mt-2 flex items-center gap-2"> <div className="mt-2 flex items-center gap-2">
<span className="text-xs text-gray-500 whitespace-nowrap">:</span> <span className="text-xs text-gray-500 whitespace-nowrap">:</span>
<div className="relative flex-1"> <div className="relative flex-1">
@@ -619,13 +629,18 @@ export default function AddInventoryPage({ warehouse, products }: Props) {
/> />
</div> </div>
</div> </div>
</> )}
{item.batchMode === 'none' && (
<div className="mt-1 px-2 py-1 bg-amber-50 text-amber-700 text-[10px] rounded border border-amber-100 flex items-center gap-1">
<span className="shrink-0 font-bold">INFO</span>
</div>
)} )}
{item.batchMode === 'existing' && item.inventoryId && ( {item.batchMode === 'existing' && item.inventoryId && (
<div className="flex flax-col gap-1 mt-1"> <div className="flex flax-col gap-1 mt-1">
<div className="text-xs text-gray-500 font-mono"> <div className="text-xs text-gray-500 font-mono">
: {item.expiryDate || '未設定'} : {item.expiryDate || '無效期紀錄'}
</div> </div>
</div> </div>
)} )}

View File

@@ -174,7 +174,7 @@ export interface InboundItem {
largeUnit?: string; largeUnit?: string;
conversionRate?: number; conversionRate?: number;
selectedUnit?: 'base' | 'large'; selectedUnit?: 'base' | 'large';
batchMode?: 'existing' | 'new'; // 批號模式 batchMode?: 'existing' | 'new' | 'none'; // 批號模式
inventoryId?: string; // 選擇現有批號時的 ID inventoryId?: string; // 選擇現有批號時的 ID
batchNumber?: string; batchNumber?: string;
originCountry?: string; // 新增產地 originCountry?: string; // 新增產地