Files
portal/app/resources/views/emails/reminder.blade.php
T
Дмитрий 39b6127bce phase2(reminders-backend): CRUD + cron-диспетчер + email/inapp (P0 этап 4)
Закрыт пункт «Reminders ⏸ no-view» из AppLayout nav-tree. Schema-таблица
reminders уже была в v8.10 §17.5 — теперь работает целиком backend-side.

Backend:
- App\Models\Reminder — Eloquent с casts/relations + isCompleted/isOverdue.
- ReminderFactory с states overdue/completed/sent.
- App\Http\Controllers\Api\ReminderController под auth:sanctum:
  GET ?filter=&deal_id=&limit= (active/today/upcoming/overdue/completed,
    окно ±1 день, counts для UI badges);
  POST {deal_id, text?, remind_at, assignee_id?} (FK guard на assignee);
  PATCH {id} (при смене remind_at сбрасывает is_sent+sent_at для retrigger);
  POST {id}/complete (idempotent);
  DELETE {id}.
  RLS-обёртка + defense-in-depth where('tenant_id').
- App\Mail\ReminderDueNotification + emails/reminder.blade.php (Forest,
  TZ из recipient.timezone).
- NotificationService::notifyReminder(Reminder) — recipient = assignee_id
  ?? created_by (если active+!deleted). Каналы email+inapp по prefs.
  payload {reminder_id, deal_id} для UI deep-link.
- App\Console\Commands\RemindersDispatchDue — cron reminders:dispatch-due
  {--dry-run} {--limit=500}. По одному reminder в DB::transaction (SET
  LOCAL app.current_tenant_id нельзя переключать). После notifyReminder
  ставит is_sent=true даже если recipient deactivated (защита от retry-spam).

Pest +32 (347/347 за 41.21 сек, 1203 assertions):
- ReminderControllerTest 21: 401 / RLS / 5 filter'ов / counts / deal_id /
  store + FK guard / update text+remind_at сбрасывает is_sent / complete
  idempotent / delete + 404 чужой.
- RemindersDispatchDueTest 11: due → email+inapp / future skip / completed
  skip / уже sent / assignee вместо created_by / deactivated user (is_sent
  всё равно) / только inapp при email=false / --dry-run / --limit / RLS.

PHPStan baseline регенерирован. IDE-helper для всех моделей.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:30:38 +03:00

40 lines
1.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Напоминание</title>
</head>
<body style="font-family: Inter, -apple-system, sans-serif; max-width: 600px; margin: 0 auto; padding: 24px; color: #081319;">
<h1 style="color: #0F6E56; font-size: 20px;">Лидерра. Напоминание</h1>
<p>Здравствуйте, {{ $recipient->first_name ?? $recipient->email }}.</p>
<p>Наступил срок напоминания по сделке <strong>#{{ $reminder->deal_id }}</strong>.</p>
@if ($reminder->text)
<blockquote style="border-left: 3px solid #0F6E56; padding: 8px 14px; background: #F0F8F5; margin: 16px 0;">
{{ $reminder->text }}
</blockquote>
@endif
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
<tr>
<td style="padding: 8px 12px; background: #F6F3EC; width: 140px; vertical-align: top;">Запланировано</td>
<td style="padding: 8px 12px; background: #FFFFFF; border-left: 1px solid #E1EEEA;">
{{ $reminder->remind_at?->setTimezone($recipient->timezone ?? 'Europe/Moscow')->format('d.m.Y H:i') }}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; background: #F6F3EC; vertical-align: top;">Сделка</td>
<td style="padding: 8px 12px; background: #FFFFFF; border-left: 1px solid #E1EEEA;">#{{ $reminder->deal_id }}</td>
</tr>
</table>
<p style="margin-top: 24px;">Откройте CRM, чтобы пометить напоминание выполненным.</p>
<p style="color: #66635C; font-size: 12px; margin-top: 32px;">
Это автоматическое уведомление о событии «Напоминание». Чтобы изменить настройки уведомлений перейдите в Настройки Уведомления.
</p>
</body>
</html>