Files
star-erp/app/Modules/Inventory/Controllers/ProductController.php
sky121113 106de4e945
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 53s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
feat: 修正庫存與撥補單邏輯並整合文件
1. 修復倉庫統計數據加總與樣式。
2. 修正可用庫存計算邏輯(排除不可銷售倉庫)。
3. 撥補單商品列表加入批號與效期顯示。
4. 修正撥補單儲存邏輯以支援精確批號轉移。
5. 整合 FEATURES.md 至 README.md。
2026-01-26 14:59:24 +08:00

185 lines
7.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Modules\Inventory\Controllers;
use App\Http\Controllers\Controller;
use App\Modules\Inventory\Models\Product;
use App\Modules\Inventory\Models\Unit;
use App\Modules\Inventory\Models\Category;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class ProductController extends Controller
{
/**
* 顯示資源列表。
*/
public function index(Request $request): Response
{
$query = Product::with(['category', 'baseUnit', 'largeUnit', 'purchaseUnit']);
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('code', 'like', "%{$search}%")
->orWhere('brand', 'like', "%{$search}%");
});
}
if ($request->filled('category_id') && $request->category_id !== 'all') {
$query->where('category_id', $request->category_id);
}
$perPage = $request->input('per_page', 10);
if (!in_array($perPage, [10, 20, 50, 100])) {
$perPage = 10;
}
$sortField = $request->input('sort_field', 'id');
$sortDirection = $request->input('sort_direction', 'desc');
// 定義允許的排序欄位以防止 SQL 注入
$allowedSorts = ['id', 'code', 'name', 'category_id', 'base_unit_id', 'conversion_rate'];
if (!in_array($sortField, $allowedSorts)) {
$sortField = 'id';
}
if (!in_array(strtolower($sortDirection), ['asc', 'desc'])) {
$sortDirection = 'desc';
}
// 如果需要,分別處理關聯排序(分類名稱),或簡單的 join
if ($sortField === 'category_id') {
// 加入分類以便按名稱排序?還是僅按 ID
// 簡單方法:目前按 ID 排序,如果使用者想要按名稱排序則 join。
// 先假設標準欄位排序。
$query->orderBy('category_id', $sortDirection);
} else {
$query->orderBy($sortField, $sortDirection);
}
$products = $query->paginate($perPage)->withQueryString();
$products->getCollection()->transform(function ($product) {
return (object) [
'id' => (string) $product->id,
'code' => $product->code,
'name' => $product->name,
'categoryId' => $product->category_id,
'category' => $product->category ? (object) [
'id' => $product->category->id,
'name' => $product->category->name,
] : null,
'brand' => $product->brand,
'specification' => $product->specification,
'baseUnitId' => $product->base_unit_id,
'baseUnit' => $product->baseUnit ? (object) [
'id' => $product->baseUnit->id,
'name' => $product->baseUnit->name,
] : null,
'largeUnitId' => $product->large_unit_id,
'largeUnit' => $product->largeUnit ? (object) [
'id' => $product->largeUnit->id,
'name' => $product->largeUnit->name,
] : null,
'purchaseUnitId' => $product->purchase_unit_id,
'purchaseUnit' => $product->purchaseUnit ? (object) [
'id' => $product->purchaseUnit->id,
'name' => $product->purchaseUnit->name,
] : null,
'conversionRate' => (float) $product->conversion_rate,
];
});
$categories = Category::where('is_active', true)->get();
return Inertia::render('Product/Index', [
'products' => $products,
'categories' => Category::where('is_active', true)->get()->map(fn($c) => (object)['id' => $c->id, 'name' => $c->name]),
'units' => Unit::all()->map(fn($u) => (object)['id' => (string) $u->id, 'name' => $u->name, 'code' => $u->code]),
'filters' => $request->only(['search', 'category_id', 'per_page', 'sort_field', 'sort_direction']),
]);
}
/**
* 將新建立的資源儲存到儲存體中。
*/
public function store(Request $request)
{
$validated = $request->validate([
'code' => 'required|string|max:2|unique:products,code',
'name' => 'required|string|max:255',
'category_id' => 'required|exists:categories,id',
'brand' => 'nullable|string|max:255',
'specification' => 'nullable|string',
'base_unit_id' => 'required|exists:units,id',
'large_unit_id' => 'nullable|exists:units,id',
'conversion_rate' => 'required_with:large_unit_id|nullable|numeric|min:0.0001',
'purchase_unit_id' => 'nullable|exists:units,id',
], [
'code.required' => '商品代號為必填',
'code.max' => '商品代號最多 2 碼',
'code.unique' => '商品代號已存在',
'name.required' => '商品名稱為必填',
'category_id.required' => '請選擇分類',
'category_id.exists' => '所選分類不存在',
'base_unit_id.required' => '基本庫存單位為必填',
'base_unit_id.exists' => '所選基本單位不存在',
'conversion_rate.required_with' => '填寫大單位時,換算率為必填',
'conversion_rate.numeric' => '換算率必須為數字',
'conversion_rate.min' => '換算率最小為 0.0001',
]);
$product = Product::create($validated);
return redirect()->back()->with('success', '商品已建立');
}
/**
* 更新儲存體中的指定資源。
*/
public function update(Request $request, Product $product)
{
$validated = $request->validate([
'code' => 'required|string|max:2|unique:products,code,' . $product->id,
'name' => 'required|string|max:255',
'category_id' => 'required|exists:categories,id',
'brand' => 'nullable|string|max:255',
'specification' => 'nullable|string',
'base_unit_id' => 'required|exists:units,id',
'large_unit_id' => 'nullable|exists:units,id',
'conversion_rate' => 'required_with:large_unit_id|nullable|numeric|min:0.0001',
'purchase_unit_id' => 'nullable|exists:units,id',
], [
'code.required' => '商品代號為必填',
'code.max' => '商品代號最多 2 碼',
'code.unique' => '商品代號已存在',
'name.required' => '商品名稱為必填',
'category_id.required' => '請選擇分類',
'category_id.exists' => '所選分類不存在',
'base_unit_id.required' => '基本庫存單位為必填',
'base_unit_id.exists' => '所選基本單位不存在',
'conversion_rate.required_with' => '填寫大單位時,換算率為必填',
'conversion_rate.numeric' => '換算率必須為數字',
'conversion_rate.min' => '換算率最小為 0.0001',
]);
$product->update($validated);
return redirect()->back()->with('success', '商品已更新');
}
/**
* 從儲存體中移除指定資源。
*/
public function destroy(Product $product)
{
$product->delete();
return redirect()->back()->with('success', '商品已刪除');
}
}