feat: agregar campo 'key_sat' a los modelos de inventario y sus solicitudes, y crear observador para notificaciones de stock bajo
This commit is contained in:
parent
c2929d0b2f
commit
bbc95f4ea2
@ -21,6 +21,7 @@ public function rules(): array
|
||||
return [
|
||||
// Campos de Inventory
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'key_sat' => ['nullable', 'integer', 'max:9'],
|
||||
'sku' => ['nullable', 'string', 'max:50', 'unique:inventories,sku'],
|
||||
'barcode' => ['nullable', 'string', 'unique:inventories,barcode'],
|
||||
'category_id' => ['required', 'exists:categories,id'],
|
||||
|
||||
@ -23,6 +23,7 @@ public function rules(): array
|
||||
return [
|
||||
// Campos de Inventory
|
||||
'name' => ['nullable', 'string', 'max:100'],
|
||||
'key_sat' => ['nullable', 'string', 'max:9'],
|
||||
'sku' => ['nullable', 'string', 'max:50'],
|
||||
'barcode' => ['nullable', 'string', 'unique:inventories,barcode,' . $inventoryId],
|
||||
'category_id' => ['nullable', 'exists:categories,id'],
|
||||
|
||||
@ -22,6 +22,7 @@ class Inventory extends Model
|
||||
'category_id',
|
||||
'unit_of_measure_id',
|
||||
'name',
|
||||
'key_sat',
|
||||
'sku',
|
||||
'barcode',
|
||||
'track_serials',
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use App\Observers\InventoryWarehouseObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([InventoryWarehouseObserver::class])]
|
||||
class InventoryWarehouse extends Model
|
||||
{
|
||||
protected $table = 'inventory_warehouse';
|
||||
|
||||
69
app/Observers/InventoryWarehouseObserver.php
Normal file
69
app/Observers/InventoryWarehouseObserver.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php namespace App\Observers;
|
||||
/**
|
||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||
*/
|
||||
|
||||
use App\Models\InventoryWarehouse;
|
||||
use App\Models\User;
|
||||
use App\Notifications\UserNotification;
|
||||
|
||||
/**
|
||||
* Observer de stock por almacén
|
||||
*
|
||||
* Envía notificación a administradores cuando el stock de un producto
|
||||
* cruza el umbral mínimo (de disponible a bajo stock).
|
||||
*
|
||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class InventoryWarehouseObserver
|
||||
{
|
||||
/**
|
||||
* Roles que reciben alertas de stock bajo
|
||||
*/
|
||||
protected array $notifiableRoles = ['developer', 'admin'];
|
||||
|
||||
/**
|
||||
* Umbral por defecto cuando min_stock no está configurado
|
||||
*/
|
||||
protected int $defaultThreshold = 5;
|
||||
|
||||
/**
|
||||
* Detectar cuando el stock cruza el umbral mínimo hacia abajo
|
||||
*/
|
||||
public function updated(InventoryWarehouse $inventoryWarehouse): void
|
||||
{
|
||||
$previousStock = (float) $inventoryWarehouse->getOriginal('stock');
|
||||
$currentStock = (float) $inventoryWarehouse->stock;
|
||||
$threshold = (float) ($inventoryWarehouse->min_stock ?? $this->defaultThreshold);
|
||||
|
||||
// Solo notificar si el stock CRUZÓ el umbral (no en cada update mientras ya esté bajo)
|
||||
if ($previousStock >= $threshold && $currentStock < $threshold) {
|
||||
$this->notifyLowStock($inventoryWarehouse, $currentStock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar notificación de stock bajo a usuarios con rol relevante
|
||||
*/
|
||||
protected function notifyLowStock(InventoryWarehouse $inventoryWarehouse, float $currentStock): void
|
||||
{
|
||||
$inventoryWarehouse->load('inventory', 'warehouse');
|
||||
|
||||
$productName = $inventoryWarehouse->inventory?->name ?? 'Producto desconocido';
|
||||
$warehouseName = $inventoryWarehouse->warehouse?->name ?? 'Almacén desconocido';
|
||||
|
||||
$users = User::role($this->notifiableRoles)->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user->notify(new UserNotification(
|
||||
title: 'Stock bajo: ' . $productName,
|
||||
description: "El producto \"{$productName}\" tiene {$currentStock} unidad(es) disponible(s) en \"{$warehouseName}\".",
|
||||
type: 'warning',
|
||||
timeout: 30,
|
||||
save: true,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ public function createProduct(array $data)
|
||||
return DB::transaction(function () use ($data) {
|
||||
$inventory = Inventory::create([
|
||||
'name' => $data['name'],
|
||||
'key_sat' => $data['key_sat'] ?? null,
|
||||
'sku' => $data['sku'],
|
||||
'barcode' => $data['barcode'] ?? null,
|
||||
'category_id' => $data['category_id'],
|
||||
@ -35,6 +36,7 @@ public function updateProduct(Inventory $inventory, array $data)
|
||||
// Actualizar campos de Inventory solo si están presentes
|
||||
$inventoryData = array_filter([
|
||||
'name' => $data['name'] ?? null,
|
||||
'key_sat' => $data['key_sat'] ?? null,
|
||||
'sku' => $data['sku'] ?? null,
|
||||
'barcode' => $data['barcode'] ?? null,
|
||||
'category_id' => $data['category_id'] ?? null,
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
$table->integer('key_sat')->nullable()->after('name');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
$table->dropColumn('key_sat');
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user