197 lines
8.4 KiB
PHP
197 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Inventory\Controllers;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
use App\Modules\Inventory\Models\Warehouse;
|
|
|
|
use Inertia\Inertia;
|
|
|
|
class WarehouseController extends Controller
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$query = Warehouse::query();
|
|
|
|
if ($request->has('search')) {
|
|
$search = $request->input('search');
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('code', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
$perPage = $request->input('per_page', 10);
|
|
if (!in_array($perPage, [10, 20, 50, 100])) {
|
|
$perPage = 10;
|
|
}
|
|
|
|
$warehouses = $query->withSum('inventories as book_stock', 'quantity') // 帳面庫存 = 所有庫存總和
|
|
->withSum('inventories as book_amount', 'total_value') // 帳面金額
|
|
->withSum(['inventories as available_stock' => function ($query) {
|
|
// 可用庫存條件
|
|
$query->where('quantity', '>', 0)
|
|
->where('quality_status', 'normal')
|
|
->whereHas('warehouse', function ($q) {
|
|
$q->where('type', '!=', \App\Enums\WarehouseType::QUARANTINE);
|
|
})
|
|
->where(function ($q) {
|
|
$q->whereNull('expiry_date')
|
|
->orWhere('expiry_date', '>=', now());
|
|
});
|
|
}], 'quantity')
|
|
->withSum(['inventories as available_amount' => function ($query) {
|
|
// 可用金額條件 (與可用庫存一致)
|
|
$query->where('quantity', '>', 0)
|
|
->where('quality_status', 'normal')
|
|
->whereHas('warehouse', function ($q) {
|
|
$q->where('type', '!=', \App\Enums\WarehouseType::QUARANTINE);
|
|
})
|
|
->where(function ($q) {
|
|
$q->whereNull('expiry_date')
|
|
->orWhere('expiry_date', '>=', now());
|
|
});
|
|
}], 'total_value')
|
|
->withSum(['inventories as abnormal_amount' => function ($query) {
|
|
$query->where('quantity', '>', 0)
|
|
->where(function ($q) {
|
|
$q->where('quality_status', '!=', 'normal')
|
|
->orWhere(function ($sq) {
|
|
$sq->whereNotNull('expiry_date')
|
|
->where('expiry_date', '<', now());
|
|
})
|
|
->orWhereHas('warehouse', function ($wq) {
|
|
$wq->where('type', \App\Enums\WarehouseType::QUARANTINE);
|
|
});
|
|
});
|
|
}], 'total_value')
|
|
->addSelect(['low_stock_count' => function ($query) {
|
|
$query->selectRaw('count(*)')
|
|
->from('warehouse_product_safety_stocks as ss')
|
|
->whereColumn('ss.warehouse_id', 'warehouses.id')
|
|
->whereRaw('(SELECT COALESCE(SUM(quantity), 0) FROM inventories WHERE warehouse_id = ss.warehouse_id AND product_id = ss.product_id) < ss.safety_stock');
|
|
}])
|
|
->orderBy('created_at', 'desc')
|
|
->paginate($perPage)
|
|
->withQueryString();
|
|
|
|
// 計算全域總計 (不分頁)
|
|
$totals = [
|
|
'available_stock' => \App\Modules\Inventory\Models\Inventory::where('quantity', '>', 0)
|
|
->where('quality_status', 'normal')
|
|
->whereHas('warehouse', function ($q) {
|
|
$q->where('type', '!=', \App\Enums\WarehouseType::QUARANTINE);
|
|
})
|
|
->where(function ($q) {
|
|
$q->whereNull('expiry_date')
|
|
->orWhere('expiry_date', '>=', now());
|
|
})->sum('quantity'),
|
|
'available_amount' => \App\Modules\Inventory\Models\Inventory::where('quantity', '>', 0)
|
|
->where('quality_status', 'normal')
|
|
->whereHas('warehouse', function ($q) {
|
|
$q->where('type', '!=', \App\Enums\WarehouseType::QUARANTINE);
|
|
})
|
|
->where(function ($q) {
|
|
$q->whereNull('expiry_date')
|
|
->orWhere('expiry_date', '>=', now());
|
|
})->sum('total_value'),
|
|
'abnormal_amount' => \App\Modules\Inventory\Models\Inventory::where('quantity', '>', 0)
|
|
->where(function ($q) {
|
|
$q->where('quality_status', '!=', 'normal')
|
|
->orWhere(function ($sq) {
|
|
$sq->whereNotNull('expiry_date')
|
|
->where('expiry_date', '<', now());
|
|
})
|
|
->orWhereHas('warehouse', function ($wq) {
|
|
$wq->where('type', \App\Enums\WarehouseType::QUARANTINE);
|
|
});
|
|
})->sum('total_value'),
|
|
'book_stock' => \App\Modules\Inventory\Models\Inventory::sum('quantity'),
|
|
'book_amount' => \App\Modules\Inventory\Models\Inventory::sum('total_value'),
|
|
];
|
|
|
|
// 取得在途倉列表供前端選擇「預設在途倉」
|
|
$transitWarehouses = Warehouse::where('type', \App\Enums\WarehouseType::TRANSIT)
|
|
->select('id', 'name', 'license_plate', 'driver_name')
|
|
->orderBy('name')
|
|
->get()
|
|
->map(fn ($w) => [
|
|
'id' => (string) $w->id,
|
|
'name' => $w->name,
|
|
'license_plate' => $w->license_plate,
|
|
'driver_name' => $w->driver_name,
|
|
]);
|
|
|
|
return Inertia::render('Warehouse/Index', [
|
|
'warehouses' => $warehouses,
|
|
'totals' => $totals,
|
|
'transitWarehouses' => $transitWarehouses,
|
|
'filters' => $request->only(['search', 'per_page']),
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'code' => 'required|string|max:20|unique:warehouses,code',
|
|
'name' => 'required|string|max:50',
|
|
'address' => 'nullable|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'type' => 'required|string',
|
|
'license_plate' => 'nullable|string|max:20',
|
|
'driver_name' => 'nullable|string|max:50',
|
|
'default_transit_warehouse_id' => 'nullable|exists:warehouses,id',
|
|
]);
|
|
|
|
Warehouse::create($validated);
|
|
|
|
return redirect()->back()->with('success', '倉庫已建立');
|
|
}
|
|
|
|
public function update(Request $request, Warehouse $warehouse)
|
|
{
|
|
$validated = $request->validate([
|
|
'code' => 'required|string|max:20|unique:warehouses,code,' . $warehouse->id,
|
|
'name' => 'required|string|max:50',
|
|
'address' => 'nullable|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'type' => 'required|string',
|
|
'license_plate' => 'nullable|string|max:20',
|
|
'driver_name' => 'nullable|string|max:50',
|
|
'default_transit_warehouse_id' => 'nullable|exists:warehouses,id',
|
|
]);
|
|
|
|
$warehouse->update($validated);
|
|
|
|
return redirect()->back()->with('success', '倉庫資訊已更新');
|
|
}
|
|
|
|
public function destroy(Warehouse $warehouse)
|
|
{
|
|
// 檢查是否有相關聯的採購單 (跨模組檢查,不使用模型關聯以符合解耦規範)
|
|
$hasPurchaseOrders = \App\Modules\Procurement\Models\PurchaseOrder::where('warehouse_id', $warehouse->id)->exists();
|
|
if ($hasPurchaseOrders) {
|
|
return redirect()->back()->with('error', '無法刪除:該倉庫有相關聯的採購單,請先處理採購單。');
|
|
}
|
|
|
|
\Illuminate\Support\Facades\DB::transaction(function () use ($warehouse) {
|
|
// 刪除庫存異動紀錄 (透過庫存關聯)
|
|
foreach ($warehouse->inventories as $inventory) {
|
|
// 刪除該庫存的所有異動紀錄
|
|
$inventory->transactions()->delete();
|
|
}
|
|
|
|
// 刪除庫存項目
|
|
$warehouse->inventories()->delete();
|
|
|
|
// 刪除倉庫
|
|
$warehouse->delete();
|
|
});
|
|
|
|
return redirect()->back()->with('success', '倉庫及其庫存與紀錄已刪除');
|
|
}
|
|
}
|