2025-12-30 15:03:19 +08:00
|
|
|
<?php
|
|
|
|
|
|
2026-01-26 10:37:47 +08:00
|
|
|
namespace App\Modules\Inventory\Controllers;
|
2025-12-30 15:03:19 +08:00
|
|
|
|
2026-01-26 10:37:47 +08:00
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
|
|
|
|
|
use App\Modules\Inventory\Models\Inventory;
|
|
|
|
|
use App\Modules\Inventory\Models\Warehouse;
|
2025-12-30 15:03:19 +08:00
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
|
|
|
|
|
|
class TransferOrderController extends Controller
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 儲存撥補單(建立調撥單並執行庫存轉移)
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'sourceWarehouseId' => 'required|exists:warehouses,id',
|
|
|
|
|
'targetWarehouseId' => 'required|exists:warehouses,id|different:sourceWarehouseId',
|
|
|
|
|
'productId' => 'required|exists:products,id',
|
|
|
|
|
'quantity' => 'required|numeric|min:0.01',
|
|
|
|
|
'transferDate' => 'required|date',
|
|
|
|
|
'status' => 'required|in:待處理,處理中,已完成,已取消', // 目前僅支援立即完成或單純記錄
|
|
|
|
|
'notes' => 'nullable|string',
|
|
|
|
|
'batchNumber' => 'nullable|string', // 暫時接收,雖然 DB 可能沒存
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return DB::transaction(function () use ($validated) {
|
2026-01-26 14:59:24 +08:00
|
|
|
// 1. 檢查來源倉庫庫存 (精確匹配產品與批號)
|
2025-12-30 15:03:19 +08:00
|
|
|
$sourceInventory = Inventory::where('warehouse_id', $validated['sourceWarehouseId'])
|
|
|
|
|
->where('product_id', $validated['productId'])
|
2026-01-26 14:59:24 +08:00
|
|
|
->where('batch_number', $validated['batchNumber'])
|
2025-12-30 15:03:19 +08:00
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if (!$sourceInventory || $sourceInventory->quantity < $validated['quantity']) {
|
|
|
|
|
throw ValidationException::withMessages([
|
2026-01-26 14:59:24 +08:00
|
|
|
'quantity' => ['來源倉庫指定批號庫存不足'],
|
2025-12-30 15:03:19 +08:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 14:59:24 +08:00
|
|
|
// 2. 獲取或建立目標倉庫庫存 (精確匹配產品與批號,並繼承效期與品質狀態)
|
2025-12-30 15:03:19 +08:00
|
|
|
$targetInventory = Inventory::firstOrCreate(
|
|
|
|
|
[
|
|
|
|
|
'warehouse_id' => $validated['targetWarehouseId'],
|
|
|
|
|
'product_id' => $validated['productId'],
|
2026-01-26 14:59:24 +08:00
|
|
|
'batch_number' => $validated['batchNumber'],
|
2025-12-30 15:03:19 +08:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'quantity' => 0,
|
2026-01-26 17:27:34 +08:00
|
|
|
'unit_cost' => $sourceInventory->unit_cost, // 繼承成本
|
|
|
|
|
'total_value' => 0,
|
2026-01-26 14:59:24 +08:00
|
|
|
'expiry_date' => $sourceInventory->expiry_date,
|
|
|
|
|
'quality_status' => $sourceInventory->quality_status,
|
|
|
|
|
'origin_country' => $sourceInventory->origin_country,
|
2025-12-30 15:03:19 +08:00
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$sourceWarehouse = Warehouse::find($validated['sourceWarehouseId']);
|
|
|
|
|
$targetWarehouse = Warehouse::find($validated['targetWarehouseId']);
|
|
|
|
|
|
|
|
|
|
// 3. 執行庫存轉移 (扣除來源)
|
|
|
|
|
$oldSourceQty = $sourceInventory->quantity;
|
|
|
|
|
$newSourceQty = $oldSourceQty - $validated['quantity'];
|
2026-01-19 11:47:10 +08:00
|
|
|
|
|
|
|
|
// 設定活動紀錄原因
|
|
|
|
|
$sourceInventory->activityLogReason = "撥補出庫 至 {$targetWarehouse->name}";
|
2026-01-26 17:27:34 +08:00
|
|
|
$sourceInventory->quantity = $newSourceQty;
|
|
|
|
|
$sourceInventory->total_value = $sourceInventory->quantity * $sourceInventory->unit_cost; // 更新總值
|
|
|
|
|
$sourceInventory->save();
|
2025-12-30 15:03:19 +08:00
|
|
|
|
|
|
|
|
// 記錄來源異動
|
|
|
|
|
$sourceInventory->transactions()->create([
|
|
|
|
|
'type' => '撥補出庫',
|
|
|
|
|
'quantity' => -$validated['quantity'],
|
2026-01-26 17:27:34 +08:00
|
|
|
'unit_cost' => $sourceInventory->unit_cost, // 記錄
|
2025-12-30 15:03:19 +08:00
|
|
|
'balance_before' => $oldSourceQty,
|
|
|
|
|
'balance_after' => $newSourceQty,
|
|
|
|
|
'reason' => "撥補至 {$targetWarehouse->name}" . ($validated['notes'] ? " ({$validated['notes']})" : ""),
|
|
|
|
|
'actual_time' => $validated['transferDate'],
|
|
|
|
|
'user_id' => auth()->id(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 4. 執行庫存轉移 (增加目標)
|
|
|
|
|
$oldTargetQty = $targetInventory->quantity;
|
|
|
|
|
$newTargetQty = $oldTargetQty + $validated['quantity'];
|
2026-01-19 11:47:10 +08:00
|
|
|
|
|
|
|
|
// 設定活動紀錄原因
|
|
|
|
|
$targetInventory->activityLogReason = "撥補入庫 來自 {$sourceWarehouse->name}";
|
2026-01-26 17:27:34 +08:00
|
|
|
// 確保目標庫存也有成本 (如果是繼承來的)
|
|
|
|
|
if ($targetInventory->unit_cost == 0 && $sourceInventory->unit_cost > 0) {
|
|
|
|
|
$targetInventory->unit_cost = $sourceInventory->unit_cost;
|
|
|
|
|
}
|
|
|
|
|
$targetInventory->quantity = $newTargetQty;
|
|
|
|
|
$targetInventory->total_value = $targetInventory->quantity * $targetInventory->unit_cost; // 更新總值
|
|
|
|
|
$targetInventory->save();
|
2025-12-30 15:03:19 +08:00
|
|
|
|
|
|
|
|
// 記錄目標異動
|
|
|
|
|
$targetInventory->transactions()->create([
|
|
|
|
|
'type' => '撥補入庫',
|
|
|
|
|
'quantity' => $validated['quantity'],
|
2026-01-26 17:27:34 +08:00
|
|
|
'unit_cost' => $targetInventory->unit_cost, // 記錄
|
2025-12-30 15:03:19 +08:00
|
|
|
'balance_before' => $oldTargetQty,
|
|
|
|
|
'balance_after' => $newTargetQty,
|
|
|
|
|
'reason' => "來自 {$sourceWarehouse->name} 的撥補" . ($validated['notes'] ? " ({$validated['notes']})" : ""),
|
|
|
|
|
'actual_time' => $validated['transferDate'],
|
|
|
|
|
'user_id' => auth()->id(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// TODO: 未來若有獨立的 TransferOrder 模型,可在此建立紀錄
|
|
|
|
|
|
|
|
|
|
return redirect()->back()->with('success', '撥補單已建立且庫存已轉移');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 獲取特定倉庫的庫存列表 (API)
|
|
|
|
|
*/
|
|
|
|
|
public function getWarehouseInventories(Warehouse $warehouse)
|
|
|
|
|
{
|
|
|
|
|
$inventories = $warehouse->inventories()
|
2026-01-08 16:32:10 +08:00
|
|
|
->with(['product.baseUnit', 'product.category'])
|
2025-12-30 15:03:19 +08:00
|
|
|
->where('quantity', '>', 0) // 只回傳有庫存的
|
|
|
|
|
->get()
|
|
|
|
|
->map(function ($inv) {
|
|
|
|
|
return [
|
2026-01-26 14:59:24 +08:00
|
|
|
'product_id' => (string) $inv->product_id,
|
|
|
|
|
'product_name' => $inv->product->name,
|
|
|
|
|
'batch_number' => $inv->batch_number,
|
|
|
|
|
'quantity' => (float) $inv->quantity,
|
2026-01-26 17:27:34 +08:00
|
|
|
'unit_cost' => (float) $inv->unit_cost, // 新增
|
|
|
|
|
'total_value' => (float) $inv->total_value, // 新增
|
2026-01-26 14:59:24 +08:00
|
|
|
'unit_name' => $inv->product->baseUnit?->name ?? '個',
|
|
|
|
|
'expiry_date' => $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null,
|
2025-12-30 15:03:19 +08:00
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return response()->json($inventories);
|
|
|
|
|
}
|
|
|
|
|
}
|