current(); if (! $tenant) { throw new NotFoundHttpException('Magazinul e disponibil doar pe subdomeniul service-ului.'); } if (! data_get($tenant->settings, 'shop.enabled')) { throw new NotFoundHttpException('Magazinul online nu este activ.'); } return $tenant; } public function catalog(Request $request) { $tenant = $this->tenantOrFail(); $term = $request->query('q'); $category = $request->query('cat'); $inStock = $request->boolean('in_stock'); $query = Part::searchPublished($term); if ($category) $query->where('category', $category); if ($inStock) $query->where('qty', '>', 0); $parts = $query->orderBy('name')->paginate(24)->withQueryString(); $categories = Part::published()->distinct()->pluck('category')->filter()->sort()->values(); return view('shop.catalog', [ 'tenant' => $tenant, 'parts' => $parts, 'categories' => $categories, 'term' => $term, 'category' => $category, 'inStock' => $inStock, 'cartCount' => $this->cartCount(), ]); } public function part(Request $request, int $id) { $tenant = $this->tenantOrFail(); $part = Part::published()->with('crossRefs')->find($id); if (! $part) throw new NotFoundHttpException('Piesa nu există sau nu e publicată.'); return view('shop.part', [ 'tenant' => $tenant, 'part' => $part, 'cartCount' => $this->cartCount(), ]); } public function vin(Request $request) { $tenant = $this->tenantOrFail(); $vin = strtoupper(trim((string) $request->query('vin', ''))); $decoded = null; if ($vin !== '') { $decoded = app(VinDecoder::class)->decode($vin); } return view('shop.vin', [ 'tenant' => $tenant, 'vin' => $vin, 'decoded' => $decoded, 'cartCount' => $this->cartCount(), ]); } // ─── Cart (session) ─────────────────────────────────────────── private function cartKey(): string { $tenant = app(TenantManager::class)->current(); return 'shop_cart_' . ($tenant?->id ?? '0'); } private function cart(): array { return (array) session($this->cartKey(), []); } private function cartCount(): int { return (int) collect($this->cart())->sum('qty'); } public function addToCart(Request $request, int $id) { $this->tenantOrFail(); $part = Part::published()->findOrFail($id); $qty = max(1, (int) $request->input('qty', 1)); $cart = $this->cart(); $cart[$id] = [ 'part_id' => $part->id, 'name' => $part->name, 'article' => $part->article, 'price' => (float) $part->sell_price, 'qty' => ($cart[$id]['qty'] ?? 0) + $qty, ]; session([$this->cartKey() => $cart]); return redirect('/shop/cart'); } public function updateCart(Request $request) { $this->tenantOrFail(); $cart = $this->cart(); foreach ((array) $request->input('qty', []) as $id => $qty) { $qty = (int) $qty; if ($qty <= 0) { unset($cart[$id]); } elseif (isset($cart[$id])) { $cart[$id]['qty'] = $qty; } } session([$this->cartKey() => $cart]); return redirect('/shop/cart'); } public function showCart() { $tenant = $this->tenantOrFail(); $cart = $this->cart(); $subtotal = collect($cart)->sum(fn ($i) => $i['price'] * $i['qty']); return view('shop.cart', [ 'tenant' => $tenant, 'cart' => $cart, 'subtotal' => $subtotal, 'cartCount' => $this->cartCount(), ]); } public function checkout() { $tenant = $this->tenantOrFail(); $cart = $this->cart(); if (empty($cart)) return redirect('/shop'); $subtotal = collect($cart)->sum(fn ($i) => $i['price'] * $i['qty']); return view('shop.checkout', [ 'tenant' => $tenant, 'cart' => $cart, 'subtotal' => $subtotal, 'deliveryOptions' => (array) data_get($tenant->settings, 'shop.delivery_methods', ['pickup']), 'cartCount' => $this->cartCount(), ]); } public function placeOrder(Request $request) { $tenant = $this->tenantOrFail(); $cart = $this->cart(); if (empty($cart)) return redirect('/shop'); $data = $request->validate([ 'customer_name' => 'required|string|max:160', 'customer_phone' => 'required|string|max:40', 'customer_email' => 'nullable|email|max:160', 'delivery_method' => 'required|in:pickup,courier,post', 'address' => 'nullable|string|max:255', 'notes' => 'nullable|string|max:1000', ]); $deliveryFee = 0.0; $subtotal = collect($cart)->sum(fn ($i) => $i['price'] * $i['qty']); if ($data['delivery_method'] !== 'pickup') { $fee = (float) data_get($tenant->settings, 'shop.delivery_fee', 0); $freeOver = (float) data_get($tenant->settings, 'shop.free_delivery_over', 0); $deliveryFee = ($freeOver > 0 && $subtotal >= $freeOver) ? 0.0 : $fee; } $order = DB::transaction(function () use ($tenant, $cart, $data, $deliveryFee) { $order = OnlineOrder::create([ 'number' => OnlineOrder::generateNumber($tenant->id), 'customer_name' => $data['customer_name'], 'customer_phone' => $data['customer_phone'], 'customer_email' => $data['customer_email'] ?? null, 'delivery_method' => $data['delivery_method'], 'address' => $data['address'] ?? null, 'notes' => $data['notes'] ?? null, 'status' => 'new', 'delivery_fee' => $deliveryFee, ]); foreach ($cart as $item) { OnlineOrderItem::create([ 'online_order_id' => $order->id, 'part_id' => $item['part_id'] ?? null, 'name' => $item['name'], 'article' => $item['article'] ?? null, 'qty' => $item['qty'], 'price' => $item['price'], ]); } $order->refresh()->recalcTotal(); return $order; }); session()->forget($this->cartKey()); // Notify (best-effort): customer + shop staff. try { app(\App\Services\Notifications\ShopOrderNotifier::class)->placed($order); } catch (\Throwable $e) { \Illuminate\Support\Facades\Log::debug('shop order notify skipped: ' . $e->getMessage()); } return redirect('/shop/order/' . $order->tracking_token); } public function orderStatus(Request $request, string $token) { $tenant = $this->tenantOrFail(); $order = OnlineOrder::with('items')->where('tracking_token', $token)->first(); if (! $order) throw new NotFoundHttpException('Comanda nu a fost găsită.'); return view('shop.order', [ 'tenant' => $tenant, 'order' => $order, 'cartCount' => $this->cartCount(), ]); } }