Faza 2 (din continuare): Email notifications
4 Mailables auto-trigger pe model events: - WorkOrderReadyMail: la WO.status → 'ready', către client.email • Atașat PDF fișa lucru (via WorkOrderPdfService) • Total/achitat/rest, recomandări (warning box) - PaymentReceivedMail: la Payment::created, confirmare cu sumă/metodă/ref - AppointmentConfirmedMail: la Appointment::created status='scheduled' - ServiceReminderMail: dispatch manual (vehicle, type=itp/oil/general, note) Layout email branded (resources/views/emails/layout.blade.php): - Header cu logo tenant + theme_color border-bottom - Footer cu telefon/email/disclaimer - Stiluri inline (compatibil tot mail client) Settings page extins cu 4 toggle: - 'Mașina e gata de ridicat' - 'Confirmare plată primită' - 'Programare confirmată' - 'Reminder ITP / revizie' Salvate în companies.settings.notify (JSON), default true. NotificationDispatcher service centralizat: - Verifică isEnabled() pe settings.notify[$key] - Skip dacă client n-are email - Try/catch + Log::warning pe eșec (nu crapă request-ul) Mailables folosesc UsesTenantBranding trait pentru context unitar. Test prin Mailpit: https://mailpit.service.mir.md (capturează toate).
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
@component('emails.layout', [
|
||||
'companyName' => $companyName,
|
||||
'themeColor' => $themeColor,
|
||||
'phone' => $phone ?? null,
|
||||
'email' => $email ?? null,
|
||||
'city' => $city ?? null,
|
||||
'logoUrl' => $logoUrl ?? null,
|
||||
'title' => 'Programare confirmată',
|
||||
])
|
||||
<h2 style="margin:0 0 12px;font-size:20px;color:{{ $themeColor }};">📅 Programare confirmată</h2>
|
||||
<p>Bună ziua{{ $client?->name ? ', ' . $client->name : '' }},</p>
|
||||
<p>Programarea dvs. este confirmată:</p>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;background:#f9fafb;border-radius:8px;">
|
||||
<tr><td style="padding:14px 18px;font-size:13px;color:#374151;">Data</td>
|
||||
<td style="padding:14px 18px;font-size:18px;text-align:right;font-weight:700;color:{{ $themeColor }};">
|
||||
{{ $appointment->date?->format('d.m.Y') }}
|
||||
</td></tr>
|
||||
<tr><td style="padding:8px 18px;font-size:13px;color:#374151;">Ora</td>
|
||||
<td style="padding:8px 18px;font-size:14px;text-align:right;font-weight:600;">
|
||||
{{ \Illuminate\Support\Str::substr($appointment->time_start, 0, 5) }} – {{ \Illuminate\Support\Str::substr($appointment->time_end, 0, 5) }}
|
||||
</td></tr>
|
||||
@if ($appointment->title)
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Subiect</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;">{{ $appointment->title }}</td></tr>
|
||||
@endif
|
||||
@if ($vehicle)
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Auto</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;">
|
||||
{{ $vehicle->make }} {{ $vehicle->model }}
|
||||
@if ($vehicle->plate) <b>[{{ $vehicle->plate }}]</b> @endif
|
||||
</td></tr>
|
||||
@endif
|
||||
@if ($master)
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Maistru</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;">{{ $master->name }}</td></tr>
|
||||
@endif
|
||||
@if ($post)
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Pod</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;">{{ $post->name }}</td></tr>
|
||||
@endif
|
||||
</table>
|
||||
|
||||
<p>Vă așteptăm! 🚗</p>
|
||||
<p style="font-size:12px;color:#6b7280;">Pentru reprogramare sau anulare, sunați-ne la {{ $phone ?? '—' }}.</p>
|
||||
@endcomponent
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{{ $title ?? 'AutoCRM' }}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:#f3f4f6;font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;color:#1f2937;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f3f4f6;padding:24px 0;">
|
||||
<tr><td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="background:#fff;border-radius:10px;box-shadow:0 1px 3px rgba(0,0,0,.06);overflow:hidden;max-width:600px;width:100%;">
|
||||
<tr><td style="padding:24px 28px;border-bottom:3px solid {{ $themeColor ?? '#3b82f6' }};">
|
||||
@if (!empty($logoUrl))
|
||||
<img src="{{ $logoUrl }}" alt="logo" style="max-height:42px;display:block;">
|
||||
@else
|
||||
<div style="font-size:20px;font-weight:700;color:{{ $themeColor ?? '#3b82f6' }};">{{ $companyName ?? 'AutoCRM' }}</div>
|
||||
@endif
|
||||
@if (!empty($city))
|
||||
<div style="font-size:12px;color:#6b7280;margin-top:4px;">{{ $city }}</div>
|
||||
@endif
|
||||
</td></tr>
|
||||
|
||||
<tr><td style="padding:24px 28px;font-size:14px;line-height:1.6;">
|
||||
{{ $slot }}
|
||||
</td></tr>
|
||||
|
||||
<tr><td style="padding:16px 28px;border-top:1px solid #e5e7eb;background:#f9fafb;font-size:11px;color:#6b7280;">
|
||||
<div>{{ $companyName ?? 'AutoCRM' }}</div>
|
||||
@if (!empty($phone)) <div>📞 {{ $phone }}</div> @endif
|
||||
@if (!empty($email)) <div>✉ {{ $email }}</div> @endif
|
||||
<div style="margin-top:8px;color:#9ca3af;">Acest email a fost generat automat. Vă rugăm nu răspundeți.</div>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
@component('emails.layout', [
|
||||
'companyName' => $companyName,
|
||||
'themeColor' => $themeColor,
|
||||
'phone' => $phone ?? null,
|
||||
'email' => $email ?? null,
|
||||
'city' => $city ?? null,
|
||||
'logoUrl' => $logoUrl ?? null,
|
||||
'title' => 'Plată confirmată',
|
||||
])
|
||||
<h2 style="margin:0 0 12px;font-size:20px;color:{{ $themeColor }};">💳 Plată confirmată</h2>
|
||||
<p>Bună ziua{{ $client?->name ? ', ' . $client->name : '' }},</p>
|
||||
<p>Confirmăm primirea plății dvs.:</p>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;background:#f9fafb;border-radius:8px;">
|
||||
<tr><td style="padding:14px 18px;font-size:13px;color:#374151;">Sumă achitată</td>
|
||||
<td style="padding:14px 18px;font-size:18px;text-align:right;font-weight:700;color:#059669;">
|
||||
{{ number_format((float) $payment->amount, 2, '.', ' ') }} {{ $currency }}
|
||||
</td></tr>
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Metoda</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;">
|
||||
{{ \App\Models\Tenant\Payment::METHODS[$payment->method] ?? $payment->method }}
|
||||
</td></tr>
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Data</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;">{{ $payment->paid_at?->format('d.m.Y') }}</td></tr>
|
||||
@if ($payment->reference)
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Referință</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;font-family:monospace;">{{ $payment->reference }}</td></tr>
|
||||
@endif
|
||||
@if ($workOrder)
|
||||
<tr><td style="padding:8px 18px;font-size:12px;color:#6b7280;">Fișă lucru</td>
|
||||
<td style="padding:8px 18px;font-size:12px;text-align:right;font-weight:600;">{{ $workOrder->number }}</td></tr>
|
||||
@endif
|
||||
</table>
|
||||
|
||||
<p>Vă mulțumim!</p>
|
||||
@endcomponent
|
||||
@@ -0,0 +1,46 @@
|
||||
@php
|
||||
$titles = [
|
||||
'itp' => '🛡️ Reminder ITP',
|
||||
'oil' => '🛢️ Reminder schimb ulei',
|
||||
'general' => '🔧 Reminder revizie',
|
||||
];
|
||||
$body = [
|
||||
'itp' => 'ITP-ul autoturismului dvs. expiră în curând. Programați-vă din timp pentru a evita amenzile.',
|
||||
'oil' => 'A trecut o perioadă de la ultimul schimb de ulei. Recomandăm o nouă verificare.',
|
||||
'general' => 'Vă reamintim că autoturismul dvs. necesită o verificare programată.',
|
||||
];
|
||||
@endphp
|
||||
@component('emails.layout', [
|
||||
'companyName' => $companyName,
|
||||
'themeColor' => $themeColor,
|
||||
'phone' => $phone ?? null,
|
||||
'email' => $email ?? null,
|
||||
'city' => $city ?? null,
|
||||
'logoUrl' => $logoUrl ?? null,
|
||||
'title' => $titles[$reminderType] ?? 'Reminder',
|
||||
])
|
||||
<h2 style="margin:0 0 12px;font-size:20px;color:{{ $themeColor }};">{{ $titles[$reminderType] ?? 'Reminder' }}</h2>
|
||||
<p>Bună ziua{{ $client?->name ? ', ' . $client->name : '' }},</p>
|
||||
<p>{{ $body[$reminderType] ?? 'Vă reamintim despre vehiculul dvs.' }}</p>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;border:1px solid #e5e7eb;border-radius:8px;">
|
||||
<tr><td style="padding:14px;">
|
||||
<div style="font-size:11px;text-transform:uppercase;color:#6b7280;letter-spacing:.5px;">Vehicul</div>
|
||||
<div style="font-size:16px;font-weight:700;margin-top:4px;">
|
||||
{{ $vehicle->make }} {{ $vehicle->model }} {{ $vehicle->year ?? '' }}
|
||||
@if ($vehicle->plate) <span style="color:{{ $themeColor }};">[{{ $vehicle->plate }}]</span> @endif
|
||||
</div>
|
||||
@if ($vehicle->mileage)
|
||||
<div style="font-size:12px;color:#6b7280;margin-top:4px;">Kilometraj: {{ number_format($vehicle->mileage, 0, '.', ' ') }} km</div>
|
||||
@endif
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
@if ($note)
|
||||
<div style="background:#fef3c7;border-left:4px solid #f59e0b;padding:12px;margin:16px 0;border-radius:6px;font-size:13px;">
|
||||
{{ $note }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<p>Programați-vă online sau sunați la <b>{{ $phone ?? '—' }}</b>.</p>
|
||||
@endcomponent
|
||||
@@ -0,0 +1,56 @@
|
||||
@php $balance = max(0, $total - $paid); @endphp
|
||||
@component('emails.layout', [
|
||||
'companyName' => $companyName,
|
||||
'themeColor' => $themeColor,
|
||||
'phone' => $phone ?? null,
|
||||
'email' => $email ?? null,
|
||||
'city' => $city ?? null,
|
||||
'logoUrl' => $logoUrl ?? null,
|
||||
'title' => 'Mașina e gata',
|
||||
])
|
||||
<h2 style="margin:0 0 12px;font-size:20px;color:{{ $themeColor }};">✅ Mașina dvs. este gata!</h2>
|
||||
<p>Bună ziua{{ $client?->name ? ', ' . $client->name : '' }},</p>
|
||||
<p>Vă informăm că lucrările pentru autoturismul dvs. au fost finalizate. Puteți veni să-l ridicați.</p>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;border:1px solid #e5e7eb;border-radius:8px;">
|
||||
<tr><td style="padding:14px;">
|
||||
<div style="font-size:11px;text-transform:uppercase;color:#6b7280;letter-spacing:.5px;">Fișă lucru</div>
|
||||
<div style="font-size:16px;font-weight:700;margin-top:4px;">{{ $workOrder->number }}</div>
|
||||
@if ($vehicle)
|
||||
<div style="margin-top:10px;font-size:13px;">
|
||||
{{ $vehicle->make }} {{ $vehicle->model }} {{ $vehicle->year ?? '' }}
|
||||
@if ($vehicle->plate) <b style="color:{{ $themeColor }};">[{{ $vehicle->plate }}]</b> @endif
|
||||
</div>
|
||||
@endif
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;background:#f9fafb;border-radius:8px;">
|
||||
<tr>
|
||||
<td style="padding:12px 16px;font-size:13px;color:#374151;">Total</td>
|
||||
<td style="padding:12px 16px;font-size:13px;text-align:right;font-weight:600;">{{ number_format($total, 2, '.', ' ') }} {{ $currency }}</td>
|
||||
</tr>
|
||||
@if ($paid > 0)
|
||||
<tr>
|
||||
<td style="padding:6px 16px;font-size:12px;color:#6b7280;">Achitat</td>
|
||||
<td style="padding:6px 16px;font-size:12px;text-align:right;color:#059669;">{{ number_format($paid, 2, '.', ' ') }} {{ $currency }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if ($balance > 0)
|
||||
<tr>
|
||||
<td style="padding:12px 16px;font-size:14px;font-weight:700;border-top:1px solid #e5e7eb;color:{{ $themeColor }};">Rest de plată</td>
|
||||
<td style="padding:12px 16px;font-size:16px;text-align:right;font-weight:700;border-top:1px solid #e5e7eb;color:{{ $themeColor }};">{{ number_format($balance, 2, '.', ' ') }} {{ $currency }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
|
||||
@if ($workOrder->recommendations)
|
||||
<div style="background:#fef3c7;border-left:4px solid #f59e0b;padding:12px;margin:16px 0;border-radius:6px;font-size:13px;">
|
||||
<b style="color:#92400e;">⚠ Recomandări:</b><br>
|
||||
{{ $workOrder->recommendations }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<p style="margin-top:20px;">Atașat veți găsi <b>fișa de lucru completă</b> (PDF).</p>
|
||||
<p>Vă mulțumim pentru încredere!</p>
|
||||
@endcomponent
|
||||
Reference in New Issue
Block a user