diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index fcf5db8..b456d43 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -3,15 +3,28 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use App\Models\Client; class ClientController extends Controller { /** * Display a listing of the resource. */ - public function index() + public function index(Request $request) { - // + $query = $request->input('search'); + + $clients = Client::query() + ->when($query, function ($q) use ($query) { + // Si hay búsqueda, filtra por nombre o SKU + return $q->where('name', 'like', "%{$query}%"); + }) + ->orderBy('name', 'asc') + //->orderBy('create_at', 'asc') + ->paginate(10) // Paginamos de a 10 + ->withQueryString(); // Mantiene el filtro de búsqueda al cambiar de página + + return view('clients.index', compact('clients')); } /** @@ -19,7 +32,7 @@ class ClientController extends Controller */ public function create() { - // + return view('clients.create'); } /** @@ -27,7 +40,28 @@ class ClientController extends Controller */ public function store(Request $request) { - // + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'phone' => 'nullable|string|max:50', + 'email' => 'nullable|email|max:255|unique:clients,email', + 'address' => 'nullable|string|max:255', + ]); + + // 1. Guardamos el cliente en una variable para tener su ID + $client = Client::create($validated); + + // 2. Verificamos el origen + if ($request->input('origin') === 'sales') { + + // Si vino de ventas, volvemos a ventas + // Y pasamos el ID del nuevo cliente para auto-seleccionarlo + return redirect()->route('sales.create', ['new_client_id' => $client->id]) + ->with('success', 'Cliente creado. Ya puedes seleccionarlo.'); + } + + // 3. Si no, comportamiento normal (volver al index de clientes) + return redirect()->route('clients.index') + ->with('success', 'Cliente creado correctamente.'); } /** @@ -38,27 +72,38 @@ class ClientController extends Controller // } - /** - * Show the form for editing the specified resource. - */ - public function edit(string $id) + public function edit(Client $client) { - // + // Reutilizamos la vista de create, o creamos una edit.blade.php similar + return view('clients.edit', compact('client')); + } + + public function update(Request $request, Client $client) + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'phone' => 'nullable|string|max:50', + 'email' => 'nullable|email|max:255|unique:clients,email,' . $client->id, // Ignorar email propio + 'address' => 'nullable|string|max:255', + ]); + + $client->update($validated); + + return redirect()->route('clients.index')->with('success', 'Cliente actualizado correctamente.'); } - /** - * Update the specified resource in storage. - */ - public function update(Request $request, string $id) - { - // - } /** * Remove the specified resource from storage. */ - public function destroy(string $id) + public function destroy(Client $client) { - // + try { + $client->delete(); + return redirect()->route('clients.index')->with('success', 'Cliente eliminado correctamente.'); + } catch (\Illuminate\Database\QueryException $e) { + + return back()->with('error', 'No se puede eliminar el cliente porque tiene ventas registradas.'); + } } } diff --git a/app/Http/Controllers/SaleController.php b/app/Http/Controllers/SaleController.php index 8e8a7f7..b7f3832 100644 --- a/app/Http/Controllers/SaleController.php +++ b/app/Http/Controllers/SaleController.php @@ -3,78 +3,77 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; -use App\Models\Product; +use App\Models\Product; +use App\Models\Client; use App\Models\Sale; use App\Models\SaleDetail; use Illuminate\Support\Facades\DB; -// -use App\Models\Client; class SaleController extends Controller { public function create() { - // Buscamos productos que tengan stock mayor a 0 para mostrar en el selector + // Traemos productos con stock y clientes para los selectores $products = Product::where('stock_quantity', '>', 0)->get(); - - $clients = \App\Models\Client::orderBy('name')->get(); + $clients = Client::orderBy('name')->get(); return view('sales.create', compact('products', 'clients')); } public function store(Request $request) { + // Validación estricta $request->validate([ + 'client_id' => 'nullable|exists:clients,id', 'payment_method' => 'required|string', - 'client_id' => 'nullable|exists:clients,id', - 'items' => 'required|array', + 'items' => 'required|array', 'items.*.product_id' => 'required|exists:products,id', - 'items.*.quantity' => 'required|integer|min:1', + 'items.*.quantity' => 'required|integer|min:1', ]); try { DB::transaction(function () use ($request) { - $totalSale = 0; + + $totalVenta = 0; - foreach ($request->items as $item) { - $product = Product::find($item['product_id']); - $totalSale += $product->precio * $item['quantity']; + // 1. Calcular total previo (Solo habrá 1 item por ahora, pero el bucle sirve igual) + foreach ($request->items as $itemData) { + $product = Product::findOrFail($itemData['product_id']); - if ($product->stock_quantity < $item['quantity']) { - throw new \Exception("No hay suficiente stock de " . $product->nombre); + if ($product->stock_quantity < $itemData['quantity']) { + throw new \Exception("Stock insuficiente para: " . $product->name); } + + $totalVenta += $product->price * $itemData['quantity']; } - //cabecera + // 2. Crear Venta $sale = Sale::create([ - //'user_id' => auth()->id(), //empleado logueado - 'client_id' => $request->client_id, - 'total' => $totalSale, + 'client_id' => $request->client_id, + 'total' => $totalVenta, 'payment_method' => $request->payment_method, + // 'user_id' => auth()->id(), // Descomenta si usas autenticación ]); - foreach ($request->items as $item) { - $product = Product::find($item['product_id']); + // 3. Guardar Detalle y Restar Stock + foreach ($request->items as $itemData) { + $product = Product::findOrFail($itemData['product_id']); SaleDetail::create([ - 'sale_id' => $sale->id, + 'sale_id' => $sale->id, 'product_id' => $product->id, - 'quantity' => $item['quantity'], - 'price' => $product->price, + 'quantity' => $itemData['quantity'], + 'price' => $product->price, ]); - $nuevoStock = $product->stock_quantity - $item['quantity']; - $product->stock_quantity = $nuevoStock; - $product->save(); + + $product->decrement('stock_quantity', $itemData['quantity']); } }); - // 5. REDIRECCIÓN DE ÉXITO - // Redirigimos al usuario con un mensaje flash [cite: 668] return redirect()->route('sales.create')->with('success', '¡Venta registrada correctamente!'); } catch (\Exception $e) { - // Si algo falló (ej: falta stock), volvemos atrás con el error - return back()->with('error', 'Error en la venta: ' . $e->getMessage()); + return back()->with('error', $e->getMessage())->withInput(); } } } diff --git a/package-lock.json b/package-lock.json index 1f1aba3..278d680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "": { "dependencies": { "@tailwindcss/postcss": "^4.1.18", - "jquery": "^3.7.1" + "jquery": "^3.7.1", + "select2": "^4.1.0-rc.0" }, "devDependencies": { "@tailwindcss/vite": "^4.1.18", @@ -2243,6 +2244,12 @@ "tslib": "^2.1.0" } }, + "node_modules/select2": { + "version": "4.1.0-rc.0", + "resolved": "https://registry.npmjs.org/select2/-/select2-4.1.0-rc.0.tgz", + "integrity": "sha512-Hr9TdhyHCZUtwznEH2CBf7967mEM0idtJ5nMtjvk3Up5tPukOLXbHUNmh10oRfeNIhj+3GD3niu+g6sVK+gK0A==", + "license": "MIT" + }, "node_modules/shell-quote": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", diff --git a/package.json b/package.json index 385e02e..259ef1c 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@tailwindcss/postcss": "^4.1.18", - "jquery": "^3.7.1" + "jquery": "^3.7.1", + "select2": "^4.1.0-rc.0" } } diff --git a/resources/js/app.js b/resources/js/app.js index e59d6a0..d0ead51 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,13 @@ import './bootstrap'; +// 1. Importar jQuery +import jQuery from 'jquery'; + +// 2. Hacerlo global (IMPORTANTE para que funcione $(document).ready en Blade) +window.$ = window.jQuery = jQuery; + +// 3. Importar Select2 +import select2 from 'select2'; +select2(); // Inicializar el plugin + +// 4. Importar los estilos de Select2 (Opcional aquí, o en CSS) +import 'select2/dist/css/select2.css'; diff --git a/resources/views/clients/create.blade.php b/resources/views/clients/create.blade.php index 6265ad0..5bddb0b 100644 --- a/resources/views/clients/create.blade.php +++ b/resources/views/clients/create.blade.php @@ -1,5 +1,49 @@ -@extends('layouts.app') -@section('main') -
| Cliente / Email | +Teléfono / WhatsApp | +Dirección | +Acciones | +
|---|---|---|---|
|
+ {{ $client->name }}
+
+ {{ $client->email ?? 'Sin email registrado' }}
+
+ |
+
+
+ @if($client->phone)
+
+ {{ $client->phone }}
+ WA
+
+ @else
+ No registrado
+ @endif
+ |
+
+ + + {{ $client->address ?? '-' }} + + | + ++ + Editar + + + + | + +
|
+
+
+
+ No se encontraron clientes. + |
+ |||