Files
WitShop/app/Http/Controllers/Admin/ProductController.php
T
marcoandClaude Sonnet 4.6 9dbc6e7ea1
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 7s
Release erstellen / build-release (push) Successful in 47s
Add delivery_note field to products for planned delivery info
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 07:17:40 +02:00

182 lines
6.7 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Attribute;
use App\Models\AttributeValue;
use App\Models\Category;
use App\Models\Product;
use App\Models\ProductImage;
use App\Models\ProductVariant;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ProductController extends Controller
{
public function index(Request $request)
{
$query = Product::with('category')->latest();
if ($request->filled('q')) {
$query->where('name', 'like', '%'.$request->q.'%');
}
$products = $query->paginate(25);
return view('admin.products.index', compact('products'));
}
public function create()
{
$categories = Category::orderBy('name')->get();
$attributes = Attribute::with('values')->get();
$variantsJson = '[]';
return view('admin.products.form', compact('categories', 'attributes', 'variantsJson'));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'category_id' => 'nullable|exists:categories,id',
'description' => 'nullable|string',
'base_price' => 'required|numeric|min:0',
'sale_price' => 'nullable|numeric|min:0',
'tax_rate' => 'required|numeric|in:7,19',
'weight' => 'nullable|numeric|min:0',
'active' => 'boolean',
'featured' => 'boolean',
'track_stock' => 'boolean',
'sort_order' => 'integer',
'delivery_note' => 'nullable|string|max:255',
]);
$data['slug'] = Str::slug($data['name']);
$data['active'] = $request->boolean('active');
$data['featured'] = $request->boolean('featured');
$data['track_stock'] = $request->boolean('track_stock');
$product = Product::create($data);
$this->syncImages($request, $product);
$this->syncVariants($request, $product);
return redirect()->route('admin.products.index')->with('success', 'Artikel erstellt.');
}
public function edit(Product $product)
{
$product->load('images', 'variants.attributeValues.attribute');
$categories = Category::orderBy('name')->get();
$attributes = Attribute::with('values')->get();
$variantsJson = json_encode($product->variants->map(fn($v) => [
'id' => $v->id,
'sku' => $v->sku,
'price_override' => $v->price_override,
'stock' => $v->stock,
'stock_warning' => $v->stock_warning,
'active' => (bool) $v->active,
'attribute_value_ids' => $v->attributeValues->pluck('id')->values()->all(),
]));
return view('admin.products.form', compact('product', 'categories', 'attributes', 'variantsJson'));
}
public function update(Request $request, Product $product)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'category_id' => 'nullable|exists:categories,id',
'description' => 'nullable|string',
'base_price' => 'required|numeric|min:0',
'sale_price' => 'nullable|numeric|min:0',
'tax_rate' => 'required|numeric|in:7,19',
'weight' => 'nullable|numeric|min:0',
'active' => 'boolean',
'featured' => 'boolean',
'track_stock' => 'boolean',
'sort_order' => 'integer',
'delivery_note' => 'nullable|string|max:255',
]);
$data['slug'] = Str::slug($data['name']);
$data['active'] = $request->boolean('active');
$data['featured'] = $request->boolean('featured');
$data['track_stock'] = $request->boolean('track_stock');
$product->update($data);
$this->syncImages($request, $product);
$this->syncVariants($request, $product);
return redirect()->route('admin.products.index')->with('success', 'Artikel aktualisiert.');
}
public function destroy(Product $product)
{
$product->delete();
return redirect()->route('admin.products.index')->with('success', 'Artikel gelöscht.');
}
private function syncImages(Request $request, Product $product): void
{
if ($request->hasFile('images')) {
foreach ($request->file('images') as $i => $file) {
$path = $file->store('products', 'public');
ProductImage::create(['product_id' => $product->id, 'path' => $path, 'sort_order' => $i]);
}
}
// Bild löschen
if ($request->filled('delete_images')) {
ProductImage::whereIn('id', $request->delete_images)
->where('product_id', $product->id)
->delete();
}
}
private function syncVariants(Request $request, Product $product): void
{
$variants = $request->input('variants', []);
$existingIds = [];
foreach ($variants as $v) {
if (empty($v['sku'])) continue;
$variant = isset($v['id'])
? ProductVariant::find($v['id'])
: null;
$variantData = [
'product_id' => $product->id,
'sku' => $v['sku'],
'price_override' => filled($v['price_override']) ? $v['price_override'] : null,
'stock' => (int) ($v['stock'] ?? 0),
'stock_warning' => (int) ($v['stock_warning'] ?? 5),
'active' => isset($v['active']),
];
if ($variant) {
$variant->update($variantData);
} else {
$variant = ProductVariant::create($variantData);
}
// Attributwerte synchronisieren
$valueIds = array_filter((array) ($v['attribute_value_ids'] ?? []));
$variant->attributeValues()->sync($valueIds);
$existingIds[] = $variant->id;
}
// Nicht mehr vorhandene Varianten löschen
$product->variants()->whereNotIn('id', $existingIds)->delete();
// Sicherstellen dass jedes Produkt mindestens eine Standard-Variante hat
if ($product->variants()->count() === 0) {
ProductVariant::create([
'product_id' => $product->id,
'sku' => 'DEFAULT-' . $product->id,
'price_override'=> null,
'stock' => 0,
'stock_warning' => 5,
'active' => true,
]);
}
}
}