Merge branch 'fixes/TL-1886' into dev
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TicketsAvailable
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param array<int, int> $ticketIds
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly int $purchaseId,
|
||||
public readonly array $ticketIds,
|
||||
) {}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use App\Domains\Purchase\Events\PurchasePaid;
|
||||
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SendPurchasePaidEmail implements ShouldQueueAfterCommit
|
||||
class SendPurchaseConfirmedEmail implements ShouldQueueAfterCommit
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
|
||||
@@ -20,6 +20,6 @@ class SendPurchasePaidEmail implements ShouldQueueAfterCommit
|
||||
|
||||
public function handle(PurchasePaid $event): void
|
||||
{
|
||||
app(NotificationMailService::class)->sendPurchasePaid($event->purchaseId);
|
||||
app(NotificationMailService::class)->sendPurchaseConfirmed($event->purchaseId);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Listeners;
|
||||
|
||||
use App\Domains\Notification\Events\TicketsAvailable;
|
||||
use App\Domains\Notification\Services\NotificationMailService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SendTicketsAvailableEmail implements ShouldQueueAfterCommit
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
|
||||
public string $queue = 'emails';
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
/** @var array<int, int> */
|
||||
public array $backoff = [30, 120, 300];
|
||||
|
||||
public function handle(TicketsAvailable $event): void
|
||||
{
|
||||
app(NotificationMailService::class)->sendTicketsAvailable($event->purchaseId, $event->ticketIds);
|
||||
}
|
||||
}
|
||||
@@ -119,55 +119,17 @@ class NotificationMailService
|
||||
});
|
||||
}
|
||||
|
||||
public function sendPurchasePaid(int $purchaseId): void
|
||||
public function sendPurchaseConfirmed(int $purchaseId): void
|
||||
{
|
||||
$context = ['purchase_id' => $purchaseId];
|
||||
|
||||
$this->sendLogged('purchase_paid', $context, function () use ($purchaseId, $context): ?array {
|
||||
$this->sendLogged('purchase_confirmed', $context, function () use ($purchaseId, $context): ?array {
|
||||
$purchase = Purchase::query()
|
||||
->with(['tenant', 'user', 'items'])
|
||||
->find($purchaseId);
|
||||
|
||||
if ($purchase === null) {
|
||||
$this->logSkipped('purchase_paid', array_merge($context, [
|
||||
'reason' => 'purchase_not_found',
|
||||
'missing_model' => Purchase::class,
|
||||
]));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->mailService
|
||||
->forTenant($purchase->tenant_codigo)
|
||||
->send(
|
||||
$this->recipientFor($purchase),
|
||||
"Pago confirmado - Compra #{$purchase->getKey()}",
|
||||
view('mail.notifications.purchase-paid', compact('purchase'))->render(),
|
||||
);
|
||||
|
||||
return [
|
||||
'tenant_code' => $purchase->tenant_codigo,
|
||||
'user_id' => $purchase->user_id,
|
||||
'purchase_status' => $purchase->status,
|
||||
'purchase_item_count' => $purchase->items->count(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/** @param array<int, int> $ticketIds */
|
||||
public function sendTicketsAvailable(int $purchaseId, array $ticketIds): void
|
||||
{
|
||||
$context = [
|
||||
'purchase_id' => $purchaseId,
|
||||
'requested_ticket_count' => count($ticketIds),
|
||||
'requested_ticket_ids' => $ticketIds,
|
||||
];
|
||||
|
||||
$this->sendLogged('tickets_available', $context, function () use ($purchaseId, $ticketIds, $context): ?array {
|
||||
$purchase = Purchase::query()->with(['tenant', 'user'])->find($purchaseId);
|
||||
|
||||
if ($purchase === null) {
|
||||
$this->logSkipped('tickets_available', array_merge($context, [
|
||||
$this->logSkipped('purchase_confirmed', array_merge($context, [
|
||||
'reason' => 'purchase_not_found',
|
||||
'missing_model' => Purchase::class,
|
||||
]));
|
||||
@@ -177,41 +139,34 @@ class NotificationMailService
|
||||
|
||||
/** @var Collection<int, Ticket> $tickets */
|
||||
$tickets = Ticket::query()
|
||||
->where('source_purchase_id', $purchase->getKey())
|
||||
->where('tenant_code', $purchase->tenant_codigo)
|
||||
->where('user_id', $purchase->user_id)
|
||||
->whereKey($ticketIds)
|
||||
->with(TicketPresentationResolver::RELATIONS)
|
||||
->get();
|
||||
|
||||
if ($tickets->isEmpty()) {
|
||||
$this->logSkipped('tickets_available', array_merge($context, [
|
||||
'reason' => 'tickets_not_found',
|
||||
'missing_model' => Ticket::class,
|
||||
'tenant_code' => $purchase->tenant_codigo,
|
||||
'user_id' => $purchase->user_id,
|
||||
]));
|
||||
|
||||
return null;
|
||||
}
|
||||
$attachments = $tickets->isEmpty()
|
||||
? []
|
||||
: [[
|
||||
'data' => $this->ticketPdfService->contents($purchase->tenant, $tickets),
|
||||
'name' => $this->ticketPdfService->filename($tickets),
|
||||
'mime' => 'application/pdf',
|
||||
]];
|
||||
|
||||
$this->mailService
|
||||
->forTenant($purchase->tenant_codigo)
|
||||
->send(
|
||||
$this->recipientFor($purchase),
|
||||
'Tus tickets ya están disponibles',
|
||||
view('mail.notifications.tickets-available', compact('purchase', 'tickets'))->render(),
|
||||
attachments: [[
|
||||
'data' => $this->ticketPdfService->contents($purchase->tenant, $tickets),
|
||||
'name' => $this->ticketPdfService->filename($tickets),
|
||||
'mime' => 'application/pdf',
|
||||
]],
|
||||
"Compra confirmada - Compra #{$purchase->getKey()}",
|
||||
view('mail.notifications.purchase-confirmed', compact('purchase', 'tickets'))->render(),
|
||||
attachments: $attachments,
|
||||
);
|
||||
|
||||
return [
|
||||
'tenant_code' => $purchase->tenant_codigo,
|
||||
'user_id' => $purchase->user_id,
|
||||
'sent_ticket_count' => $tickets->count(),
|
||||
'sent_ticket_ids' => $tickets->modelKeys(),
|
||||
'purchase_status' => $purchase->status,
|
||||
'purchase_item_count' => $purchase->items->count(),
|
||||
'ticket_count' => $tickets->count(),
|
||||
'ticket_ids' => $tickets->modelKeys(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@ Orquesta notificaciones de negocio por correo a partir de eventos de otros domin
|
||||
|
||||
- `UserRegistered`: dispara el correo de bienvenida.
|
||||
- `PasswordResetRequested`: envía el código de recuperación si el intento sigue pendiente.
|
||||
- `PurchasePaid`: envía la confirmación de pago.
|
||||
- `TicketsAvailable`: informa y entrega la disponibilidad de tickets.
|
||||
- `PurchasePaid`: envía la confirmación de compra y adjunta los tickets generados, cuando corresponde.
|
||||
|
||||
## Componentes
|
||||
|
||||
Los listeners `SendWelcomeEmail`, `SendPasswordResetEmail`, `SendPurchasePaidEmail` y `SendTicketsAvailableEmail` delegan en `NotificationMailService`. Este servicio carga el contexto necesario, renderiza las vistas y envía mediante `Integration/MailService`.
|
||||
Los listeners `SendWelcomeEmail`, `SendPasswordResetEmail` y `SendPurchaseConfirmedEmail` delegan en `NotificationMailService`. Este servicio carga el contexto necesario, renderiza las vistas y envía mediante `Integration/MailService`.
|
||||
|
||||
## API y dependencias
|
||||
|
||||
@@ -24,5 +23,5 @@ No expone rutas HTTP. Consume datos de `Auth`, `Tenant`, `Purchase` y `Ticket`,
|
||||
- Los listeners reciben identificadores y vuelven a cargar los modelos, evitando transportar entidades obsoletas.
|
||||
- La recuperación no se envía si el intento dejó de estar pendiente.
|
||||
- Los correos de cuenta (bienvenida y recuperación de contraseña) usan la identidad visual del `WebsiteType` asociado al tenant, con fallback al tenant si no tiene uno configurado.
|
||||
- Los correos transaccionales (pago confirmado y tickets disponibles) usan la identidad visual del tenant/evento de la compra.
|
||||
- El correo transaccional de compra confirmada usa la identidad visual del tenant y adjunta un único PDF cuando la compra generó tickets.
|
||||
- Los handlers deben permanecer idempotentes o tolerantes a reintentos de cola.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Domains\Ticket\Listeners;
|
||||
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Notification\Events\TicketsAvailable;
|
||||
use App\Domains\Purchase\Events\PurchasePaid;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Ticket\Exceptions\TicketGenerationException;
|
||||
@@ -21,8 +20,6 @@ class GenerateTicketsForPaidPurchase
|
||||
->with(['user', 'items'])
|
||||
->findOrFail($event->purchaseId);
|
||||
$user = $purchase->user;
|
||||
$ticketIds = [];
|
||||
|
||||
foreach ($purchase->items as $purchaseItem) {
|
||||
$catalogItem = CatalogItem::query()
|
||||
->where('tenant_code', $purchase->tenant_codigo)
|
||||
@@ -40,19 +37,13 @@ class GenerateTicketsForPaidPurchase
|
||||
throw TicketGenerationException::purchaseWithoutUser($purchase);
|
||||
}
|
||||
|
||||
$generatedTickets = $this->ticketGenerator->generate(
|
||||
$this->ticketGenerator->generate(
|
||||
$catalogItem,
|
||||
$user,
|
||||
$purchaseItem->cantidad,
|
||||
$purchaseItem->source_variant_id,
|
||||
$purchase->getKey(),
|
||||
);
|
||||
|
||||
array_push($ticketIds, ...$generatedTickets->pluck('id')->all());
|
||||
}
|
||||
|
||||
if ($ticketIds !== []) {
|
||||
TicketsAvailable::dispatch($purchase->getKey(), $ticketIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ vigente, vencido o usado, y resuelve sus fechas efectivas de inicio y fin sin pe
|
||||
1. `Purchase` emite `PurchasePaid` al confirmarse el pago.
|
||||
2. `GenerateTicketsForPaidPurchase` atiende el evento.
|
||||
3. `TicketGeneratorService` crea los tickets requeridos según ítems, cantidades y vigencia.
|
||||
4. El flujo puede emitir disponibilidad para que `Notification` informe al comprador.
|
||||
4. `Notification` envía la confirmación de compra después de la generación y adjunta los tickets cuando existen.
|
||||
|
||||
## Endpoints
|
||||
|
||||
|
||||
Reference in New Issue
Block a user