up
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\ReporteVentasMail;
|
||||
use App\Models\User;
|
||||
use App\Models\Venta;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class EnviarReporteVentas extends Command
|
||||
{
|
||||
protected $signature = 'reporte:ventas';
|
||||
|
||||
protected $description = 'Enviar reporte de ventas semanal por correo';
|
||||
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$fromDate = Carbon::now()->subWeek()->startOfWeek(); // Lunes anterior
|
||||
$toDate = Carbon::now()->subWeek()->endOfWeek(); // Domingo anterior
|
||||
|
||||
$ventas = Venta::with(['cliente', 'detalles.producto', 'detalles.variante'])
|
||||
->whereBetween('created_at', [$fromDate, $toDate])
|
||||
->get();
|
||||
|
||||
// Obtener todos los usuarios con el rol 'Administrador'
|
||||
$administradores = User::role('Administrador')->get();
|
||||
|
||||
if ($administradores->isEmpty()) {
|
||||
$this->warn('No se encontraron usuarios con el rol Administrador.');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($administradores as $admin) {
|
||||
Mail::to($admin->email)->send(new ReporteVentasMail($ventas, $fromDate, $toDate, $admin->email));
|
||||
$this->info("Reporte enviado a: {$admin->email}");
|
||||
}
|
||||
|
||||
$this->info('Todos los reportes han sido enviados a los administradores.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Bodega;
|
||||
use App\Models\Producto;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MigrarStockBodegaPrincipal extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'stock:migrar-principal';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Migra todo el stock actual de productos a la bodega principal';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Iniciando migración de stock a bodega principal...');
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// 1. Crear o encontrar la bodega principal
|
||||
$bodegaPrincipal = Bodega::firstOrCreate([
|
||||
'nombre' => 'Principal'
|
||||
]);
|
||||
|
||||
$this->info("Bodega principal creada/encontrada: {$bodegaPrincipal->nombre}");
|
||||
|
||||
// 2. Obtener todos los productos que necesitan migración
|
||||
$productos = Producto::with('variants')->get()->filter(function ($producto) {
|
||||
// Productos con stock directo O con variantes que tienen stock
|
||||
return $producto->stock > 0 ||
|
||||
($producto->variants()->exists() && $producto->variants()->sum('stock') > 0);
|
||||
});
|
||||
|
||||
$this->info("Encontrados {$productos->count()} productos con stock para migrar");
|
||||
|
||||
$productosActualizados = 0;
|
||||
|
||||
foreach ($productos as $producto) {
|
||||
// Calcular stock total a migrar
|
||||
$stockAMigrar = 0;
|
||||
|
||||
if ($producto->variants()->exists()) {
|
||||
// Para productos con variantes, usar la suma de variantes
|
||||
$stockAMigrar = $producto->variants()->sum('stock');
|
||||
$this->line(" → Producto con variantes: {$producto->nombre}");
|
||||
$this->line(" Stock total de variantes: {$stockAMigrar}");
|
||||
} else {
|
||||
// Para productos sin variantes, usar stock directo
|
||||
$stockAMigrar = $producto->stock;
|
||||
}
|
||||
|
||||
if ($stockAMigrar <= 0) {
|
||||
continue; // Saltar productos sin stock
|
||||
}
|
||||
// Verificar si ya existe una relación con esta bodega
|
||||
$existeRelacion = $producto->bodegas()
|
||||
->where('bodega_id', $bodegaPrincipal->id)
|
||||
->exists();
|
||||
|
||||
if (!$existeRelacion) {
|
||||
// Crear la relación con el stock calculado
|
||||
$producto->bodegas()->attach($bodegaPrincipal->id, [
|
||||
'stock' => $stockAMigrar
|
||||
]);
|
||||
|
||||
$this->line("✓ {$producto->nombre}: {$stockAMigrar} unidades → Bodega Principal");
|
||||
$productosActualizados++;
|
||||
} else {
|
||||
// Actualizar el stock existente sumando el stock calculado
|
||||
$stockActual = $producto->bodegas()
|
||||
->where('bodega_id', $bodegaPrincipal->id)
|
||||
->first()->pivot->stock;
|
||||
|
||||
$nuevoStock = $stockActual + $stockAMigrar;
|
||||
|
||||
$producto->bodegas()->updateExistingPivot($bodegaPrincipal->id, [
|
||||
'stock' => $nuevoStock
|
||||
]);
|
||||
|
||||
$this->line("✓ {$producto->nombre}: {$stockActual} + {$stockAMigrar} = {$nuevoStock} unidades → Bodega Principal");
|
||||
$productosActualizados++;
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
$this->info("\n🎉 Migración completada exitosamente!");
|
||||
$this->info("📦 Productos procesados: {$productosActualizados}");
|
||||
$this->info("🏢 Bodega principal: {$bodegaPrincipal->nombre}");
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error("❌ Error durante la migración: " . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Bodega;
|
||||
use App\Models\ProductVariant;
|
||||
use App\Models\Producto;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MigrarVariantesBodegaPrincipal extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'stock:migrar-todo-principal';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Migra el stock de productos y variantes a la bodega principal';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🚀 Iniciando migración completa de stock a bodega principal...');
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// 1. Crear o encontrar la bodega principal
|
||||
$bodegaPrincipal = Bodega::firstOrCreate([
|
||||
'nombre' => 'Principal'
|
||||
], [
|
||||
'descripcion' => 'Bodega principal del sistema',
|
||||
'ubicacion' => 'Sede principal',
|
||||
'estado' => true
|
||||
]);
|
||||
|
||||
$this->info("✅ Bodega principal: {$bodegaPrincipal->nombre}");
|
||||
|
||||
// 2. MIGRAR PRODUCTOS DIRECTOS (sin variantes)
|
||||
$this->info("\n📦 Migrando productos directos...");
|
||||
$productosDirectos = Producto::where('estado', true)
|
||||
->where('stock', '>', 0)
|
||||
->whereDoesntHave('variants') // Solo productos sin variantes
|
||||
->get();
|
||||
|
||||
$this->info("Encontrados {$productosDirectos->count()} productos directos con stock");
|
||||
|
||||
$productosActualizados = 0;
|
||||
|
||||
foreach ($productosDirectos as $producto) {
|
||||
// Verificar si ya existe una relación con esta bodega
|
||||
$existeRelacion = $producto->bodegas()
|
||||
->where('bodega_id', $bodegaPrincipal->id)
|
||||
->exists();
|
||||
|
||||
if (!$existeRelacion) {
|
||||
// Crear la relación con el stock actual del producto
|
||||
$producto->bodegas()->attach($bodegaPrincipal->id, [
|
||||
'stock' => $producto->stock
|
||||
]);
|
||||
|
||||
$this->line("✓ {$producto->nombre}: {$producto->stock} unidades → Bodega Principal");
|
||||
$productosActualizados++;
|
||||
} else {
|
||||
// Actualizar el stock existente
|
||||
$stockActual = $producto->bodegas()
|
||||
->where('bodega_id', $bodegaPrincipal->id)
|
||||
->first()->pivot->stock;
|
||||
|
||||
$nuevoStock = $stockActual + $producto->stock;
|
||||
|
||||
$producto->bodegas()->updateExistingPivot($bodegaPrincipal->id, [
|
||||
'stock' => $nuevoStock
|
||||
]);
|
||||
|
||||
$this->line("✓ {$producto->nombre}: {$stockActual} + {$producto->stock} = {$nuevoStock} unidades → Bodega Principal");
|
||||
$productosActualizados++;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. MIGRAR VARIANTES DE PRODUCTOS
|
||||
$this->info("\n🔄 Migrando variantes de productos...");
|
||||
$variantes = ProductVariant::with('producto')
|
||||
->where('stock', '>', 0)
|
||||
->get();
|
||||
|
||||
$this->info("Encontradas {$variantes->count()} variantes con stock");
|
||||
|
||||
$variantesActualizadas = 0;
|
||||
|
||||
foreach ($variantes as $variante) {
|
||||
// Verificar si ya existe una relación con esta bodega
|
||||
$existeRelacion = $variante->bodegas()
|
||||
->where('bodega_id', $bodegaPrincipal->id)
|
||||
->exists();
|
||||
|
||||
if (!$existeRelacion) {
|
||||
// Crear la relación con el stock actual de la variante
|
||||
$variante->bodegas()->attach($bodegaPrincipal->id, [
|
||||
'stock' => $variante->stock
|
||||
]);
|
||||
|
||||
$this->line("✓ {$variante->producto->nombre} [{$variante->sku}]: {$variante->stock} unidades → Bodega Principal");
|
||||
$variantesActualizadas++;
|
||||
} else {
|
||||
// Actualizar el stock existente sumando el stock de la variante
|
||||
$stockActual = $variante->bodegas()
|
||||
->where('bodega_id', $bodegaPrincipal->id)
|
||||
->first()->pivot->stock;
|
||||
|
||||
$nuevoStock = $stockActual + $variante->stock;
|
||||
|
||||
$variante->bodegas()->updateExistingPivot($bodegaPrincipal->id, [
|
||||
'stock' => $nuevoStock
|
||||
]);
|
||||
|
||||
$this->line("✓ {$variante->producto->nombre} [{$variante->sku}]: {$stockActual} + {$variante->stock} = {$nuevoStock} unidades → Bodega Principal");
|
||||
$variantesActualizadas++;
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
$this->info("\n🎉 Migración completa de stock exitosa!");
|
||||
$this->info("📦 Productos directos procesados: {$productosActualizados}");
|
||||
$this->info("� Variantes procesadas: {$variantesActualizadas}");
|
||||
$this->info("🏢 Bodega principal: {$bodegaPrincipal->nombre}");
|
||||
$this->info("📊 Total de elementos migrados: " . ($productosActualizados + $variantesActualizadas));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error("❌ Error durante la migración: " . $e->getMessage());
|
||||
$this->error("🔍 Línea: " . $e->getLine());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\AlertaStockMail;
|
||||
use App\Models\User;
|
||||
use App\Models\Producto;
|
||||
|
||||
class VerificarAlertasStock extends Command
|
||||
{
|
||||
protected $signature = 'stock:verificar-alertas {--email= : Email específico para enviar la alerta}';
|
||||
|
||||
protected $description = 'Verificar niveles de stock y enviar alertas por correo cuando se alcance el mínimo o máximo';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🔍 Iniciando verificación de niveles de stock...');
|
||||
|
||||
// Obtener productos con stock mínimo o inferior
|
||||
$productosStockMinimo = $this->obtenerProductosStockMinimo();
|
||||
|
||||
// Obtener productos que exceden el stock máximo
|
||||
$productosStockMaximo = $this->obtenerProductosStockMaximo();
|
||||
|
||||
if ($productosStockMinimo->isEmpty() && $productosStockMaximo->isEmpty()) {
|
||||
$this->info('✅ Todos los productos están en niveles normales de stock.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Mostrar resumen en consola
|
||||
$this->mostrarResumen($productosStockMinimo, $productosStockMaximo);
|
||||
|
||||
// Enviar alertas por correo
|
||||
$this->enviarAlertas($productosStockMinimo, $productosStockMaximo);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function obtenerProductosStockMinimo()
|
||||
{
|
||||
return Producto::with(['categoria', 'variants', 'bodegas'])
|
||||
->where('estado', true)
|
||||
->get()
|
||||
->filter(function ($producto) {
|
||||
// Usar el método getStockEfectivo que considera bodegas
|
||||
$stockActual = $producto->getStockEfectivo();
|
||||
|
||||
return $stockActual <= $producto->stock_minimo;
|
||||
});
|
||||
}
|
||||
|
||||
private function obtenerProductosStockMaximo()
|
||||
{
|
||||
return Producto::with(['categoria', 'variants', 'bodegas'])
|
||||
->where('estado', true)
|
||||
->whereNotNull('stock_maximo')
|
||||
->get()
|
||||
->filter(function ($producto) {
|
||||
// Usar el método getStockEfectivo que considera bodegas
|
||||
$stockActual = $producto->getStockEfectivo();
|
||||
|
||||
return $stockActual > $producto->stock_maximo;
|
||||
});
|
||||
}
|
||||
|
||||
private function mostrarResumen($productosStockMinimo, $productosStockMaximo)
|
||||
{
|
||||
if ($productosStockMinimo->isNotEmpty()) {
|
||||
$this->warn("⚠️ {$productosStockMinimo->count()} productos con stock mínimo o inferior:");
|
||||
|
||||
$headers = ['Producto', 'Stock Actual', 'Stock Mínimo', 'Estado'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($productosStockMinimo as $producto) {
|
||||
$stockActual = $producto->getStockEfectivo();
|
||||
|
||||
$estado = $stockActual == 0 ? 'Sin Stock' : ($stockActual < $producto->stock_minimo ? 'Por debajo' : 'En mínimo');
|
||||
|
||||
$rows[] = [
|
||||
$producto->nombre,
|
||||
$stockActual,
|
||||
$producto->stock_minimo,
|
||||
$estado
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
}
|
||||
|
||||
if ($productosStockMaximo->isNotEmpty()) {
|
||||
$this->info("📈 {$productosStockMaximo->count()} productos que exceden el stock máximo:");
|
||||
|
||||
$headers = ['Producto', 'Stock Actual', 'Stock Máximo', 'Exceso'];
|
||||
$rows = [];
|
||||
|
||||
foreach ($productosStockMaximo as $producto) {
|
||||
$stockActual = $producto->getStockEfectivo();
|
||||
|
||||
$exceso = $stockActual - $producto->stock_maximo;
|
||||
|
||||
$rows[] = [
|
||||
$producto->nombre,
|
||||
$stockActual,
|
||||
$producto->stock_maximo,
|
||||
"+{$exceso}"
|
||||
];
|
||||
}
|
||||
|
||||
$this->table($headers, $rows);
|
||||
}
|
||||
}
|
||||
|
||||
private function enviarAlertas($productosStockMinimo, $productosStockMaximo)
|
||||
{
|
||||
// Determinar destinatarios
|
||||
$email = $this->option('email');
|
||||
|
||||
if ($email) {
|
||||
$destinatarios = collect([$email]);
|
||||
$this->info("📧 Enviando alerta a email específico: {$email}");
|
||||
} else {
|
||||
// Obtener administradores
|
||||
$administradores = User::role('Administrador')->get();
|
||||
|
||||
if ($administradores->isEmpty()) {
|
||||
$this->warn('⚠️ No se encontraron usuarios con el rol Administrador.');
|
||||
return;
|
||||
}
|
||||
|
||||
$destinatarios = $administradores->pluck('email');
|
||||
$this->info("📧 Enviando alertas a {$administradores->count()} administradores...");
|
||||
}
|
||||
|
||||
// Enviar correos
|
||||
foreach ($destinatarios as $emailDestino) {
|
||||
try {
|
||||
Mail::to($emailDestino)->send(new AlertaStockMail(
|
||||
$productosStockMinimo,
|
||||
$productosStockMaximo,
|
||||
$emailDestino
|
||||
));
|
||||
|
||||
$this->info("✅ Alerta enviada a: {$emailDestino}");
|
||||
} catch (\Exception $e) {
|
||||
$this->error("❌ Error al enviar a {$emailDestino}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('🎉 Proceso de envío de alertas completado.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user