feat(inventory): 支援庫存新增不使用批號模式與自動累加邏輯
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
@@ -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,50 +592,55 @@ 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
|
value={item.originCountry || ""}
|
||||||
value={item.originCountry || ""}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
const val = e.target.value.toUpperCase().slice(0, 2);
|
||||||
const val = e.target.value.toUpperCase().slice(0, 2);
|
handleUpdateItem(item.tempId, { originCountry: val });
|
||||||
handleUpdateItem(item.tempId, { originCountry: val });
|
}}
|
||||||
}}
|
maxLength={2}
|
||||||
maxLength={2}
|
placeholder="產地"
|
||||||
placeholder="產地"
|
className="h-8 text-xs text-center border-gray-300"
|
||||||
className="h-8 text-xs text-center border-gray-300"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex-[3] text-xs bg-primary-50/50 text-primary-main px-2 py-1 rounded border border-primary-200/50 font-mono overflow-hidden whitespace-nowrap">
|
|
||||||
{getBatchPreview(item.productId, product?.code, item.originCountry || 'TW', inboundDate)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* 新增效期輸入 (在新增批號模式下) */}
|
<div className="flex-[3] text-xs bg-primary-50/50 text-primary-main px-2 py-1 rounded border border-primary-200/50 font-mono overflow-hidden whitespace-nowrap">
|
||||||
<div className="mt-2 flex items-center gap-2">
|
{getBatchPreview(item.productId, product?.code, item.originCountry || 'TW', inboundDate)}
|
||||||
<span className="text-xs text-gray-500 whitespace-nowrap">效期:</span>
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<Calendar className="absolute left-2.5 top-2.5 h-3.5 w-3.5 text-gray-400 pointer-events-none" />
|
|
||||||
<Input
|
|
||||||
type="date"
|
|
||||||
value={item.expiryDate || ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleUpdateItem(item.tempId, {
|
|
||||||
expiryDate: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="h-8 pl-8 text-xs border-gray-300 w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 新增效期輸入 (僅在建立新批號模式下) */}
|
||||||
|
{item.batchMode === 'new' && (
|
||||||
|
<div className="mt-2 flex items-center gap-2">
|
||||||
|
<span className="text-xs text-gray-500 whitespace-nowrap">效期:</span>
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<Calendar className="absolute left-2.5 top-2.5 h-3.5 w-3.5 text-gray-400 pointer-events-none" />
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={item.expiryDate || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdateItem(item.tempId, {
|
||||||
|
expiryDate: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="h-8 pl-8 text-xs border-gray-300 w-full"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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; // 新增產地
|
||||||
|
|||||||
Reference in New Issue
Block a user