Files
star-erp/app/Modules/Production/Controllers/RecipeController.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

192 lines
6.6 KiB
PHP

<?php
namespace App\Modules\Production\Controllers;
use App\Http\Controllers\Controller;
use App\Modules\Production\Models\Recipe;
use App\Modules\Production\Models\RecipeItem;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;
class RecipeController extends Controller
{
protected $inventoryService;
public function __construct(InventoryServiceInterface $inventoryService)
{
$this->inventoryService = $inventoryService;
}
/**
* 配方列表
*/
public function index(Request $request): Response
{
$query = Recipe::query();
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('code', 'like', "%{$search}%")
->orWhere('name', 'like', "%{$search}%");
$productIds = $this->inventoryService->getProductsByName($search)->pluck('id');
$q->orWhereIn('product_id', $productIds);
});
}
$query->orderBy($request->input('sort_field', 'created_at'), $request->input('sort_direction', 'desc'));
$recipes = $query->paginate($request->input('per_page', 10))->withQueryString();
// Manual Hydration
$productIds = $recipes->pluck('product_id')->unique()->filter()->toArray();
$products = $this->inventoryService->getProductsByIds($productIds)->keyBy('id');
$recipes->getCollection()->transform(function ($recipe) use ($products) {
$recipe->product = $products->get($recipe->product_id);
return $recipe;
});
return Inertia::render('Production/Recipe/Index', [
'recipes' => $recipes,
'filters' => $request->only(['search', 'per_page', 'sort_field', 'sort_direction']),
]);
}
/**
* 新增配方表單
*/
public function create(): Response
{
return Inertia::render('Production/Recipe/Create', [
'products' => $this->inventoryService->getAllProducts(),
'units' => $this->inventoryService->getUnits(),
]);
}
/**
* 儲存配方
*/
public function store(Request $request)
{
$validated = $request->validate([
'product_id' => 'required|exists:products,id',
'code' => 'required|string|max:50|unique:recipes,code',
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'yield_quantity' => 'required|numeric|min:0.01',
'items' => 'required|array|min:1',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.0001',
'items.*.unit_id' => 'nullable|exists:units,id',
'items.*.remark' => 'nullable|string',
]);
DB::transaction(function () use ($validated) {
$recipe = Recipe::create([
'product_id' => $validated['product_id'],
'code' => $validated['code'],
'name' => $validated['name'],
'description' => $validated['description'],
'yield_quantity' => $validated['yield_quantity'],
'is_active' => true,
]);
foreach ($validated['items'] as $item) {
RecipeItem::create([
'recipe_id' => $recipe->id,
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'unit_id' => $item['unit_id'],
'remark' => $item['remark'],
]);
}
});
return redirect()->route('recipes.index')->with('success', '配方已建立');
}
/**
* 編輯配方表單
*/
public function edit(Recipe $recipe): Response
{
// Hydrate Product
$recipe->product = $this->inventoryService->getProduct($recipe->product_id);
// Load items with details
$items = $recipe->items;
$productIds = $items->pluck('product_id')->unique()->toArray();
$products = $this->inventoryService->getProductsByIds($productIds)->keyBy('id');
$units = $this->inventoryService->getUnits()->keyBy('id');
foreach ($items as $item) {
$item->product = $products->get($item->product_id);
$item->unit = $units->get($item->unit_id);
}
return Inertia::render('Production/Recipe/Edit', [
'recipe' => $recipe,
'products' => $this->inventoryService->getAllProducts(),
'units' => $this->inventoryService->getUnits(),
]);
}
/**
* 更新配方
*/
public function update(Request $request, Recipe $recipe)
{
$validated = $request->validate([
'product_id' => 'required|exists:products,id',
'code' => 'required|string|max:50|unique:recipes,code,' . $recipe->id,
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'yield_quantity' => 'required|numeric|min:0.01',
'items' => 'required|array|min:1',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric|min:0.0001',
'items.*.unit_id' => 'nullable|exists:units,id',
'items.*.remark' => 'nullable|string',
]);
DB::transaction(function () use ($validated, $recipe) {
$recipe->update([
'product_id' => $validated['product_id'],
'code' => $validated['code'],
'name' => $validated['name'],
'description' => $validated['description'],
'yield_quantity' => $validated['yield_quantity'],
]);
// Sync items (Delete all and recreate)
$recipe->items()->delete();
foreach ($validated['items'] as $item) {
RecipeItem::create([
'recipe_id' => $recipe->id,
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'unit_id' => $item['unit_id'],
'remark' => $item['remark'],
]);
}
});
return redirect()->route('recipes.index')->with('success', '配方已更新');
}
/**
* 刪除配方
*/
public function destroy(Recipe $recipe)
{
$recipe->delete();
return redirect()->back()->with('success', '配方已刪除');
}
}