feat(notification): add PDF attachments to ticket availability emails
This commit is contained in:
@@ -72,11 +72,15 @@ class MailService extends BaseIntegrationService
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array{data: string, name: string, mime: string}> $attachments
|
||||||
|
*/
|
||||||
public function send(
|
public function send(
|
||||||
string|array $recipient,
|
string|array $recipient,
|
||||||
string $subject,
|
string $subject,
|
||||||
string $content,
|
string $content,
|
||||||
Tenant|WebsiteType|null $brand = null,
|
Tenant|WebsiteType|null $brand = null,
|
||||||
|
array $attachments = [],
|
||||||
): void {
|
): void {
|
||||||
if (! $this->mailer || ! $this->tenant) {
|
if (! $this->mailer || ! $this->tenant) {
|
||||||
throw new Exception('MailService no está configurado. Llamá a forTenant() o forClient() primero.');
|
throw new Exception('MailService no está configurado. Llamá a forTenant() o forClient() primero.');
|
||||||
@@ -105,6 +109,14 @@ class MailService extends BaseIntegrationService
|
|||||||
->subject($subject)
|
->subject($subject)
|
||||||
->html($html);
|
->html($html);
|
||||||
|
|
||||||
|
foreach ($attachments as $attachment) {
|
||||||
|
$mail->attachData(
|
||||||
|
$attachment['data'],
|
||||||
|
$attachment['name'],
|
||||||
|
['mime' => $attachment['mime']],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->mailer->to($recipient)->send($mail);
|
$this->mailer->to($recipient)->send($mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Domains\Notification\Events\PasswordResetRequested;
|
|||||||
use App\Domains\Purchase\Models\Purchase;
|
use App\Domains\Purchase\Models\Purchase;
|
||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
use App\Domains\Ticket\Models\Ticket;
|
use App\Domains\Ticket\Models\Ticket;
|
||||||
|
use App\Domains\Ticket\Services\TicketPdfService;
|
||||||
use App\Domains\Ticket\Services\TicketPresentationResolver;
|
use App\Domains\Ticket\Services\TicketPresentationResolver;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -17,6 +18,7 @@ class NotificationMailService
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MailService $mailService,
|
private readonly MailService $mailService,
|
||||||
|
private readonly TicketPdfService $ticketPdfService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function sendWelcome(int $userId, string $tenantCode): void
|
public function sendWelcome(int $userId, string $tenantCode): void
|
||||||
@@ -130,6 +132,11 @@ class NotificationMailService
|
|||||||
$this->recipientFor($purchase),
|
$this->recipientFor($purchase),
|
||||||
'Tus tickets ya están disponibles',
|
'Tus tickets ya están disponibles',
|
||||||
view('mail.notifications.tickets-available', compact('purchase', 'tickets'))->render(),
|
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',
|
||||||
|
]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Domains\Ticket\Services;
|
|||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
use App\Domains\Ticket\Models\Ticket;
|
use App\Domains\Ticket\Models\Ticket;
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
use Barryvdh\DomPDF\Facade\Pdf;
|
||||||
|
use Barryvdh\DomPDF\PDF as DomPdf;
|
||||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||||
use Endroid\QrCode\QrCode;
|
use Endroid\QrCode\QrCode;
|
||||||
use Endroid\QrCode\Writer\PngWriter;
|
use Endroid\QrCode\Writer\PngWriter;
|
||||||
@@ -19,12 +20,36 @@ class TicketPdfService
|
|||||||
* @param Collection<int, Ticket> $tickets
|
* @param Collection<int, Ticket> $tickets
|
||||||
*/
|
*/
|
||||||
public function download(Tenant $tenant, Collection $tickets): Response
|
public function download(Tenant $tenant, Collection $tickets): Response
|
||||||
|
{
|
||||||
|
return $this->pdf($tenant, $tickets)->download($this->filename($tickets));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection<int, Ticket> $tickets
|
||||||
|
*/
|
||||||
|
public function contents(Tenant $tenant, Collection $tickets): string
|
||||||
|
{
|
||||||
|
return $this->pdf($tenant, $tickets)->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection<int, Ticket> $tickets
|
||||||
|
*/
|
||||||
|
public function filename(Collection $tickets): string
|
||||||
|
{
|
||||||
|
return 'tickets_'.$tickets->pluck('id')->implode('_').'.pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection<int, Ticket> $tickets
|
||||||
|
*/
|
||||||
|
private function pdf(Tenant $tenant, Collection $tickets): DomPdf
|
||||||
{
|
{
|
||||||
$tenant->loadMissing('headerLogo');
|
$tenant->loadMissing('headerLogo');
|
||||||
$primaryColor = $this->color($tenant->primary_color, '#009933');
|
$primaryColor = $this->color($tenant->primary_color, '#009933');
|
||||||
$headerBackgroundColor = $this->color($tenant->header_bg_color, $primaryColor);
|
$headerBackgroundColor = $this->color($tenant->header_bg_color, $primaryColor);
|
||||||
|
|
||||||
$pdf = Pdf::loadView('pdf.tickets', [
|
return Pdf::loadView('pdf.tickets', [
|
||||||
'tenant' => $tenant,
|
'tenant' => $tenant,
|
||||||
'tickets' => $tickets,
|
'tickets' => $tickets,
|
||||||
'logoDataUri' => $this->logoDataUri($tenant),
|
'logoDataUri' => $this->logoDataUri($tenant),
|
||||||
@@ -35,10 +60,6 @@ class TicketPdfService
|
|||||||
fn (Ticket $ticket): array => [$ticket->id => $this->qrCodeDataUri($ticket->ticket)]
|
fn (Ticket $ticket): array => [$ticket->id => $this->qrCodeDataUri($ticket->ticket)]
|
||||||
),
|
),
|
||||||
])->setPaper('a4');
|
])->setPaper('a4');
|
||||||
|
|
||||||
$ticketIds = $tickets->pluck('id')->implode('_');
|
|
||||||
|
|
||||||
return $pdf->download("tickets_{$ticketIds}.pdf");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function qrCodeDataUri(string $value): string
|
private function qrCodeDataUri(string $value): string
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<h1 style="margin: 0 0 20px;">Tus tickets ya están disponibles</h1>
|
<h1 style="margin: 0 0 20px;">Tus tickets ya están disponibles</h1>
|
||||||
<p>Generamos {{ $tickets->count() }} {{ $tickets->count() === 1 ? 'ticket' : 'tickets' }} para la compra <strong>#{{ $purchase->id }}</strong>.</p>
|
<p>Generamos {{ $tickets->count() }} {{ $tickets->count() === 1 ? 'ticket' : 'tickets' }} para la compra <strong>#{{ $purchase->id }}</strong>.</p>
|
||||||
|
<p>{{ $tickets->count() === 1 ? 'El ticket está adjunto' : 'Los tickets están adjuntos' }} a este correo en formato PDF.</p>
|
||||||
<ul style="padding-left: 20px;">
|
<ul style="padding-left: 20px;">
|
||||||
@foreach ($tickets as $ticket)
|
@foreach ($tickets as $ticket)
|
||||||
<li style="margin-bottom: 8px;">{{ $ticket->name }}</li>
|
<li style="margin-bottom: 8px;">{{ $ticket->name }}</li>
|
||||||
|
|||||||
@@ -192,11 +192,16 @@ class NotificationMailServiceTest extends TestCase
|
|||||||
&& str_contains($mail->render(), 'border-top: 4px solid #112233')
|
&& str_contains($mail->render(), 'border-top: 4px solid #112233')
|
||||||
&& ! str_contains($mail->render(), 'border-top: 4px solid #ff7006');
|
&& ! str_contains($mail->render(), 'border-top: 4px solid #ff7006');
|
||||||
});
|
});
|
||||||
Mail::assertSent(Mailable::class, function (Mailable $mail): bool {
|
Mail::assertSent(Mailable::class, function (Mailable $mail) use ($ticket): bool {
|
||||||
$mail->assertTo('checkout@example.com');
|
$mail->assertTo('checkout@example.com');
|
||||||
|
$attachment = collect($mail->rawAttachments)->firstWhere('name', "tickets_{$ticket->id}.pdf");
|
||||||
|
|
||||||
return $mail->subject === 'Tus tickets ya están disponibles'
|
return $mail->subject === 'Tus tickets ya están disponibles'
|
||||||
&& str_contains($mail->render(), 'Entrada general')
|
&& str_contains($mail->render(), 'Entrada general')
|
||||||
|
&& str_contains($mail->render(), 'El ticket está adjunto')
|
||||||
|
&& $attachment !== null
|
||||||
|
&& $attachment['options'] === ['mime' => 'application/pdf']
|
||||||
|
&& str_starts_with($attachment['data'], '%PDF-')
|
||||||
&& str_contains($mail->render(), 'border-top: 4px solid #112233')
|
&& str_contains($mail->render(), 'border-top: 4px solid #112233')
|
||||||
&& ! str_contains($mail->render(), 'border-top: 4px solid #ff7006');
|
&& ! str_contains($mail->render(), 'border-top: 4px solid #ff7006');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user