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:
Juan Felipe Zapata Moreno 2026-02-23 16:31:19 -06:00
parent c2929d0b2f
commit bbc95f4ea2
7 changed files with 105 additions and 0 deletions

View File

@ -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'],

View File

@ -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'],

View File

@ -22,6 +22,7 @@ class Inventory extends Model
'category_id',
'unit_of_measure_id',
'name',
'key_sat',
'sku',
'barcode',
'track_serials',

View File

@ -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';

View 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,
));
}
}
}

View File

@ -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,

View File

@ -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');
});
}
};