Merge branch 'dev' into homo

This commit is contained in:
2026-08-19 14:19:06 -03:00
5 changed files with 86 additions and 5 deletions

View File

@@ -39,6 +39,8 @@ LOG_LEVEL=debug
LOG_DAILY_DAYS=14
TELEPAGOS_LOG_LEVEL=info
TELEPAGOS_LOG_DAYS=30
COMMANDS_LOG_LEVEL=info
COMMANDS_LOG_DAYS=30
DB_CONNECTION=mysql
DB_HOST=127.0.0.1

View File

@@ -34,4 +34,4 @@ Depende de `Catalog` para productos y variantes, de `Tenant` para aislar datos y
Un carrito puede pasar a `checkout`. Las compras directas usan un carrito técnico con `origin=direct_checkout`; los carritos normales conservan `origin=user` y pueden restaurarse al cancelar o vencer la compra.
El comando unificado `php artisan reservations:expire` procesa primero las compras vencidas y luego las reservas activas sin compra cuyo `expires_at` haya vencido. Se ejecuta cada minuto mediante el scheduler, conserva la fila de reserva con estado `expired`, elimina el ítem abandonado y elimina lógicamente el carrito cuando queda vacío.
El comando unificado `php artisan reservations:expire` procesa primero las compras vencidas y luego las reservas activas sin compra cuyo `expires_at` haya vencido. Se ejecuta cada minuto mediante el scheduler, conserva la fila de reserva con estado `expired`, elimina el ítem abandonado y elimina lógicamente el carrito cuando queda vacío. Cada intento registra sus resultados o su error en el log diario `storage/logs/commands/commands-AAAA-MM-DD.log`.

View File

@@ -4,6 +4,8 @@ namespace App\Domains\Catalog\Services;
use App\Domains\Cart\Services\ExpireCartReservationsService;
use App\Domains\Purchase\Services\CheckoutService;
use Illuminate\Support\Facades\Log;
use Throwable;
class ExpireStockReservationsService
{
@@ -17,9 +19,33 @@ class ExpireStockReservationsService
*/
public function expireOverdue(): array
{
return [
'purchases' => $this->checkout->expireOverduePurchases(),
'cart_items' => $this->carts->expireOverdue(),
];
$expiredPurchases = null;
$expiredCartItems = null;
try {
$expiredPurchases = $this->checkout->expireOverduePurchases();
$expiredCartItems = $this->carts->expireOverdue();
Log::channel('commands')->info('Stock reservation cleanup completed.', [
'command' => 'reservations:expire',
'expired_purchases' => $expiredPurchases,
'expired_cart_items' => $expiredCartItems,
'total_expired' => $expiredPurchases + $expiredCartItems,
]);
return [
'purchases' => $expiredPurchases,
'cart_items' => $expiredCartItems,
];
} catch (Throwable $exception) {
Log::channel('commands')->error('Stock reservation cleanup failed.', [
'command' => 'reservations:expire',
'expired_purchases' => $expiredPurchases,
'expired_cart_items' => $expiredCartItems,
'exception' => $exception,
]);
throw $exception;
}
}
}

View File

@@ -81,6 +81,14 @@ return [
'replace_placeholders' => true,
],
'commands' => [
'driver' => 'daily',
'path' => storage_path('logs/commands/commands.log'),
'level' => env('COMMANDS_LOG_LEVEL', 'info'),
'days' => env('COMMANDS_LOG_DAYS', 30),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),

View File

@@ -5,6 +5,9 @@ namespace Tests\Unit\Catalog;
use App\Domains\Cart\Services\ExpireCartReservationsService;
use App\Domains\Catalog\Services\ExpireStockReservationsService;
use App\Domains\Purchase\Services\CheckoutService;
use Illuminate\Support\Facades\Log;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Tests\TestCase;
class ExpireStockReservationsServiceTest extends TestCase
@@ -23,6 +26,20 @@ class ExpireStockReservationsServiceTest extends TestCase
->ordered()
->andReturn(3);
$logger = \Mockery::mock(LoggerInterface::class);
Log::shouldReceive('channel')
->once()
->with('commands')
->andReturn($logger);
$logger->shouldReceive('info')
->once()
->with('Stock reservation cleanup completed.', [
'command' => 'reservations:expire',
'expired_purchases' => 2,
'expired_cart_items' => 3,
'total_expired' => 5,
]);
$result = (new ExpireStockReservationsService($checkout, $carts))->expireOverdue();
$this->assertSame([
@@ -30,4 +47,32 @@ class ExpireStockReservationsServiceTest extends TestCase
'cart_items' => 3,
], $result);
}
public function test_it_logs_failed_cleanup_attempts_and_rethrows_the_error(): void
{
$exception = new RuntimeException('Unable to clean carts.');
$checkout = \Mockery::mock(CheckoutService::class);
$checkout->shouldReceive('expireOverduePurchases')->once()->andReturn(2);
$carts = \Mockery::mock(ExpireCartReservationsService::class);
$carts->shouldReceive('expireOverdue')->once()->andThrow($exception);
$logger = \Mockery::mock(LoggerInterface::class);
Log::shouldReceive('channel')
->once()
->with('commands')
->andReturn($logger);
$logger->shouldReceive('error')
->once()
->with('Stock reservation cleanup failed.', [
'command' => 'reservations:expire',
'expired_purchases' => 2,
'expired_cart_items' => null,
'exception' => $exception,
]);
$this->expectExceptionObject($exception);
(new ExpireStockReservationsService($checkout, $carts))->expireOverdue();
}
}