feat(inventory): 實作過期與瑕疵庫存總計顯示,並強化庫存明細過期提示

This commit is contained in:
2026-02-05 15:50:14 +08:00
parent ba3c10ac13
commit a518d390bd
16 changed files with 751 additions and 574 deletions

View File

@@ -113,57 +113,77 @@ class ProductController extends Controller
]);
}
/**
* 顯示建立表單。
*/
public function create(): Response
{
return Inertia::render('Product/Create', [
'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]),
]);
}
/**
* 將新建立的資源儲存到儲存體中。
*/
public function store(Request $request)
{
$validated = $request->validate([
'code' => 'required|string|min:2|max:8|unique:products,code',
'barcode' => 'required|string|unique:products,barcode',
'code' => 'nullable|unique:products,code',
'barcode' => 'nullable|unique:products,barcode',
'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',
'purchase_unit_id' => 'nullable|exists:units,id',
'conversion_rate' => 'nullable|numeric|min:0',
'location' => 'nullable|string|max:255',
'cost_price' => 'nullable|numeric|min:0',
'price' => 'nullable|numeric|min:0',
'member_price' => 'nullable|numeric|min:0',
'wholesale_price' => 'nullable|numeric|min:0',
], [
'code.required' => '商品代號為必填',
'code.max' => '商品代號最多 8 碼',
'code.min' => '商品代號最少 2 碼',
'code.unique' => '商品代號已存在',
'barcode.required' => '條碼編號為必填',
'barcode.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',
'cost_price.numeric' => '成本價必須為數字',
'cost_price.min' => '成本價不能小於 0',
'price.numeric' => '售價必須為數字',
'price.min' => '售價不能小於 0',
'member_price.numeric' => '會員價必須為數字',
'member_price.min' => '會員價不能小於 0',
'wholesale_price.numeric' => '批發價必須為數字',
'wholesale_price.min' => '批發價不能小於 0',
'is_active' => 'boolean',
]);
if (empty($validated['code'])) {
$validated['code'] = $this->generateRandomCode();
}
$product = Product::create($validated);
return redirect()->back()->with('success', '商品已建立');
return redirect()->route('products.index')->with('success', '商品已建立');
}
/**
* 顯示編輯表單。
*/
public function edit(Product $product): Response
{
return Inertia::render('Product/Edit', [
'product' => (object) [
'id' => (string) $product->id,
'code' => $product->code,
'barcode' => $product->barcode,
'name' => $product->name,
'categoryId' => $product->category_id,
'brand' => $product->brand,
'specification' => $product->specification,
'baseUnitId' => $product->base_unit_id,
'largeUnitId' => $product->large_unit_id,
'conversionRate' => (float) $product->conversion_rate,
'purchaseUnitId' => $product->purchase_unit_id,
'location' => $product->location,
'cost_price' => (float) $product->cost_price,
'price' => (float) $product->price,
'member_price' => (float) $product->member_price,
'wholesale_price' => (float) $product->wholesale_price,
],
'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]),
]);
}
/**
@@ -172,50 +192,31 @@ class ProductController extends Controller
public function update(Request $request, Product $product)
{
$validated = $request->validate([
'code' => 'required|string|min:2|max:8|unique:products,code,' . $product->id,
'barcode' => 'required|string|unique:products,barcode,' . $product->id,
'code' => 'nullable|unique:products,code,' . $product->id,
'barcode' => 'nullable|unique:products,barcode,' . $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',
'purchase_unit_id' => 'nullable|exists:units,id',
'conversion_rate' => 'nullable|numeric|min:0',
'location' => 'nullable|string|max:255',
'cost_price' => 'nullable|numeric|min:0',
'price' => 'nullable|numeric|min:0',
'member_price' => 'nullable|numeric|min:0',
'wholesale_price' => 'nullable|numeric|min:0',
], [
'code.required' => '商品代號為必填',
'code.max' => '商品代號最多 8 碼',
'code.min' => '商品代號最少 2 碼',
'code.unique' => '商品代號已存在',
'barcode.required' => '條碼編號為必填',
'barcode.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',
'cost_price.numeric' => '成本價必須為數字',
'cost_price.min' => '成本價不能小於 0',
'price.numeric' => '售價必須為數字',
'price.min' => '售價不能小於 0',
'member_price.numeric' => '會員價必須為數字',
'member_price.min' => '會員價不能小於 0',
'wholesale_price.numeric' => '批發價必須為數字',
'wholesale_price.min' => '批發價不能小於 0',
'is_active' => 'boolean',
]);
if (empty($validated['code'])) {
$validated['code'] = $this->generateRandomCode();
}
$product->update($validated);
return redirect()->back()->with('success', '商品已更新');
return redirect()->route('products.index')->with('success', '商品已更新');
}
/**
@@ -259,4 +260,22 @@ class ProductController extends Controller
return redirect()->back()->withErrors(['file' => '匯入失敗: ' . $e->getMessage()]);
}
}
/**
* 生成隨機 8 碼代號 (大寫英文+數字)
*/
private function generateRandomCode(): string
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$code = '';
do {
$code = '';
for ($i = 0; $i < 8; $i++) {
$code .= $characters[rand(0, strlen($characters) - 1)];
}
} while (Product::where('code', $code)->exists());
return $code;
}
}