ソースを参照

feat: :sparkles: feat(implementação-envio-de-email) Foi realizado a implementação do envio do comprovante

Foi realizada a criação e os ajustes necessários para o envio automático do comprovante por e-mail no momento em que o serviço for finalizado. Também foi implementada toda a estrutura responsável pela renderização visual do comprovante, incluindo layout HTML responsivo, organização das informações do agendamento, formatação dinâmica de valores, datas e horários, além da integração do disparo automático dentro do fluxo de finalização do agendamento através do FinishScheduleJob.

fase:dev | origin:escopo
kayo henrique 1 ヶ月 前
コミット
ad8eb94087

+ 28 - 11
app/Jobs/FinishScheduleJob.php

@@ -11,6 +11,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
+use App\Services\EmailService;
 use Illuminate\Support\Facades\Log;
 
 class FinishScheduleJob implements ShouldQueue
@@ -26,22 +27,22 @@ class FinishScheduleJob implements ShouldQueue
     try {
       $schedule = Schedule::find($this->scheduleId);
       $date_cleaned = Carbon::parse($schedule->date)->format('Y-m-d');
-  
+
       if (!$schedule) {
         return;
       }
-  
+
       Log::channel('schedule_end_jobs')->info('Verificando status do agendamento id: ' . $schedule->id);
       Log::channel('schedule_end_jobs')->info('Status do agendamento: ' . $schedule->status);
-  
+
       if ($schedule->status !== 'started') {
         return;
       }
-  
+
       Log::channel('schedule_end_jobs')->info('Verificando data');
       Log::channel('schedule_end_jobs')->info('Data do agendamento: ' . $date_cleaned);
       Log::channel('schedule_end_jobs')->info('Data atual: ' . now()->toDateString());
-      
+
 
       if ($date_cleaned > now()->toDateString()) {
         return;
@@ -50,15 +51,15 @@ class FinishScheduleJob implements ShouldQueue
       Log::channel('schedule_end_jobs')->info('Verificando horário');
       Log::channel('schedule_end_jobs')->info('Horário do agendamento: ' . $schedule->end_time);
       Log::channel('schedule_end_jobs')->info('Horário atual: ' . now()->toTimeString());
-  
+
       $end_date_time = Carbon::parse($date_cleaned . ' ' . $schedule->end_time);
-      
+
       if ($end_date_time > now()) {
         return;
       }
-  
+
       Log::channel('schedule_end_jobs')->info('Validado com sucesso, atualizado agendamento id: ' . $schedule->id . ' para status finalizado');
-  
+
       $schedule->update([
         'status' => 'finished'
       ]);
@@ -67,12 +68,28 @@ class FinishScheduleJob implements ShouldQueue
       $provider->update([
         'total_services' => $provider->total_services + 1,
       ]);
-      
+
       $client = Client::find($schedule->client_id);
       $client->update([
         'total_services' => $client->total_services + 1,
       ]);
-
+      $emailService = new EmailService();
+      $serviceAmount = (float) $schedule->total_amount;
+      $serviceFee = 7.00;
+      $finalAmount = $serviceAmount + $serviceFee;
+      $emailService->sendEmailReceipt(
+        email: $schedule->client->user->email,
+        schedule: $schedule,
+        client_name: $schedule->client->user->name,
+        service_date: $schedule->date,
+        start_time: $schedule->start_time,
+        end_time: $schedule->end_time,
+        address: $schedule->address->address,
+        total_amount: $serviceAmount,
+        service_fee: $serviceFee,
+        final_amount: $finalAmount,
+        payment_method: 'PIX'
+      );
     } catch (\Exception $e) {
       Log::channel('schedule_end_jobs')->error('Erro ao finalizar agendamento id: ' . $this->scheduleId . '. Erro: ' . $e->getMessage());
       return;

+ 21 - 4
app/Mail/EmailReceipt.php

@@ -8,32 +8,49 @@ use Illuminate\Mail\Mailables\Content;
 use Illuminate\Mail\Mailables\Envelope;
 use Illuminate\Queue\SerializesModels;
 
-class ComprovanteAgendamentoEmail extends Mailable
+class EmailReceipt extends Mailable
 {
     use Queueable, SerializesModels;
 
     public function __construct(
+
         public readonly object $schedule,
+
         public readonly string $client_name,
+
         public readonly string $service_date,
+
         public readonly string $start_time,
+
         public readonly string $end_time,
+
         public readonly string $address,
+
         public readonly string $total_amount,
+
+        public readonly string $service_fee,
+
+        public readonly string $final_amount,
+
         public readonly string $payment_method,
+
     ) {}
 
     public function envelope(): Envelope
     {
         return new Envelope(
-            subject: __('email.service_completed.subject'),
+
+            subject: __('mail.service_completed.subject'),
+
         );
     }
 
     public function content(): Content
     {
         return new Content(
-            view: 'emails.comprovanteagendamento',
+
+            view: 'emails.email_receipt',
+
         );
     }
 
@@ -41,4 +58,4 @@ class ComprovanteAgendamentoEmail extends Mailable
     {
         return [];
     }
-}
+}

+ 17 - 0
app/Services/EmailService.php

@@ -4,6 +4,7 @@ namespace App\Services;
 
 use App\Mail\SendCodeMail;
 use Illuminate\Support\Facades\Mail;
+use App\Mail\EmailReceipt;
 
 class EmailService
 {
@@ -18,4 +19,20 @@ class EmailService
     {
         Mail::to($email)->send(new SendCodeMail($code, $recipientName));
     }
+
+    public function sendEmailReceipt(
+        string $email, 
+        object $schedule, 
+        string $client_name, 
+        string $service_date, 
+        string $start_time, 
+        string $end_time, 
+        string $address, 
+        string $total_amount, 
+        string $service_fee, 
+        string $final_amount,
+        string $payment_method): void
+    {
+        Mail::to($email)->send(new EmailReceipt($schedule, $client_name, $service_date, $start_time, $end_time, $address, $total_amount, $service_fee, $final_amount, $payment_method));
+    }
 }

+ 26 - 1
lang/en/mail.php

@@ -5,10 +5,35 @@ return [
         'subject'           => 'Your verification code',
         'header_subtitle'   => 'Access verification',
         'greeting'          => 'Hello, :name!',
-        'greeting_anonymous'=> 'Hello!',
+        'greeting_anonymous' => 'Hello!',
         'body_intro'        => 'Use the code below to access your account',
         'expiry_notice'     => 'This code expires in 15 minutes.',
         'ignore_notice'     => 'If you did not request this code, please ignore this email.',
         'footer_note'       => 'This is an automated email. Please do not reply.',
     ],
+    'service_completed' => [
+        'subject' => 'Service completed successfully',
+        'title' => 'Service Completed',
+        'subtitle' => 'Your payment has been successfully confirmed.',
+        'payment_confirmed' => 'Payment confirmed',
+        'payment_confirmed_subtitle' => 'The service has been completed and payment approved.',
+        'service_resume' => 'Service summary',
+        'client' => 'Client',
+        'service_date' => 'Service date',
+        'service_time' => 'Time',
+        'service_address' => 'Address',
+        'payment_title' => 'Payment',
+        'total_amount' => 'Total amount',
+        'payment_method' => 'Payment method',
+        'paid' => 'Paid',
+        'payment_success' => 'Payment processed and confirmed successfully.',
+        'footer_title' => 'Thank you for using Diaria App 💜',
+        'footer_description' => 'We hope your experience was amazing. Count on us for your next services.',
+        'footer_help' => 'Need help?',
+        'footer_contact' => 'Contact our support',
+        'footer_note' => 'This is an automatic email. Please do not reply.',
+        'service_fee' => 'Service fee',
+        'final_amount' => 'Final amount',
+        'currency' => '$',
+    ],
 ];

+ 26 - 1
lang/es/mail.php

@@ -5,10 +5,35 @@ return [
         'subject'           => 'Tu código de verificación',
         'header_subtitle'   => 'Verificación de acceso',
         'greeting'          => '¡Hola, :name!',
-        'greeting_anonymous'=> '¡Hola!',
+        'greeting_anonymous' => '¡Hola!',
         'body_intro'        => 'Usa el código a continuación para acceder a tu cuenta',
         'expiry_notice'     => 'Este código expira en 15 minutos.',
         'ignore_notice'     => 'Si no solicitaste este código, ignora este correo.',
         'footer_note'       => 'Este es un correo automático. Por favor, no respondas.',
     ],
+    'service_completed' => [
+        'subject' => 'Servicio finalizado con éxito',
+        'title' => 'Servicio Finalizado',
+        'subtitle' => 'Su pago fue confirmado con éxito.',
+        'payment_confirmed' => 'Pago confirmado',
+        'payment_confirmed_subtitle' => 'El servicio fue completado y el pago aprobado.',
+        'service_resume' => 'Resumen del servicio',
+        'client' => 'Cliente',
+        'service_date' => 'Fecha del servicio',
+        'service_time' => 'Horario',
+        'service_address' => 'Dirección',
+        'payment_title' => 'Pago',
+        'total_amount' => 'Valor total',
+        'payment_method' => 'Método de pago',
+        'paid' => 'Pagado',
+        'payment_success' => 'Pago procesado y confirmado exitosamente.',
+        'footer_title' => 'Gracias por usar Diaria App 💜',
+        'footer_description' => 'Esperamos que su experiencia haya sido increíble. Cuente con nosotros para sus próximos servicios.',
+        'footer_help' => '¿Necesita ayuda?',
+        'footer_contact' => 'Contacte nuestro soporte',
+        'footer_note' => 'Este es un correo automático. Por favor no responda.',
+        'service_fee' => 'Tarifa del servicio',
+        'final_amount' => 'Valor final',
+        'currency' => 'US$',
+    ],
 ];

+ 26 - 1
lang/pt/mail.php

@@ -5,10 +5,35 @@ return [
         'subject'           => 'Seu código de verificação',
         'header_subtitle'   => 'Verificação de acesso',
         'greeting'          => 'Olá, :name!',
-        'greeting_anonymous'=> 'Olá!',
+        'greeting_anonymous' => 'Olá!',
         'body_intro'        => 'Use o código abaixo para acessar sua conta',
         'expiry_notice'     => 'Este código expira em 15 minutos.',
         'ignore_notice'     => 'Se você não solicitou este código, ignore este e-mail.',
         'footer_note'       => 'Este é um e-mail automático. Por favor, não responda.',
     ],
+    'service_completed' => [
+        'subject' => 'Serviço finalizado com sucesso',
+        'title' => 'Serviço Finalizado',
+        'subtitle' => 'Seu pagamento foi confirmado com sucesso.',
+        'payment_confirmed' => 'Pagamento confirmado',
+        'payment_confirmed_subtitle' => 'O serviço foi concluído e o pagamento aprovado.',
+        'service_resume' => 'Resumo do serviço',
+        'client' => 'Cliente',
+        'service_date' => 'Data do serviço',
+        'service_time' => 'Horário',
+        'service_address' => 'Endereço',
+        'payment_title' => 'Pagamento',
+        'total_amount' => 'Valor total',
+        'payment_method' => 'Forma de pagamento',
+        'paid' => 'Pago',
+        'payment_success' => 'Pagamento processado e confirmado com sucesso.',
+        'footer_title' => 'Obrigado por utilizar o Diaria App 💜',
+        'footer_description' => 'Esperamos que sua experiência tenha sido incrível. Conte sempre com a gente para seus próximos serviços.',
+        'footer_help' => 'Precisa de ajuda?',
+        'footer_contact' => 'Fale com nosso suporte',
+        'footer_note' => 'Este é um e-mail automático. Não responda esta mensagem.',
+        'service_fee' => 'Taxa de serviço',
+        'final_amount' => 'Valor final',
+        'currency' => 'R$',
+    ],
 ];

ファイルの差分が大きいため隠しています
+ 6 - 0
public/images/diarinho_perfil_cliente_favoritos.svg


+ 147 - 68
resources/views/emails/email_receipt.blade.php

@@ -1,12 +1,23 @@
 <!DOCTYPE html>
 <html lang="pt-BR">
+
 <head>
+
   <meta charset="UTF-8" />
-  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
 
-  <title>{{ __('email.service_completed.subject') }}</title>
+  <meta
+    name="viewport"
+    content="width=device-width, initial-scale=1.0"
+  />
+
+  <title>
+    {{ __('mail.service_completed.subject') }}
+  </title>
+
+  <!-- CSS -->
 
   <style>
+
     * {
       margin: 0;
       padding: 0;
@@ -28,17 +39,14 @@
       box-shadow: 0 10px 40px rgba(0,0,0,0.08);
     }
 
+    /* HEADER */
+
     .header {
       background: linear-gradient(135deg, #7b2cff, #4d14d1);
       padding: 50px 30px 30px;
       color: white;
     }
 
-    .logo {
-      width: 140px;
-      margin-bottom: 35px;
-    }
-
     .header-content {
       display: flex;
       align-items: center;
@@ -67,11 +75,15 @@
       width: 220px;
     }
 
+    /* CONTENT */
+
     .content {
       padding: 30px;
       background: #f5f5f7;
     }
 
+    /* STATUS */
+
     .success-card {
       background: white;
       border-radius: 22px;
@@ -107,6 +119,8 @@
       font-size: 18px;
     }
 
+    /* CARD */
+
     .card {
       background: white;
       border-radius: 24px;
@@ -153,6 +167,8 @@
       background: #ececec;
     }
 
+    /* PAYMENT */
+
     .payment-wrapper {
       display: flex;
       justify-content: space-between;
@@ -164,10 +180,47 @@
       flex: 1;
     }
 
-    .payment-total h2 {
+    .payment-summary {
+      margin-top: 18px;
+    }
+
+    .payment-row {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 18px;
+    }
+
+    .payment-row span {
+      font-size: 18px;
+      color: #666;
+    }
+
+    .payment-row strong {
+      font-size: 22px;
+      color: #1f1f1f;
+    }
+
+    .payment-final {
+      border-top: 1px solid #ececec;
+      padding-top: 22px;
+      margin-top: 10px;
+
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+    }
+
+    .payment-final-label {
+      font-size: 24px;
+      font-weight: 700;
+      color: #5a22d6;
+    }
+
+    .payment-final-value {
+      font-size: 42px;
+      font-weight: 800;
       color: #5a22d6;
-      font-size: 54px;
-      margin-top: 12px;
     }
 
     .payment-method {
@@ -197,6 +250,8 @@
       font-weight: bold;
     }
 
+    /* FOOTER CARD */
+
     .footer-card {
       background: #f1ebff;
       border-radius: 24px;
@@ -224,6 +279,8 @@
       font-size: 18px;
     }
 
+    /* FOOTER */
+
     .footer {
       background: linear-gradient(135deg, #5d1ce0, #3f0fb3);
       padding: 35px 30px;
@@ -238,11 +295,6 @@
       flex-wrap: wrap;
     }
 
-    .footer-logo {
-      width: 120px;
-      margin-bottom: 10px;
-    }
-
     .footer-app {
       font-size: 30px;
       font-weight: 700;
@@ -262,6 +314,8 @@
       opacity: 0.7;
     }
 
+    /* RESPONSIVE */
+
     @media(max-width: 640px) {
 
       .header-content,
@@ -292,15 +346,19 @@
         font-size: 22px;
       }
 
-      .payment-total h2 {
-        font-size: 42px;
+      .payment-final {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 10px;
       }
 
       .footer-help {
         text-align: left;
       }
     }
+
   </style>
+
 </head>
 
 <body>
@@ -311,33 +369,28 @@
 
   <div class="header">
 
-    <img
-      src="{{ asset('logo.png') }}"
-      class="logo"
-      alt="Sob Medida"
-    />
-
     <div class="header-content">
 
       <div class="header-text">
 
         <div class="title">
-          {{ __('email.service_completed.title') }}
+          {{ __('mail.service_completed.title') }}
         </div>
 
         <div class="subtitle">
-          {{ __('email.service_completed.subtitle') }}
+          {{ __('mail.service_completed.subtitle') }}
         </div>
 
       </div>
 
       <img
-        src="{{ asset('diarinho.png') }}"
+        src="{{ asset('images/diarinho.png') }}"
         class="diarinho"
         alt="Diarinho"
       />
 
     </div>
+
   </div>
 
   <!-- CONTENT -->
@@ -355,23 +408,23 @@
       <div>
 
         <div class="success-title">
-          {{ __('email.service_completed.payment_confirmed') }}
+          {{ __('mail.service_completed.payment_confirmed') }}
         </div>
 
         <div class="success-subtitle">
-          {{ __('email.service_completed.payment_confirmed_subtitle') }}
+          {{ __('mail.service_completed.payment_confirmed_subtitle') }}
         </div>
 
       </div>
 
     </div>
 
-    <!-- RESUMO -->
+    <!-- SERVICE -->
 
     <div class="card">
 
       <div class="card-title">
-        {{ __('email.service_completed.service_resume') }}
+        {{ __('mail.service_completed.service_resume') }}
       </div>
 
       <div class="service-grid">
@@ -379,33 +432,39 @@
         <div class="service-column">
 
           <div class="item">
+
             <div class="item-label">
-              {{ __('email.service_completed.client') }}
+              {{ __('mail.service_completed.client') }}
             </div>
 
             <div class="item-value">
               {{ $client_name }}
             </div>
+
           </div>
 
           <div class="item">
+
             <div class="item-label">
-              {{ __('email.service_completed.service_date') }}
+              {{ __('mail.service_completed.service_date') }}
             </div>
 
             <div class="item-value">
-              {{ $service_date }}
+              {{ \Carbon\Carbon::parse($service_date)->format('d/m/Y') }}
             </div>
+
           </div>
 
           <div class="item">
 
             <div class="item-label">
-              {{ __('email.service_completed.service_time') }}
+              {{ __('mail.service_completed.service_time') }}
             </div>
 
             <div class="item-value">
-              {{ $start_time }} às {{ $end_time }}
+              {{ substr($start_time, 0, 5) }}
+              às
+              {{ substr($end_time, 0, 5) }}
             </div>
 
           </div>
@@ -419,7 +478,7 @@
           <div class="item">
 
             <div class="item-label">
-              {{ __('email.service_completed.service_address') }}
+              {{ __('mail.service_completed.service_address') }}
             </div>
 
             <div class="item-value">
@@ -434,25 +493,60 @@
 
     </div>
 
-    <!-- PAGAMENTO -->
+    <!-- PAYMENT -->
 
     <div class="card">
 
       <div class="card-title">
-        {{ __('email.service_completed.payment_title') }}
+        {{ __('mail.service_completed.payment_title') }}
       </div>
 
       <div class="payment-wrapper">
 
         <div class="payment-total">
 
-          <div class="item-label">
-            {{ __('email.service_completed.total_amount') }}
-          </div>
+          <div class="payment-summary">
+
+            <div class="payment-row">
 
-          <h2>
-            {{ $total_amount }}
-          </h2>
+              <span>
+                {{ __('mail.service_completed.total_amount') }}
+              </span>
+
+              <strong>
+                {{ __('mail.currency') }}
+                {{ number_format($total_amount, 2, ',', '.') }}
+              </strong>
+
+            </div>
+
+            <div class="payment-row">
+
+              <span>
+                {{ __('mail.service_completed.service_fee') }}
+              </span>
+
+              <strong>
+                {{ __('mail.currency') }}
+                {{ number_format($service_fee, 2, ',', '.') }}
+              </strong>
+
+            </div>
+
+            <div class="payment-final">
+
+              <span class="payment-final-label">
+                {{ __('mail.service_completed.final_amount') }}
+              </span>
+
+              <span class="payment-final-value">
+                {{ __('mail.currency') }}
+                {{ number_format($final_amount, 2, ',', '.') }}
+              </span>
+
+            </div>
+
+          </div>
 
         </div>
 
@@ -461,7 +555,7 @@
         <div class="payment-method">
 
           <div class="item-label">
-            {{ __('email.service_completed.payment_method') }}
+            {{ __('mail.service_completed.payment_method') }}
           </div>
 
           <div class="payment-badge">
@@ -469,21 +563,11 @@
             {{ $payment_method }}
 
             <span class="paid-status">
-              {{ __('email.service_completed.paid') }}
+              {{ __('mail.service_completed.paid') }}
             </span>
 
           </div>
 
-          <div
-            style="
-              margin-top: 16px;
-              color: #777;
-              font-size: 16px;
-            "
-          >
-            {{ __('email.service_completed.payment_success') }}
-          </div>
-
         </div>
 
       </div>
@@ -495,7 +579,7 @@
     <div class="footer-card">
 
       <img
-        src="{{ asset('diarinho.png') }}"
+        src="{{ asset('images/diarinho.png') }}"
         class="footer-diarinho"
         alt="Diarinho"
       />
@@ -503,11 +587,11 @@
       <div>
 
         <div class="footer-title">
-          {{ __('email.service_completed.footer_title') }}
+          {{ __('mail.service_completed.footer_title') }}
         </div>
 
         <div class="footer-text">
-          {{ __('email.service_completed.footer_description') }}
+          {{ __('mail.service_completed.footer_description') }}
         </div>
 
       </div>
@@ -524,12 +608,6 @@
 
       <div>
 
-        <img
-          src="{{ asset('logo.png') }}"
-          class="footer-logo"
-          alt="Sob Medida"
-        />
-
         <div class="footer-app">
           diariaapp
         </div>
@@ -538,9 +616,9 @@
 
       <div class="footer-help">
 
-        {{ __('email.service_completed.footer_help') }}<br>
+        {{ __('mail.service_completed.footer_help') }}<br>
 
-        {{ __('email.service_completed.footer_contact') }}<br>
+        {{ __('mail.service_completed.footer_contact') }}<br>
 
         (11) 99999-9999
 
@@ -549,7 +627,7 @@
     </div>
 
     <div class="footer-bottom">
-      {{ __('email.service_completed.footer_note') }}
+      {{ __('mail.service_completed.footer_note') }}
     </div>
 
   </div>
@@ -557,4 +635,5 @@
 </div>
 
 </body>
+
 </html>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません